From 267c6f2ac71f92999e969232431ba04678e7437e Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 07:54:39 +0200 Subject: Adding upstream version 4:24.2.0. Signed-off-by: Daniel Baumann --- sw/qa/core/txtnode/data/btlr-cell-chinese.doc | Bin 0 -> 38912 bytes .../core/txtnode/data/floattable-anchor-split.docx | Bin 0 -> 13035 bytes sw/qa/core/txtnode/data/fly-anchor-undo.odt | Bin 0 -> 10299 bytes .../data/special-insert-after-merged-cells.fodt | 25 + sw/qa/core/txtnode/data/tdf157287.odt | Bin 0 -> 11460 bytes sw/qa/core/txtnode/data/textbox-copy-anchor.docx | Bin 0 -> 224854 bytes sw/qa/core/txtnode/data/textbox-node-split.docx | Bin 0 -> 20929 bytes .../core/txtnode/data/title-field-invalidate.fodt | 25 + sw/qa/core/txtnode/justify.cxx | 185 +++++++ sw/qa/core/txtnode/txtnode.cxx | 544 +++++++++++++++++++++ 10 files changed, 779 insertions(+) create mode 100644 sw/qa/core/txtnode/data/btlr-cell-chinese.doc create mode 100644 sw/qa/core/txtnode/data/floattable-anchor-split.docx create mode 100644 sw/qa/core/txtnode/data/fly-anchor-undo.odt create mode 100644 sw/qa/core/txtnode/data/special-insert-after-merged-cells.fodt create mode 100644 sw/qa/core/txtnode/data/tdf157287.odt create mode 100644 sw/qa/core/txtnode/data/textbox-copy-anchor.docx create mode 100644 sw/qa/core/txtnode/data/textbox-node-split.docx create mode 100644 sw/qa/core/txtnode/data/title-field-invalidate.fodt create mode 100644 sw/qa/core/txtnode/justify.cxx create mode 100644 sw/qa/core/txtnode/txtnode.cxx (limited to 'sw/qa/core/txtnode') diff --git a/sw/qa/core/txtnode/data/btlr-cell-chinese.doc b/sw/qa/core/txtnode/data/btlr-cell-chinese.doc new file mode 100644 index 0000000000..a4a70f6779 Binary files /dev/null and b/sw/qa/core/txtnode/data/btlr-cell-chinese.doc differ diff --git a/sw/qa/core/txtnode/data/floattable-anchor-split.docx b/sw/qa/core/txtnode/data/floattable-anchor-split.docx new file mode 100644 index 0000000000..a5dcdb28eb Binary files /dev/null and b/sw/qa/core/txtnode/data/floattable-anchor-split.docx differ diff --git a/sw/qa/core/txtnode/data/fly-anchor-undo.odt b/sw/qa/core/txtnode/data/fly-anchor-undo.odt new file mode 100644 index 0000000000..dd2093161f Binary files /dev/null and b/sw/qa/core/txtnode/data/fly-anchor-undo.odt differ diff --git a/sw/qa/core/txtnode/data/special-insert-after-merged-cells.fodt b/sw/qa/core/txtnode/data/special-insert-after-merged-cells.fodt new file mode 100644 index 0000000000..55ba746b66 --- /dev/null +++ b/sw/qa/core/txtnode/data/special-insert-after-merged-cells.fodt @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sw/qa/core/txtnode/data/tdf157287.odt b/sw/qa/core/txtnode/data/tdf157287.odt new file mode 100644 index 0000000000..3ff6c4c6b9 Binary files /dev/null and b/sw/qa/core/txtnode/data/tdf157287.odt differ diff --git a/sw/qa/core/txtnode/data/textbox-copy-anchor.docx b/sw/qa/core/txtnode/data/textbox-copy-anchor.docx new file mode 100644 index 0000000000..b835097f1b Binary files /dev/null and b/sw/qa/core/txtnode/data/textbox-copy-anchor.docx differ diff --git a/sw/qa/core/txtnode/data/textbox-node-split.docx b/sw/qa/core/txtnode/data/textbox-node-split.docx new file mode 100644 index 0000000000..5760ee6e2d Binary files /dev/null and b/sw/qa/core/txtnode/data/textbox-node-split.docx differ diff --git a/sw/qa/core/txtnode/data/title-field-invalidate.fodt b/sw/qa/core/txtnode/data/title-field-invalidate.fodt new file mode 100644 index 0000000000..e7d07cab7b --- /dev/null +++ b/sw/qa/core/txtnode/data/title-field-invalidate.fodt @@ -0,0 +1,25 @@ + + + + mysubject + mytitle + 1.1 + + + + + + + + + + mysubject mytitle 1.1 May 18, 2021 + + + + + + body text + + + diff --git a/sw/qa/core/txtnode/justify.cxx b/sw/qa/core/txtnode/justify.cxx new file mode 100644 index 0000000000..f8f178543a --- /dev/null +++ b/sw/qa/core/txtnode/justify.cxx @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include + +namespace +{ +/// Covers sw/source/core/txtnode/justify fixes. +class SwCoreJustifyTest : public SwModelTestBase +{ +}; + +/// CharWidthArray: store char widths because they are more readable. +class CharWidthArray +{ +public: + KernArray maArray; + template CharWidthArray(Args&&... args) + { + for (auto arg : { args... }) + maArray.push_back(arg); + } + template void InvokeWithKernArray(Function f); + void ConvertToKernArray(); + void ConvertToCharWidths(); +}; + +inline bool operator==(const CharWidthArray& lhs, const CharWidthArray& rhs) +{ + return lhs.maArray == rhs.maArray; +} + +std::ostream& operator<<(std::ostream& rStrm, const CharWidthArray& rCharWidthArray) +{ + const KernArray& rArray(rCharWidthArray.maArray); + sal_Int32 nLen = rArray.size(); + rStrm << "{ "; + for (sal_Int32 i = 0; i < nLen; ++i) + { + rStrm << rArray[i]; + rStrm << (i < nLen - 1 ? ", " : " "); + } + rStrm << "}"; + return rStrm; +} + +void CharWidthArray::ConvertToKernArray() +{ + for (std::size_t i = 1; i < maArray.size(); ++i) + maArray.adjust(i, maArray[i - 1]); +} + +void CharWidthArray::ConvertToCharWidths() +{ + for (sal_Int32 i = maArray.size() - 1; i > 0; --i) + maArray.adjust(i, -maArray[i - 1]); +} + +/// Convert maArray to kern array values, then invoke the function, and convert it back. +template void CharWidthArray::InvokeWithKernArray(Function f) +{ + ConvertToKernArray(); + f(); + ConvertToCharWidths(); +} +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSpaceDistributionHalfSpace) +{ + // Related to: tdf#149017 + static constexpr OUStringLiteral aText = u"ne del pro"; + CharWidthArray aActual{ 720, 639, 360, 720, 639, 400, 360, 720, 480, 720 }; + CharWidthArray aExpected{ 720, 851, 573, 720, 639, 612, 573, 720, 480, 720 }; + + aActual.InvokeWithKernArray( + [&] { sw::Justify::SpaceDistribution(aActual.maArray, aText, 0, 10, 425, 0, false); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSpaceDistributionNoHalfSpace) +{ + // Related to: tdf#149017 + static constexpr OUStringLiteral aText = u"ne del pro"; + CharWidthArray aActual{ 720, 639, 360, 720, 639, 400, 360, 720, 480, 720 }; + CharWidthArray aExpected{ 720, 639, 785, 720, 639, 400, 785, 720, 480, 720 }; + + aActual.InvokeWithKernArray( + [&] { sw::Justify::SpaceDistribution(aActual.maArray, aText, 0, 10, 425, 0, true); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSpaceDistributionUnicodeIVS) +{ + // Related to: tdf#148594 + static constexpr OUStringLiteral aText + = u"\u9B54\u9AD8\u4E00\U000E01E1\u4E08\u4F55\u9B54\u9AD8\u4E00\U000E01E1"; + CharWidthArray aActual{ 1600, 1600, 1600, 0, 0, 1600, 1600, 1600, 1600, 1600, 0, 0 }; + CharWidthArray aExpected{ 1800, 1800, 1800, 0, 0, 1800, 1800, 1800, 1800, 1800, 0, 0 }; + aActual.InvokeWithKernArray( + [&] { sw::Justify::SpaceDistribution(aActual.maArray, aText, 0, 12, 0, 200, false); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGrid) +{ + tools::Long nDelta = 0; + // "曰〈道高一尺化太平〉云云" + static constexpr OUStringLiteral aText + = u"\u66f0\u3008\u9053\u9ad8\u4e00\u5c3a\u5316\u592a\u5e73\u3009\u4e91\u4e91"; + CharWidthArray aActual{ 880, 880, 880, 880, 880, 880, 880, 880, 880, 880, 880, 880 }; + CharWidthArray aExpected{ + 1360, 1040, 1200, 1200, 1200, 1200, 1200, 1200, 1040, 1360, 1200, 1040 + }; + aActual.InvokeWithKernArray( + [&] { nDelta = sw::Justify::SnapToGrid(aActual.maArray, aText, 0, 12, 400, false); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); + CPPUNIT_ASSERT_EQUAL(tools::Long(160), nDelta); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGridMixWidth) +{ + // Related to: tdf#149365 + tools::Long nDelta = 0; + // "中中中ケコサシスセソカケコ" ( mixing fullwidth ideograph and half-width kana ) + static constexpr OUStringLiteral aText + = u"\u4e2d\u4e2d\u4e2d\uff79\uff7a\uff7b\uff7c\uff7d\uff7e\uff7f\uff76\uff79\uff7a"; + CharWidthArray aActual{ 640, 640, 640, 320, 320, 320, 320, 320, 320, 320, 320, 320, 320 }; + CharWidthArray aExpected{ 800, 800, 760, 400, 400, 400, 400, 400, 400, 400, 400, 400, 360 }; + aActual.InvokeWithKernArray( + [&] { nDelta = sw::Justify::SnapToGrid(aActual.maArray, aText, 0, 13, 400, false); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); + CPPUNIT_ASSERT_EQUAL(tools::Long(80), nDelta); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGridIVS) +{ + // Related to: tdf#149214 + tools::Long nDelta = 0; + static constexpr OUStringLiteral aText = u"\u9053\u9ad8\u4e00\U000E01E2\u5c3a\u5316"; + + CharWidthArray aActual{ 800, 800, 800, 0, 0, 800, 800 }; + CharWidthArray aExpected{ 800, 800, 800, 0, 0, 800, 800 }; + aActual.InvokeWithKernArray( + [&] { nDelta = sw::Justify::SnapToGrid(aActual.maArray, aText, 0, 7, 400, false); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); + CPPUNIT_ASSERT_EQUAL(tools::Long(0), nDelta); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGridEdge1) +{ + CharWidthArray aActual{ 640, 640, 640, 640, 640, 640, 320, 960 }; + CharWidthArray aExpected{ 840, 840, 840, 840, 840, 840, 440, 1240 }; + aActual.InvokeWithKernArray( + [&] { sw::Justify::SnapToGridEdge(aActual.maArray, 8, 400, 40, 0); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGridEdge2) +{ + CharWidthArray aActual{ 640, 640, 640, 640, 640, 640, 320, 640 }; + CharWidthArray aExpected{ 840, 840, 840, 840, 840, 840, 440, 840 }; + aActual.InvokeWithKernArray( + [&] { sw::Justify::SnapToGridEdge(aActual.maArray, 8, 100, 40, 80); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + +CPPUNIT_TEST_FIXTURE(SwCoreJustifyTest, testSnapToGridEdgeIVS) +{ + CharWidthArray aActual{ 640, 0, 0, 640, 640, 640, 640, 640 }; + CharWidthArray aExpected{ 840, 0, 0, 840, 840, 840, 840, 840 }; + aActual.InvokeWithKernArray( + [&] { sw::Justify::SnapToGridEdge(aActual.maArray, 8, 400, 40, 0); }); + CPPUNIT_ASSERT_EQUAL(aExpected, aActual); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/qa/core/txtnode/txtnode.cxx b/sw/qa/core/txtnode/txtnode.cxx new file mode 100644 index 0000000000..c2df8a407e --- /dev/null +++ b/sw/qa/core/txtnode/txtnode.cxx @@ -0,0 +1,544 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/// Covers sw/source/core/txtnode/ fixes. +class SwCoreTxtnodeTest : public SwModelTestBase +{ +public: + SwCoreTxtnodeTest() + : SwModelTestBase("/sw/qa/core/txtnode/data/") + { + } +}; + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testBtlrCellChinese) +{ + // Load a document with a table cell, with btlr cell direction. The cell has text which is + // classified as vertical, i.e. the glyph has the same direction in both the lrtb ("Latin") and + // tbrl ("Chinese") directions. Make sure that Chinese text is handled the same way in the btlr + // case as it's handled in the Latin case. + createSwDoc("btlr-cell-chinese.doc"); + SwXTextDocument* pTextDoc = dynamic_cast(mxComponent.get()); + SwDocShell* pShell = pTextDoc->GetDocShell(); + std::shared_ptr xMetaFile = pShell->GetPreviewMetaFile(); + MetafileXmlDump dumper; + xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, *xMetaFile); + assertXPath(pXmlDoc, "//font[1]"_ostr, "orientation"_ostr, "900"); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: false + // - Actual : true + // i.e. the glyph was rotated further, so it was upside down. + assertXPath(pXmlDoc, "//font[1]"_ostr, "vertical"_ostr, "false"); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testSpecialInsertAfterMergedCells) +{ + // Load a document with a table with bottom right cells merged vertically. + // SpecialInsert with alt-Enter must work here, too. + createSwDoc("special-insert-after-merged-cells.fodt"); + SwDoc* pDoc = getSwDoc(); + SwNodeOffset const nNodes(pDoc->GetNodes().Count()); + SwXTextDocument* pTextDoc = dynamic_cast(mxComponent.get()); + SwDocShell* pShell = pTextDoc->GetDocShell(); + SwWrtShell* pWrtShell = pShell->GetWrtShell(); + // go to the merged cell + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + + // When pressing alt-Enter on the keyboard: + SwEditWin& rEditWin = pWrtShell->GetView().GetEditWin(); + vcl::KeyCode aKeyCode(KEY_RETURN, KEY_MOD2); + KeyEvent aKeyEvent(' ', aKeyCode); + rEditWin.KeyInput(aKeyEvent); + + // Without the accompanying fix in place, this test would have failed with: + // - Expected: nNodes + 1 + // - Actual : nNodes + // i.e. new empty paragraph wasn't inserted under the table + CPPUNIT_ASSERT_EQUAL(nNodes + 1, pDoc->GetNodes().Count()); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testTextBoxCopyAnchor) +{ + createSwDoc("textbox-copy-anchor.docx"); + SwXTextDocument* pTextDoc = dynamic_cast(mxComponent.get()); + SwDocShell* pShell = pTextDoc->GetDocShell(); + SwWrtShell* pWrtShell = pShell->GetWrtShell(); + SwDoc aClipboard; + pWrtShell->SelAll(); + pWrtShell->Copy(aClipboard); + pWrtShell->SttEndDoc(/*bStart=*/false); + pWrtShell->Paste(aClipboard); + + const auto& rFormats = *pShell->GetDoc()->GetSpzFrameFormats(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 4 + // - Actual : 6 + // i.e. 2 fly frames were copied twice. + CPPUNIT_ASSERT_EQUAL(static_cast(4), rFormats.size()); + + SwPosition aDrawAnchor1 = *rFormats[0]->GetAnchor().GetContentAnchor(); + SwPosition aFlyAnchor1 = *rFormats[1]->GetAnchor().GetContentAnchor(); + CPPUNIT_ASSERT_EQUAL(aFlyAnchor1.GetNodeIndex(), aDrawAnchor1.GetNodeIndex()); + SwPosition aDrawAnchor2 = *rFormats[2]->GetAnchor().GetContentAnchor(); + SwPosition aFlyAnchor2 = *rFormats[3]->GetAnchor().GetContentAnchor(); + // This also failed, aFlyAnchor2 was wrong, as it got out of sync with aDrawAnchor2. + CPPUNIT_ASSERT_EQUAL(aFlyAnchor2.GetNodeIndex(), aDrawAnchor2.GetNodeIndex()); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testTextBoxNodeSplit) +{ + createSwDoc("textbox-node-split.docx"); + SwXTextDocument* pTextDoc = dynamic_cast(mxComponent.get()); + SwDocShell* pShell = pTextDoc->GetDocShell(); + SwWrtShell* pWrtShell = pShell->GetWrtShell(); + pWrtShell->SttEndDoc(/*bStart=*/false); + // Without the accompanying fix in place, this would have crashed in + // SwFlyAtContentFrame::SwClientNotify(). + pWrtShell->SplitNode(); +} + +namespace +{ +struct ViewCallback +{ + int m_nInvalidations = 0; + + static void callback(int nType, const char* pPayload, void* pData); + void callbackImpl(int nType, const char* pPayload); +}; + +void ViewCallback::callback(int nType, const char* pPayload, void* pData) +{ + static_cast(pData)->callbackImpl(nType, pPayload); +} + +void ViewCallback::callbackImpl(int nType, const char* /*pPayload*/) +{ + switch (nType) + { + case LOK_CALLBACK_INVALIDATE_TILES: + { + ++m_nInvalidations; + } + break; + } +} +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testTitleFieldInvalidate) +{ + // Set up LOK to track invalidations. + comphelper::LibreOfficeKit::setActive(true); + + // Given a document with a title field: + createSwDoc("title-field-invalidate.fodt"); + SwXTextDocument* pTextDoc = dynamic_cast(mxComponent.get()); + pTextDoc->initializeForTiledRendering({}); + SwDocShell* pShell = pTextDoc->GetDocShell(); + SwDoc* pDoc = pShell->GetDoc(); + SwWrtShell* pWrtShell = pShell->GetWrtShell(); + pWrtShell->SttEndDoc(/*bStt=*/false); + ViewCallback aCallback; + TestLokCallbackWrapper aCallbackWrapper(&ViewCallback::callback, &aCallback); + pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&aCallbackWrapper); + aCallbackWrapper.setLOKViewId(SfxLokHelper::getView(pWrtShell->GetSfxViewShell())); + Scheduler::ProcessEventsToIdle(); + aCallback.m_nInvalidations = 0; + + // When typing to the document: + pWrtShell->Insert("x"); + pWrtShell->GetSfxViewShell()->flushPendingLOKInvalidateTiles(); + + // Then make sure that only the text frame at the cursor is invalidated: + pDoc->getIDocumentStatistics().GetUpdatedDocStat(/*bCompleteAsync=*/true, /*bFields=*/false); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 1 + // - Actual : 2 + // i.e. the footer was also invalidated on each keypress. + CPPUNIT_ASSERT_EQUAL(1, aCallback.m_nInvalidations); + + // Tear down LOK. + pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(nullptr); + mxComponent->dispose(); + mxComponent.clear(); + comphelper::LibreOfficeKit::setActive(false); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testFlyAnchorUndo) +{ + // Given a document with a fly frame, anchored after the last char of the document: + createSwDoc("fly-anchor-undo.odt"); + SwXTextDocument* pTextDoc = dynamic_cast(mxComponent.get()); + SwDocShell* pShell = pTextDoc->GetDocShell(); + SwDoc* pDoc = pShell->GetDoc(); + const auto& rSpz = *pDoc->GetSpzFrameFormats(); + sal_Int32 nExpected = rSpz[0]->GetAnchor().GetAnchorContentOffset(); + + // When deleting that last character and undoing it: + SwWrtShell* pWrtShell = pShell->GetWrtShell(); + pWrtShell->SttEndDoc(/*bStt=*/false); + pWrtShell->DelLeft(); + pWrtShell->Undo(); + + // Then make sure the anchor position after the undo is the same as the original: + sal_Int32 nActual = rSpz[0]->GetAnchor().GetAnchorContentOffset(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 3 + // - Actual : 2 + // i.e. the anchor position was left unchanged by the undo. + CPPUNIT_ASSERT_EQUAL(nExpected, nActual); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testSplitNodeSuperscriptCopy) +{ + // Given a document with superscript text at the end of a paragraph: + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->Insert("1st"); + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 2, /*bBasicCall=*/false); + SfxItemSetFixed aSet(pWrtShell->GetAttrPool()); + SvxEscapementItem aItem(SvxEscapement::Superscript, RES_CHRATR_ESCAPEMENT); + aSet.Put(aItem); + pWrtShell->SetAttrSet(aSet); + + // When hitting enter at the end of the paragraph: + pWrtShell->SttEndDoc(/*bStt=*/false); + pWrtShell->SplitNode(/*bAutoFormat=*/true); + + // Then make sure that the superscript formatting doesn't appear on the next paragraph: + aSet.ClearItem(RES_CHRATR_ESCAPEMENT); + pWrtShell->GetCurAttr(aSet); + // Without the accompanying fix in place, this test would have failed, the unexpected + // superscript appeared in the next paragraph. + CPPUNIT_ASSERT(!aSet.HasItem(RES_CHRATR_ESCAPEMENT)); +} + +/* FIXME: behavior change reverted due to regression; + * see sw/source/core/txtnode/atrref.cxx + *CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testDontExpandRefmark) + *{ + * // Given a document with a refmark: + * createSwDoc(); + * + * uno::Sequence aArgs = { + * comphelper::makePropertyValue("TypeName", uno::Any(OUString("SetRef"))), + * comphelper::makePropertyValue( + * "Name", uno::Any(OUString("ZOTERO_ITEM CSL_CITATION {} RNDpyJknp173F"))), + * comphelper::makePropertyValue("Content", uno::Any(OUString("foo"))), + * }; + * dispatchCommand(mxComponent, ".uno:InsertField", aArgs); + * + * SwDoc* pDoc = getSwDoc(); + * SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + * SwPosition& rCursor = *pWrtShell->GetCursor()->GetPoint(); + * SwTextNode* pTextNode = rCursor.GetNode().GetTextNode(); + * std::vector aAttrs + * = pTextNode->GetTextAttrsAt(rCursor.GetContentIndex(), RES_TXTATR_REFMARK); + * + * auto& rRefmark = const_cast(aAttrs[0]->GetRefMark()); + * auto pTextRefMark = const_cast(rRefmark.GetTextRefMark()); + * + * // When typing after the refmark... + * pWrtShell->SttEndDoc(true); + * pWrtShell->Right(SwCursorSkipMode::Chars, false, 3, false); + * pWrtShell->Insert(" bar"); + * + * // and skipping back to insert a comma after the refmark + * pWrtShell->Left(SwCursorSkipMode::Chars, false, 4, false); + * pWrtShell->Insert(","); + * + * // Without the accompanying fix in place, this test would have failed with: + * // - Expected: 3 + * // - Actual : 4 + * // i.e. the reference mark expanded + * CPPUNIT_ASSERT_EQUAL(3, static_cast(*pTextRefMark->End())); + *} + */ + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testInsertDropDownContentControlTwice) +{ + // Given an already selected dropdown content control: + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->InsertContentControl(SwContentControlType::DROP_DOWN_LIST); + + // When trying to insert an inner one, make sure that we don't crash: + pWrtShell->InsertContentControl(SwContentControlType::DROP_DOWN_LIST); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testCheckboxContentControlKeyboard) +{ + // Given an already selected checkbox content control: + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->InsertContentControl(SwContentControlType::CHECKBOX); + SwEditWin& rEditWin = pWrtShell->GetView().GetEditWin(); + + // When pressing space on the keyboard: + KeyEvent aKeyEvent(' ', KEY_SPACE); + rEditWin.KeyInput(aKeyEvent); + + // Then make sure the state is toggled: + SwTextNode* pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); + SwTextAttr* pAttr = pTextNode->GetTextAttrForCharAt(0, RES_TXTATR_CONTENTCONTROL); + auto pTextContentControl = static_txtattr_cast(pAttr); + auto& rFormatContentControl + = static_cast(pTextContentControl->GetAttr()); + std::shared_ptr pContentControl = rFormatContentControl.GetContentControl(); + // Without the accompanying fix in place, this test would have failed, because the state + // remained unchanged. + CPPUNIT_ASSERT(pContentControl->GetChecked()); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testDropdownContentControlKeyboard) +{ + // Given an already selected dropdown content control: + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->InsertContentControl(SwContentControlType::DROP_DOWN_LIST); + + // When checking if alt-down should open a popup: + SwTextContentControl* pTextContentControl = pWrtShell->CursorInsideContentControl(); + auto& rFormatContentControl + = static_cast(pTextContentControl->GetAttr()); + std::shared_ptr pContentControl = rFormatContentControl.GetContentControl(); + vcl::KeyCode aKeyCode(KEY_DOWN, KEY_MOD2); + bool bShouldOpen = pContentControl->ShouldOpenPopup(aKeyCode); + + // Then make sure that the answer is yes for dropdowns: + // Without the accompanying fix in place, this test would have failed, the dropdown popup was + // mouse-only. + CPPUNIT_ASSERT(bShouldOpen); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testPictureContentControlKeyboard) +{ + // Given an already selected picture content control: + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->InsertContentControl(SwContentControlType::PICTURE); + pWrtShell->GotoObj(/*bNext=*/true, GotoObjFlags::Any); + + // When checking if enter should trigger the file picker: + const SwFrameFormat* pFlyFormat = pWrtShell->GetFlyFrameFormat(); + const SwFormatAnchor& rFormatAnchor = pFlyFormat->GetAnchor(); + SwNode* pAnchorNode = rFormatAnchor.GetAnchorNode(); + SwTextNode* pTextNode = pAnchorNode->GetTextNode(); + SwTextAttr* pAttr + = pTextNode->GetTextAttrAt(rFormatAnchor.GetAnchorContentOffset(), + RES_TXTATR_CONTENTCONTROL, ::sw::GetTextAttrMode::Parent); + auto pTextContentControl = static_txtattr_cast(pAttr); + auto& rFormatContentControl + = static_cast(pTextContentControl->GetAttr()); + std::shared_ptr pContentControl = rFormatContentControl.GetContentControl(); + bool bIsInteracting = pContentControl->IsInteractingCharacter('\r'); + + // Then make sure that the answer is yes for pictures: + // Without the accompanying fix in place, this test would have failed, the picture replacement + // file-picker was mouse-only. + CPPUNIT_ASSERT(bIsInteracting); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testDateContentControlKeyboard) +{ + // Given an already selected date content control: + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->InsertContentControl(SwContentControlType::DATE); + + // When checking if alt-down should open a popup: + SwTextContentControl* pTextContentControl = pWrtShell->CursorInsideContentControl(); + auto& rFormatContentControl + = static_cast(pTextContentControl->GetAttr()); + std::shared_ptr pContentControl = rFormatContentControl.GetContentControl(); + vcl::KeyCode aKeyCode(KEY_DOWN, KEY_MOD2); + bool bShouldOpen = pContentControl->ShouldOpenPopup(aKeyCode); + + // Then make sure that the answer is yes for date: + // Without the accompanying fix in place, this test would have failed, the date popup was + // mouse-only. + CPPUNIT_ASSERT(bShouldOpen); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testContentControlCopy) +{ + // Given a document with a content control: + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->InsertContentControl(SwContentControlType::CHECKBOX); + + // When copying that content control: + pWrtShell->SelAll(); + rtl::Reference xTransfer = new SwTransferable(*pWrtShell); + xTransfer->Copy(); + // Kill the selection, go to the end of the document: + pWrtShell->EndOfSection(); + TransferableDataHelper aHelper(xTransfer); + SwTransferable::Paste(*pWrtShell, aHelper); + + // Then make sure that the copy is also a checkbox: + SwContentControlManager& rManager = pDoc->GetContentControlManager(); + CPPUNIT_ASSERT_EQUAL(static_cast(2), rManager.GetCount()); + const SwFormatContentControl& rFormat1 = rManager.Get(0)->GetContentControl(); + CPPUNIT_ASSERT_EQUAL(SwContentControlType::CHECKBOX, rFormat1.GetContentControl()->GetType()); + const SwFormatContentControl& rFormat2 = rManager.Get(1)->GetContentControl(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 1 (CHECKBOX) + // - Actual : 0 (RICH_TEXT) + // i.e. properties were not copied from the source to the destination content control. + CPPUNIT_ASSERT_EQUAL(SwContentControlType::CHECKBOX, rFormat2.GetContentControl()->GetType()); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testTdf157287) +{ + createSwDoc("tdf157287.odt"); + uno::Reference xTextFieldsSupplier(mxComponent, uno::UNO_QUERY); + auto xFieldsAccess(xTextFieldsSupplier->getTextFields()); + uno::Reference xFields(xFieldsAccess->createEnumeration()); + uno::Reference xField(xFields->nextElement(), uno::UNO_QUERY); + + CPPUNIT_ASSERT_EQUAL(OUString("30"), xField->getPresentation(false)); + + uno::Reference xTextTablesSupplier(mxComponent, uno::UNO_QUERY); + uno::Reference xIndexAccess(xTextTablesSupplier->getTextTables(), + uno::UNO_QUERY); + uno::Reference xTextTable(xIndexAccess->getByIndex(0), uno::UNO_QUERY); + + uno::Reference xCellA1(xTextTable->getCellByName("B1"), uno::UNO_QUERY); + xCellA1->setString("100"); + + dispatchCommand(mxComponent, ".uno:UpdateFields", {}); + + // Without the fix in place, this test would have failed with + // - Expected: 120 + // - Actual : + CPPUNIT_ASSERT_EQUAL(OUString("120"), xField->getPresentation(false)); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testFlySplitFootnote) +{ + // Given a document with a split fly (to host a table): + createSwDoc(); + SwDoc* pDoc = getSwDoc(); + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + SwFlyFrameAttrMgr aMgr(true, pWrtShell, Frmmgr_Type::TEXT, nullptr); + RndStdIds eAnchor = RndStdIds::FLY_AT_PARA; + pWrtShell->StartAllAction(); + aMgr.InsertFlyFrame(eAnchor, aMgr.GetPos(), aMgr.GetSize()); + pWrtShell->EndAllAction(); + pWrtShell->StartAllAction(); + sw::FrameFormats& rFlys = *pDoc->GetSpzFrameFormats(); + sw::SpzFrameFormat* pFly = rFlys[0]; + SwAttrSet aSet(pFly->GetAttrSet()); + aSet.Put(SwFormatFlySplit(true)); + pDoc->SetAttr(aSet, *pFly); + pWrtShell->EndAllAction(); + pWrtShell->UnSelectFrame(); + pWrtShell->LeaveSelFrameMode(); + pWrtShell->GetView().AttrChangedNotify(nullptr); + pWrtShell->MoveSection(GoCurrSection, fnSectionEnd); + + // When inserting a footnote: + pWrtShell->InsertFootnote(OUString()); + + // Then make sure the footnote gets inserted to the doc model. + // Without the accompanying fix in place, this test would have failed, insert code refused to + // have footnotes in all fly frames. + CPPUNIT_ASSERT(!pDoc->GetFootnoteIdxs().empty()); +} + +CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testSplitFlyAnchorSplit) +{ + // Given a document with a 2 pages long floating table: + createSwDoc("floattable-anchor-split.docx"); + + // When splitting the "AB" anchor text into "A" (remains as anchor text) and "B" (new text node + // after it): + SwWrtShell* pWrtShell = getSwDocShell()->GetWrtShell(); + pWrtShell->SttEndDoc(/*bStt=*/false); + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + // Without the accompanying fix in place, this test would have failed with a layout loop. + pWrtShell->SplitNode(); + + // Then make sure the resulting layout is what we want: + SwDoc* pDoc = getSwDoc(); + SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout(); + auto pPage1 = pLayout->Lower()->DynCastPageFrame(); + CPPUNIT_ASSERT(pPage1); + // Page 1 has the master fly: + CPPUNIT_ASSERT(pPage1->GetSortedObjs()); + auto pPage2 = pPage1->GetNext()->DynCastPageFrame(); + CPPUNIT_ASSERT(pPage2); + // Page 2 has the follow fly: + CPPUNIT_ASSERT(pPage2->GetSortedObjs()); + // Anchor text is now just "A": + auto pText1 = pPage2->FindFirstBodyContent()->DynCastTextFrame(); + CPPUNIT_ASSERT_EQUAL(OUString("A"), pText1->GetText()); + // New text frame is just "B": + auto pText2 = pText1->GetNext()->DynCastTextFrame(); + CPPUNIT_ASSERT_EQUAL(OUString("B"), pText2->GetText()); + + // Also test that the new follow anchor text frame still has a fly portion, otherwise the anchor + // text and the floating table would overlap: + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + OUString aPortionType + = getXPath(pXmlDoc, "//page[2]/body/txt[1]/SwParaPortion/SwLineLayout[1]/child::*[1]"_ostr, + "type"_ostr); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: PortionType::Fly + // - Actual : PortionType::Para + // i.e. the fly portion was missing, text overlapped. + CPPUNIT_ASSERT_EQUAL(OUString("PortionType::Fly"), aPortionType); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3