android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / qa / core / text / text.cxx
blobc68a0fa99cbe95455246a1a63d360b8d629427ef
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <swmodeltestbase.hxx>
12 #include <memory>
14 #include <com/sun/star/text/BibliographyDataField.hpp>
15 #include <com/sun/star/text/BibliographyDataType.hpp>
16 #include <com/sun/star/text/ControlCharacter.hpp>
17 #include <com/sun/star/text/WritingMode2.hpp>
18 #include <com/sun/star/text/XDocumentIndex.hpp>
20 #include <vcl/gdimtf.hxx>
21 #include <vcl/metaact.hxx>
22 #include <vcl/filter/PDFiumLibrary.hxx>
23 #include <comphelper/propertyvalue.hxx>
24 #include <editeng/fhgtitem.hxx>
25 #include <editeng/wghtitem.hxx>
27 #include <docsh.hxx>
28 #include <unotxdoc.hxx>
29 #include <wrtsh.hxx>
30 #include <IDocumentLayoutAccess.hxx>
31 #include <rootfrm.hxx>
32 #include <txtfrm.hxx>
33 #include <porlay.hxx>
34 #include <pormulti.hxx>
35 #include <formatlinebreak.hxx>
36 #include <sortedobjs.hxx>
37 #include <anchoredobject.hxx>
38 #include <fmtcntnt.hxx>
39 #include <fmtfsize.hxx>
40 #include <IDocumentRedlineAccess.hxx>
41 #include <formatcontentcontrol.hxx>
42 #include <strings.hrc>
43 #include <ndtxt.hxx>
44 #include <txatbase.hxx>
45 #include <textcontentcontrol.hxx>
46 #include <pagefrm.hxx>
48 /// Covers sw/source/core/text/ fixes.
49 class SwCoreTextTest : public SwModelTestBase
51 public:
52 SwCoreTextTest()
53 : SwModelTestBase("/sw/qa/core/text/data/")
58 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testFootnoteConnect)
60 createSwDoc("footnote-connect.fodt");
61 SwDoc* pDoc = getSwDoc();
62 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
63 // Jump to the start of the next page.
64 pWrtShell->SttNxtPg();
65 // Remove the page break.
66 pWrtShell->DelLeft();
67 // Split the multi-line text frame, containing an endnote.
68 pWrtShell->DelLeft();
69 // Join the split text frame.
70 pWrtShell->DelLeft();
71 // Turn the 3 page document into a 2 page one, so the endnote frame is moved.
72 // Without the accompanying fix in place, this test would have crashed due to a use-after-free
73 // in SwFootnoteFrame::GetRef().
74 pWrtShell->DelLeft();
77 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testSemiTransparentText)
79 // Create an in-memory empty document.
80 createSwDoc();
82 // Set text to half-transparent and type a character.
83 uno::Reference<beans::XPropertySet> xParagraph(getParagraph(1), uno::UNO_QUERY);
84 CPPUNIT_ASSERT(xParagraph.is());
85 sal_Int16 nTransparence = 50;
86 xParagraph->setPropertyValue("CharTransparence", uno::Any(nTransparence));
87 uno::Reference<text::XTextRange> xTextRange(xParagraph, uno::UNO_QUERY);
88 CPPUNIT_ASSERT(xTextRange.is());
89 xTextRange->setString("x");
91 // Render the document to a metafile.
92 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
93 CPPUNIT_ASSERT(pTextDoc);
94 SwDocShell* pDocShell = pTextDoc->GetDocShell();
95 CPPUNIT_ASSERT(pDocShell);
96 std::shared_ptr<GDIMetaFile> xMetaFile = pDocShell->GetPreviewMetaFile();
97 CPPUNIT_ASSERT(xMetaFile);
99 // Make sure that DrawTransparent() was used during rendering.
100 MetafileXmlDump dumper;
101 xmlDocUniquePtr pXmlDoc = dumpAndParse(dumper, *xMetaFile);
102 CPPUNIT_ASSERT(pXmlDoc);
103 assertXPath(pXmlDoc, "//floattransparent");
106 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testBibliographyUrlPdfExport)
108 // Given a document with a bibliography entry field:
109 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
110 if (!pPDFium)
112 return;
114 createSwDoc();
115 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
116 uno::Reference<beans::XPropertySet> xField(
117 xFactory->createInstance("com.sun.star.text.TextField.Bibliography"), uno::UNO_QUERY);
118 uno::Sequence<beans::PropertyValue> aFields = {
119 comphelper::makePropertyValue("BibiliographicType", text::BibliographyDataType::WWW),
120 comphelper::makePropertyValue("Identifier", OUString("AT")),
121 comphelper::makePropertyValue("Author", OUString("Author")),
122 comphelper::makePropertyValue("Title", OUString("Title")),
123 comphelper::makePropertyValue("URL", OUString("http://www.example.com/test.pdf#page=1")),
125 xField->setPropertyValue("Fields", uno::Any(aFields));
126 uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
127 uno::Reference<text::XText> xText = xTextDocument->getText();
128 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
129 uno::Reference<text::XTextContent> xContent(xField, uno::UNO_QUERY);
130 xText->insertTextContent(xCursor, xContent, /*bAbsorb=*/false);
132 // When exporting to PDF:
133 save("writer_pdf_Export");
135 // Then make sure the field links the source.
136 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
137 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
138 // Without the accompanying fix in place, this test would have failed, the field was not
139 // clickable (while it was clickable on the UI).
140 CPPUNIT_ASSERT(pPdfPage->hasLinks());
143 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testBibliographyUrlPdfExport2)
145 // Given a document with a bibliography entry field:
146 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
147 if (!pPDFium)
149 return;
151 createSwDoc();
152 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
153 uno::Reference<beans::XPropertySet> xField(
154 xFactory->createInstance("com.sun.star.text.TextField.Bibliography"), uno::UNO_QUERY);
155 uno::Sequence<beans::PropertyValue> aFields = {
156 comphelper::makePropertyValue("BibiliographicType", text::BibliographyDataType::WWW),
157 comphelper::makePropertyValue("Identifier", OUString("AT")),
158 comphelper::makePropertyValue("Author", OUString("Author")),
159 comphelper::makePropertyValue("Title", OUString("Title")),
160 comphelper::makePropertyValue("URL", OUString("#page=1")),
162 xField->setPropertyValue("Fields", uno::Any(aFields));
163 uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
164 uno::Reference<text::XText> xText = xTextDocument->getText();
165 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
166 uno::Reference<text::XTextContent> xContent(xField, uno::UNO_QUERY);
167 xText->insertTextContent(xCursor, xContent, /*bAbsorb=*/false);
169 // When exporting to PDF:
170 save("writer_pdf_Export");
172 // Then make sure the field links when the Target URL is set
173 // (this test is important, isn't the same as the one above)
174 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
175 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
176 CPPUNIT_ASSERT(pPdfPage->hasLinks());
179 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testBibliographyUrlPdfExport3)
181 // Given a document with a bibliography entry field:
182 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
183 if (!pPDFium)
185 return;
187 createSwDoc();
188 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
189 uno::Reference<beans::XPropertySet> xField(
190 xFactory->createInstance("com.sun.star.text.TextField.Bibliography"), uno::UNO_QUERY);
191 uno::Sequence<beans::PropertyValue> aFields = {
192 comphelper::makePropertyValue("BibiliographicType", text::BibliographyDataType::WWW),
193 comphelper::makePropertyValue("Identifier", OUString("AT")),
194 comphelper::makePropertyValue("Author", OUString("Author")),
195 comphelper::makePropertyValue("Title", OUString("Title")),
196 comphelper::makePropertyValue("TargetURL", OUString("#page=1")),
198 xField->setPropertyValue("Fields", uno::Any(aFields));
199 uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
200 uno::Reference<text::XText> xText = xTextDocument->getText();
201 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
202 uno::Reference<text::XTextContent> xContent(xField, uno::UNO_QUERY);
203 xText->insertTextContent(xCursor, xContent, /*bAbsorb=*/false);
205 // When exporting to PDF:
206 save("writer_pdf_Export");
208 // Then make sure there are no links since UseTargetURL is not set
209 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
210 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
211 CPPUNIT_ASSERT(!pPdfPage->hasLinks());
214 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testBibliographyUrlPdfExport4)
216 // Given a document with a bibliography entry field:
217 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
218 if (!pPDFium)
220 return;
222 createSwDoc();
223 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
224 uno::Reference<beans::XPropertySet> xField(
225 xFactory->createInstance("com.sun.star.text.TextField.Bibliography"), uno::UNO_QUERY);
226 uno::Sequence<beans::PropertyValue> aFields = {
227 comphelper::makePropertyValue("BibiliographicType", text::BibliographyDataType::WWW),
228 comphelper::makePropertyValue("Identifier", OUString("AT")),
229 comphelper::makePropertyValue("Author", OUString("Author")),
230 comphelper::makePropertyValue("Title", OUString("Title")),
231 comphelper::makePropertyValue("TargetType", OUString("1")), // 1 == UseTargetURL
232 comphelper::makePropertyValue("TargetURL", OUString("#page=1")),
234 xField->setPropertyValue("Fields", uno::Any(aFields));
235 uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
236 uno::Reference<text::XText> xText = xTextDocument->getText();
237 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
238 uno::Reference<text::XTextContent> xContent(xField, uno::UNO_QUERY);
239 xText->insertTextContent(xCursor, xContent, /*bAbsorb=*/false);
241 // When exporting to PDF:
242 save("writer_pdf_Export");
244 // Then make sure the field links when the Target URL is set
245 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
246 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
247 CPPUNIT_ASSERT(pPdfPage->hasLinks());
250 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testBibliographyUrlPdfExport5)
252 // Given a document with a bibliography entry field:
253 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
254 if (!pPDFium)
256 return;
258 createSwDoc();
259 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
260 uno::Reference<beans::XPropertySet> xField(
261 xFactory->createInstance("com.sun.star.text.TextField.Bibliography"), uno::UNO_QUERY);
262 uno::Sequence<beans::PropertyValue> aFields = {
263 comphelper::makePropertyValue("BibiliographicType", text::BibliographyDataType::WWW),
264 comphelper::makePropertyValue("Identifier", OUString("AT")),
265 comphelper::makePropertyValue("Author", OUString("Author")),
266 comphelper::makePropertyValue("Title", OUString("Title")),
267 comphelper::makePropertyValue("TargetType", OUString("3")), // 3 == BibliographyTableRow
269 xField->setPropertyValue("Fields", uno::Any(aFields));
270 uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
271 uno::Reference<text::XText> xText = xTextDocument->getText();
272 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
273 uno::Reference<text::XTextContent> xContent(xField, uno::UNO_QUERY);
274 xText->insertTextContent(xCursor, xContent, /*bAbsorb=*/false);
275 // Create a bibliography table.
276 uno::Reference<text::XTextContent> xTable(
277 xFactory->createInstance("com.sun.star.text.Bibliography"), uno::UNO_QUERY);
278 xCursor->gotoEnd(/*bExpand=*/false);
279 xText->insertControlCharacter(xCursor, text::ControlCharacter::APPEND_PARAGRAPH,
280 /*bAbsorb=*/false);
281 xText->insertTextContent(xCursor, xTable, /*bAbsorb=*/false);
282 // Update the table
283 uno::Reference<text::XDocumentIndex> xTableIndex(xTable, uno::UNO_QUERY);
284 xTableIndex->update();
286 // When exporting to PDF:
287 save("writer_pdf_Export");
289 // Then make sure the mark links to the table when table is present
290 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
291 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
292 CPPUNIT_ASSERT(pPdfPage->hasLinks());
295 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testBibliographyUrlPdfExport6)
297 // Given a document with a bibliography entry field:
298 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
299 if (!pPDFium)
301 return;
303 createSwDoc();
304 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
306 // Create a bibliography mark
307 uno::Reference<beans::XPropertySet> xField(
308 xFactory->createInstance("com.sun.star.text.TextField.Bibliography"), uno::UNO_QUERY);
309 uno::Sequence<beans::PropertyValue> aFields = {
310 comphelper::makePropertyValue("BibiliographicType", text::BibliographyDataType::WWW),
311 comphelper::makePropertyValue("Identifier", OUString("AT")),
312 comphelper::makePropertyValue("Author", OUString("AuthorName")),
313 comphelper::makePropertyValue("Title", OUString("Title")),
314 comphelper::makePropertyValue("TargetType", OUString("3")), // 3 == BibliographyTableRow
316 xField->setPropertyValue("Fields", uno::Any(aFields));
317 uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
318 uno::Reference<text::XText> xText = xTextDocument->getText();
319 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
320 uno::Reference<text::XTextContent> xContent(xField, uno::UNO_QUERY);
321 xText->insertTextContent(xCursor, xContent, /*bAbsorb=*/false);
323 // Create a bibliography table.
324 uno::Reference<text::XTextContent> xTable(
325 xFactory->createInstance("com.sun.star.text.Bibliography"), uno::UNO_QUERY);
326 xCursor->gotoEnd(/*bExpand=*/false);
327 xText->insertControlCharacter(xCursor, text::ControlCharacter::APPEND_PARAGRAPH,
328 /*bAbsorb=*/false);
329 xText->insertTextContent(xCursor, xTable, /*bAbsorb=*/false);
331 // Set formatting for AUTH_TYPE_WWW to include tab stop
332 uno::Reference<beans::XPropertySet> xTableAsPropertySet(xTable, uno::UNO_QUERY_THROW);
333 uno::Reference<container::XIndexReplace> aAllPatterns(
334 xTableAsPropertySet->getPropertyValue("LevelFormat"), uno::UNO_QUERY);
336 uno::Sequence<uno::Sequence<beans::PropertyValue>> aFormattingPattern
337 = { {
338 comphelper::makePropertyValue("TokenType", OUString("TokenBibliographyDataField")),
339 comphelper::makePropertyValue("BibliographyDataField",
340 text::BibliographyDataField::AUTHOR),
341 comphelper::makePropertyValue("CharacterStyleName", OUString("")),
344 comphelper::makePropertyValue("TokenType", OUString("TokenTabStop")),
345 comphelper::makePropertyValue("TabStopRightAligned", true),
346 comphelper::makePropertyValue("CharacterStyleName", OUString("")),
349 comphelper::makePropertyValue("TokenType", OUString("TokenBibliographyDataField")),
350 comphelper::makePropertyValue("BibliographyDataField",
351 text::BibliographyDataField::TITLE),
352 comphelper::makePropertyValue("CharacterStyleName", OUString("")),
355 comphelper::makePropertyValue("TokenType", OUString("TokenTabStop")),
356 comphelper::makePropertyValue("TabStopRightAligned", false),
357 comphelper::makePropertyValue("TabStopFillCharacter", OUString(".")),
358 comphelper::makePropertyValue("CharacterStyleName", OUString("")),
361 comphelper::makePropertyValue("TokenType", OUString("TokenText")),
362 comphelper::makePropertyValue("Text", OUString("FixedText")),
363 comphelper::makePropertyValue("CharacterStyleName", OUString("")),
364 } };
366 aAllPatterns->replaceByIndex(AUTH_TYPE_WWW + 1, uno::Any(aFormattingPattern));
367 xTableAsPropertySet->setPropertyValue("LevelFormat", uno::Any(aAllPatterns));
369 // Update the table
370 uno::Reference<text::XDocumentIndex> xTableIndex(xTable, uno::UNO_QUERY);
371 xTableIndex->update();
373 // Assert the table updated correctly
374 OUString sExpectedPattern("AuthorName\tTitle\tFixedText");
375 OUString sDocumentText = xTextDocument->getText()->getString();
376 sal_Int32 iTabPos = sDocumentText.indexOf(sExpectedPattern);
377 CPPUNIT_ASSERT(iTabPos >= 0);
378 CPPUNIT_ASSERT_EQUAL(iTabPos, sDocumentText.lastIndexOf(sExpectedPattern));
380 // When exporting to PDF:
381 save("writer_pdf_Export");
383 // Then make sure the mark links to the table even when format contains tab stop
384 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
385 std::unique_ptr<vcl::pdf::PDFiumPage> pPdfPage = pPdfDocument->openPage(/*nIndex=*/0);
386 CPPUNIT_ASSERT(pPdfPage->hasLinks());
389 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testTabOverMarginSection)
391 createSwDoc("tabovermargin-section.fodt");
392 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
393 sal_Int32 nWidth
394 = getXPath(pXmlDoc, "//SwParaPortion/SwLineLayout/child::*[@type='PortionType::TabRight']",
395 "width")
396 .toInt32();
397 // Without the accompanying fix in place, this test would have failed with:
398 // - Expected less than: 5000
399 // - Actual : 9372
400 // i.e. the tab portion width was not the expected 4386, but much larger, so the number after
401 // the tab portion was not visible.
402 CPPUNIT_ASSERT_LESS(static_cast<sal_Int32>(5000), nWidth);
405 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testLineHeight)
407 // Given a document with an as-char image, height in twips not fitting into sal_uInt16:
408 createSwDoc("line-height.fodt");
410 // When laying out that document:
411 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
413 // Then make sure its top is the top of the page:
414 // Without the accompanying fix in place, this test would have failed with:
415 // - Expected: 284
416 // - Actual : -65252
417 // due to various unsigned integer truncations.
418 assertXPath(pXmlDoc, "//fly/infos/bounds", "top", OUString::number(DOCUMENTBORDER));
421 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testLineWidth)
423 // Given a document with an as-char image, width in twips not fitting into sal_uInt16:
424 createSwDoc("line-width.fodt");
425 SwDoc* pDoc = getSwDoc();
426 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
427 sal_Int32 nOldLeft = pWrtShell->GetCharRect().Left();
429 // When moving the cursor to the right:
430 pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
432 // Then make sure we move to the right by the image width:
433 sal_Int32 nNewLeft = pWrtShell->GetCharRect().Left();
434 // Without the accompanying fix in place, this test would have failed with:
435 // - Expected greater than: 65536
436 // - Actual : 1872
437 // i.e. the width (around 67408 twips) was truncated.
438 CPPUNIT_ASSERT_GREATER(static_cast<sal_Int32>(65536), nNewLeft - nOldLeft);
441 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testChineseAutoFirstLineIndent)
443 // The test document contains two simple multi-line paragraph. For both paragraphs, the first line indent
444 // is set to 'auto'. Line spacing is 100% for the 1st paragraph and 200% for the 2nd paragraph.
445 // Also, there is a "AutoFirstLineIndentDisregardLineSpace" capability flag set in the document.
446 createSwDoc("firstLineIndent-withFlag.fodt");
448 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
450 // Get the line width of the first line for the 1st paragraph.
451 sal_Int32 nFirstLineWidth
452 = getXPath(pXmlDoc, "//body/txt[1]/SwParaPortion/SwLineLayout[1]", "width").toInt32();
453 // Get the line width of the first line for the 2nd paragraph.
454 sal_Int32 nSecondLineWidth
455 = getXPath(pXmlDoc, "//body/txt[2]/SwParaPortion/SwLineLayout[1]", "width").toInt32();
457 // Tdf#129448: the changing of line-height should not affect the auto first line indent.
458 // As a result, the first line width of the two paragraphs should be the same.
459 CPPUNIT_ASSERT_EQUAL(nSecondLineWidth, nFirstLineWidth);
462 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testRuby)
464 // Given a document with multiple ruby portions:
465 createSwDoc("ruby.fodt");
466 SwDoc* pDoc = getSwDoc();
468 // When laying out that document:
469 SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
471 // Then make sure that no unwanted margin portions are created, making the actual text
472 // invisible:
473 SwFrame* pPageFrame = pLayout->GetLower();
474 SwFrame* pBodyFrame = pPageFrame->GetLower();
475 SwFrame* pFrame = pBodyFrame->GetLower();
476 CPPUNIT_ASSERT(pFrame->IsTextFrame());
477 auto pTextFrame = static_cast<SwTextFrame*>(pFrame);
478 SwParaPortion* pPara = pTextFrame->GetPara();
479 bool bFirst = true;
480 for (SwLinePortion* pPor = pPara->GetFirstPortion(); pPor; pPor = pPor->GetNextPortion())
482 // Look for multi-portions in the only paragraph of the document.
483 if (pPor->GetWhichPor() != PortionType::Multi)
485 continue;
488 if (bFirst)
490 bFirst = false;
491 continue;
494 // The second multi-portion has two lines, check the start of the second line.
495 auto pMulti = static_cast<SwMultiPortion*>(pPor);
496 // Without the accompanying fix in place, this test would have failed, as the portion was a
497 // margin portion, not a text portion. The margin was so large that the actual text portion was
498 // hidden. No margin is needed here at all.
499 CPPUNIT_ASSERT(pMulti->GetRoot().GetNext()->GetFirstPortion()->IsTextPortion());
503 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testEmptyNumberingPageSplit)
505 // Given a document with 2 pages: the only para on page 1 is a numbering without a number
506 // portion:
507 createSwDoc("empty-numbering-page-split.fodt");
509 // When inserting an image that doesn't fit the body frame:
510 // Then make sure that the layout update after insertion finishes:
511 uno::Sequence<beans::PropertyValue> aArgs = {
512 comphelper::makePropertyValue("FileName", createFileURL(u"image.png")),
514 // Without the accompanying fix in place, this never finished.
515 dispatchCommand(mxComponent, ".uno:InsertGraphic", aArgs);
518 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testClearingLineBreak)
520 // Given a document with a fly frame and two characters wrapped around it:
521 createSwDoc("clearing-break.fodt");
522 // Insert a clearing break between "A" and "B":
523 uno::Reference<text::XTextDocument> xDocument(mxComponent, uno::UNO_QUERY);
524 uno::Reference<text::XText> xText = xDocument->getText();
525 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
526 xCursor->gotoEnd(/*bSelect=*/false);
527 xCursor->goLeft(/*nCount=*/1, /*bSelect=*/false);
528 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
529 uno::Reference<text::XTextContent> xLineBreak(
530 xFactory->createInstance("com.sun.star.text.LineBreak"), uno::UNO_QUERY);
531 uno::Reference<beans::XPropertySet> xLineBreakProps(xLineBreak, uno::UNO_QUERY);
532 auto eClear = static_cast<sal_Int16>(SwLineBreakClear::ALL);
533 xLineBreakProps->setPropertyValue("Clear", uno::Any(eClear));
534 xText->insertTextContent(xCursor, xLineBreak, /*bAbsorb=*/false);
536 // When laying out that document:
537 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
539 // Then make sure that the second line "jumps down", below the fly frame:
540 // Without the accompanying fix in place, this test would have failed with:
541 // - Expected: 1024
542 // - Actual : 276
543 // i.e. the line height wasn't the twips value of the 1.806 cm from the file, but was based on
544 // the font size of the text, which is only correct for non-clearing breaks.
545 assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout[1]", "height", "1024");
548 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testClearingLineBreakAtStart)
550 // Given a document with a fly frame and a character wrapped around it:
551 createSwDoc("clearing-break-start.fodt");
552 // Insert a clearing break before "X":
553 uno::Reference<text::XTextDocument> xDocument(mxComponent, uno::UNO_QUERY);
554 uno::Reference<text::XText> xText = xDocument->getText();
555 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
556 xCursor->gotoEnd(/*bSelect=*/false);
557 xCursor->goLeft(/*nCount=*/1, /*bSelect=*/false);
558 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
559 uno::Reference<text::XTextContent> xLineBreak(
560 xFactory->createInstance("com.sun.star.text.LineBreak"), uno::UNO_QUERY);
561 uno::Reference<beans::XPropertySet> xLineBreakProps(xLineBreak, uno::UNO_QUERY);
562 auto eClear = static_cast<sal_Int16>(SwLineBreakClear::ALL);
563 xLineBreakProps->setPropertyValue("Clear", uno::Any(eClear));
564 xText->insertTextContent(xCursor, xLineBreak, /*bAbsorb=*/false);
566 // When laying out that document:
567 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
569 // Then make sure that the second line "jumps down", below the fly frame:
570 // Without the accompanying fix in place, this test would have failed with:
571 // - Expected: 1024
572 // - Actual : 276
573 // i.e. the line height was too small, but only in case the full line was a fly and a break
574 // portion, without any real content.
575 assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout[1]", "height", "1024");
578 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testClearingLineBreakLeft)
580 // Given a document with two anchored objects (left height is 5cm, right height is 7.5cm) and a
581 // clearing break (type=left):
582 createSwDoc();
583 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
584 uno::Reference<text::XTextDocument> xDocument(mxComponent, uno::UNO_QUERY);
585 uno::Reference<text::XText> xText = xDocument->getText();
586 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
588 uno::Reference<drawing::XShape> xShape(
589 xFactory->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY);
590 xShape->setSize(awt::Size(5000, 5000));
591 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
592 xShapeProps->setPropertyValue("AnchorType",
593 uno::Any(text::TextContentAnchorType_AT_CHARACTER));
594 uno::Reference<text::XTextContent> xShapeContent(xShape, uno::UNO_QUERY);
595 xText->insertTextContent(xCursor, xShapeContent, /*bAbsorb=*/false);
598 uno::Reference<drawing::XShape> xShape(
599 xFactory->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY);
600 xShape->setSize(awt::Size(5000, 7500));
601 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
602 xShapeProps->setPropertyValue("AnchorType",
603 uno::Any(text::TextContentAnchorType_AT_CHARACTER));
604 xShapeProps->setPropertyValue("HoriOrientPosition", uno::Any(sal_Int32(10000)));
605 uno::Reference<text::XTextContent> xShapeContent2(xShape, uno::UNO_QUERY);
606 xText->insertTextContent(xCursor, xShapeContent2, /*bAbsorb=*/false);
608 uno::Reference<text::XTextContent> xLineBreak(
609 xFactory->createInstance("com.sun.star.text.LineBreak"), uno::UNO_QUERY);
610 uno::Reference<beans::XPropertySet> xLineBreakProps(xLineBreak, uno::UNO_QUERY);
611 auto eClear = static_cast<sal_Int16>(SwLineBreakClear::LEFT);
612 xLineBreakProps->setPropertyValue("Clear", uno::Any(eClear));
613 xText->insertString(xCursor, "foo", /*bAbsorb=*/false);
614 xText->insertTextContent(xCursor, xLineBreak, /*bAbsorb=*/false);
615 xText->insertString(xCursor, "bar", /*bAbsorb=*/false);
617 // When laying out that document:
618 calcLayout();
620 // Then make sure the "bar" jumps down below the left shape, but not below the right shape (due
621 // to type=left):
622 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
623 // Without the accompanying fix in place, this test would have failed with:
624 // - Expected: 2837
625 // - Actual : 4254
626 // i.e. any non-none type was handled as type=all, and this was jumping below both shapes.
627 assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout[1]", "height", "2837");
630 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testClearingLineBreakLeftRTL)
632 // Given a document with an anchored object in an RTL para and a clearing break (type=left):
633 createSwDoc();
634 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
635 uno::Reference<text::XTextDocument> xDocument(mxComponent, uno::UNO_QUERY);
636 uno::Reference<text::XText> xText = xDocument->getText();
637 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
638 uno::Reference<beans::XPropertySet> xCursorProps(xCursor, uno::UNO_QUERY);
639 xCursorProps->setPropertyValue("WritingMode", uno::Any(text::WritingMode2::RL_TB));
641 uno::Reference<drawing::XShape> xShape(
642 xFactory->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY);
643 xShape->setSize(awt::Size(5000, 5000));
644 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
645 xShapeProps->setPropertyValue("AnchorType",
646 uno::Any(text::TextContentAnchorType_AT_CHARACTER));
647 uno::Reference<text::XTextContent> xShapeContent(xShape, uno::UNO_QUERY);
648 xText->insertTextContent(xCursor, xShapeContent, /*bAbsorb=*/false);
650 uno::Reference<text::XTextContent> xLineBreak(
651 xFactory->createInstance("com.sun.star.text.LineBreak"), uno::UNO_QUERY);
652 uno::Reference<beans::XPropertySet> xLineBreakProps(xLineBreak, uno::UNO_QUERY);
653 auto eClear = static_cast<sal_Int16>(SwLineBreakClear::RIGHT);
654 xLineBreakProps->setPropertyValue("Clear", uno::Any(eClear));
655 xText->insertString(xCursor, "foo", /*bAbsorb=*/false);
656 xText->insertTextContent(xCursor, xLineBreak, /*bAbsorb=*/false);
657 xText->insertString(xCursor, "bar", /*bAbsorb=*/false);
659 // When laying out that document:
660 calcLayout();
662 // Then make sure the "bar" does not jump down (due to type=left && RTL):
663 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
664 // Without the accompanying fix in place, this test would have failed with:
665 // - Expected: 276
666 // - Actual : 2837
667 // i.e. left/right was not ignored in the RTL case.
668 assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout[1]", "height", "276");
671 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testClearingLineBreakVertical)
673 // Given a document with an anchored object in a vertical page and a clearing break (type=all):
674 createSwDoc();
675 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
676 uno::Reference<text::XTextDocument> xDocument(mxComponent, uno::UNO_QUERY);
677 uno::Reference<text::XText> xText = xDocument->getText();
678 uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor();
679 uno::Reference<beans::XPropertySet> xStandard(getStyles("PageStyles")->getByName("Standard"),
680 uno::UNO_QUERY);
681 xStandard->setPropertyValue("WritingMode", uno::Any(text::WritingMode2::TB_RL));
683 uno::Reference<drawing::XShape> xShape(
684 xFactory->createInstance("com.sun.star.drawing.RectangleShape"), uno::UNO_QUERY);
685 xShape->setSize(awt::Size(5000, 5000));
686 uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
687 xShapeProps->setPropertyValue("AnchorType",
688 uno::Any(text::TextContentAnchorType_AT_CHARACTER));
689 uno::Reference<text::XTextContent> xShapeContent(xShape, uno::UNO_QUERY);
690 xText->insertTextContent(xCursor, xShapeContent, /*bAbsorb=*/false);
692 uno::Reference<text::XTextContent> xLineBreak(
693 xFactory->createInstance("com.sun.star.text.LineBreak"), uno::UNO_QUERY);
694 uno::Reference<beans::XPropertySet> xLineBreakProps(xLineBreak, uno::UNO_QUERY);
695 auto eClear = static_cast<sal_Int16>(SwLineBreakClear::ALL);
696 xLineBreakProps->setPropertyValue("Clear", uno::Any(eClear));
697 xText->insertString(xCursor, "foo", /*bAbsorb=*/false);
698 xText->insertTextContent(xCursor, xLineBreak, /*bAbsorb=*/false);
699 xText->insertString(xCursor, "bar", /*bAbsorb=*/false);
701 // When laying out that document:
702 calcLayout();
704 // Then make sure the "bar" does jump (logic) down the correct amount:
705 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
706 // Without the accompanying fix in place, this test would have failed with:
707 // - Expected: 2837
708 // - Actual : 7135
709 // i.e. the expected break height is the twips value of the 5cm rectangle size, it was much
710 // more.
711 assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout[1]/SwBreakPortion", "height", "2837");
714 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testClearingLineBreakHeader)
716 // Given a document with a shape in the header and a clearing break in the body text:
717 createSwDoc("clearing-break-header.fodt");
719 // When laying out that document:
720 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
722 // Then make sure that the shape from the header is ignored while calculating the line height:
723 // Without the accompanying fix in place, this test would have failed with:
724 // - Expected: 276
725 // - Actual : 15398
726 // i.e. the shape was in the background, but we failed to ignore it for the break portion.
727 assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout[1]", "height", "276");
730 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testAsCharImageDocModelFromViewPoint)
732 // Given a document with an as-char image:
733 createSwDoc();
734 SwDoc* pDoc = getSwDoc();
735 uno::Reference<lang::XMultiServiceFactory> xFactory(mxComponent, uno::UNO_QUERY);
736 uno::Reference<beans::XPropertySet> xTextGraphic(
737 xFactory->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY);
738 // Only set the anchor type, the actual bitmap content is not interesting.
739 xTextGraphic->setPropertyValue("AnchorType",
740 uno::Any(text::TextContentAnchorType_AS_CHARACTER));
741 uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
742 uno::Reference<text::XText> xBodyText = xTextDocument->getText();
743 uno::Reference<text::XTextCursor> xCursor(xBodyText->createTextCursor());
744 uno::Reference<text::XTextContent> xTextContent(xTextGraphic, uno::UNO_QUERY);
745 xBodyText->insertTextContent(xCursor, xTextContent, false);
746 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
747 SwRootFrame* pRootFrame = pWrtShell->GetLayout();
748 SwFrame* pPageFrame = pRootFrame->GetLower();
749 SwFrame* pBodyFrame = pPageFrame->GetLower();
750 SwFrame* pTextFrame = pBodyFrame->GetLower();
751 const SwSortedObjs& rSortedObjs = *pTextFrame->GetDrawObjs();
752 const SwAnchoredObject* pAnchoredObject = rSortedObjs[0];
753 // The content points to the start node, the next node is the graphic node.
754 SwNodeIndex aGraphicNode = *pAnchoredObject->GetFrameFormat().GetContent().GetContentIdx();
755 ++aGraphicNode;
756 tools::Rectangle aFlyFrame = pAnchoredObject->GetDrawObj()->GetLastBoundRect();
757 Point aDocPos = aFlyFrame.Center();
759 // When translating the view point to the model position:
760 pWrtShell->SttCursorMove();
761 pWrtShell->CallSetCursor(&aDocPos, /*bOnlyText=*/false);
762 pWrtShell->EndCursorMove();
764 // Then make sure that we find the graphic node, and not its anchor:
765 SwShellCursor* pShellCursor = pWrtShell->getShellCursor(/*bBlock=*/false);
766 // Without the accompanying fix in place, this test would have failed with:
767 // - Expected: SwNodeIndex (node 6)
768 // - Actual : SwNodeIndex (node 12)
769 // i.e. the cursor position was the text node hosting the as-char image, not the graphic node of
770 // the image.
771 CPPUNIT_ASSERT_EQUAL(aGraphicNode.GetIndex(), pShellCursor->GetMark()->GetNodeIndex());
774 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testRedlineDelete)
776 // Given a document with A4 paper size, some text, redlining on, but hidden:
777 createSwDoc();
778 SwDoc* pDoc = getSwDoc();
779 SwDocShell* pDocShell = pDoc->GetDocShell();
780 SwWrtShell* pWrtShell = pDocShell->GetWrtShell();
782 // Set page size to A4.
783 size_t nCurIdx = pWrtShell->GetCurPageDesc();
784 SwPageDesc aPageDesc(pWrtShell->GetPageDesc(nCurIdx));
785 SwFrameFormat& rMaster = aPageDesc.GetMaster();
786 SwFormatFrameSize aSize(SwFrameSize::Fixed);
787 aSize.SetSize(Size(11906, 16838));
788 rMaster.SetFormatAttr(aSize);
789 pWrtShell->ChgPageDesc(nCurIdx, aPageDesc);
791 OUString aBefore("aaaaaaaaa aaaaaaaaaa aa aa aa ");
792 OUString aDelete("delete eeeeeeeeeee ee eeeeeeeeeee ee eeeeee");
793 pWrtShell->Insert(aBefore + " " + aDelete
794 + " zz zzz zzzzzzzzz zzz zzzz zzzz zzzzzzzzz zzzzzz zzz zzzzzzzzzzz zzz");
795 // Enable redlining.
796 pDocShell->SetChangeRecording(/*bActivate=*/true);
797 // Hide redlining.
798 pWrtShell->StartAllAction();
799 pWrtShell->GetLayout()->SetHideRedlines(true);
800 pWrtShell->EndAllAction();
802 // When deleting content in the middle of the paragraph:
803 pWrtShell->SttEndDoc(/*bStt=*/true);
804 pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, /*nCount=*/aBefore.getLength(),
805 /*bBasicCall=*/false);
806 pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, /*nCount=*/aDelete.getLength(),
807 /*bBasicCall=*/false);
808 // Without the accompanying fix in place, this test would have crashed:
809 pWrtShell->Delete();
811 // Then make sure that the redline is created:
812 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1),
813 pDoc->getIDocumentRedlineAccess().GetRedlineTable().size());
816 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testTdf120715_CursorMoveWhenTypingSpaceAtCenteredLineEnd)
818 createSwDoc("tdf43100_tdf120715_cursorOnSpacesOverMargin.docx");
819 SwDoc* pDoc = getSwDoc();
820 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
822 // Make a paint to force the call of AddExtraBlankWidth, that calculate width for holePortions.
823 pDoc->GetDocShell()->GetPreviewBitmap();
825 // Move the cursor to the last character of the document.
826 pWrtShell->EndOfSection();
828 //Press space and check if the cursor move right with the additional space.
829 sal_Int32 nOldCursorPos = pWrtShell->GetCharRect().Left();
830 pWrtShell->Insert(" ");
831 sal_Int32 nNewCursorPos = pWrtShell->GetCharRect().Left();
832 CPPUNIT_ASSERT_GREATER(nOldCursorPos, nNewCursorPos);
835 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testTdf43100_CursorMoveToSpacesOverMargin)
837 // Test the cursor movement over the right margin in several different paragraphs.
838 // These differences are based on its paragraphs
839 // - alignment (left, center, right, justified),
840 // - line count (1 line, 2 lines, blank line containing only spaces)
841 createSwDoc("tdf43100_tdf120715_cursorOnSpacesOverMargin.docx");
842 SwDoc* pDoc = getSwDoc();
843 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
845 // Make a paint to force the call of AddExtraBlankWidth, that calculate width for holePortions.
846 pDoc->GetDocShell()->GetPreviewBitmap();
848 // Move the cursor to the 2. line.
849 pWrtShell->Down(/*bSelect=*/false, 1, /*bBasicCall=*/false);
850 // Move the cursor to the right margin.
851 pWrtShell->RightMargin(false, false);
853 sal_Int32 nMarginPos = pWrtShell->GetCharRect().Left();
854 sal_Int32 nLastCursorPos = nMarginPos;
856 // Move the cursor right 5 times, every step should increase the cursor x position.
857 // Before this fix, the cursor stopped at the margin.
858 for (int i = 0; i < 5; i++)
860 pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
861 sal_Int32 nNewCursorPos = pWrtShell->GetCharRect().Left();
862 CPPUNIT_ASSERT_GREATER(nLastCursorPos, nNewCursorPos);
863 nLastCursorPos = nNewCursorPos;
866 // Move down the cursor several lines, and check if it will keep nearly its horizontal position.
867 // Some of the lines are not reach beyond the margin, there the cursor won't be able to keep its
868 // original position.
869 bool aLineReachOverMargin[] = { false, true, true, false, false, true, true, false, true,
870 true, true, true, false, true, true, false, false };
871 // Cursor position can be a bit inaccurate, because it can only be positioned on characters,
872 // that is based on the actual line layout, therefore the actual cursor position
873 // is checked against a more distinct position instead of the nMarginPos.
874 sal_Int32 nAvgLeft = (nMarginPos + nLastCursorPos) / 2;
875 for (int i = 2; i < 17; i++)
877 pWrtShell->Down(/*bSelect=*/false, 1, /*bBasicCall=*/false);
878 sal_Int32 nNewCursorPos = pWrtShell->GetCharRect().Left();
879 if (aLineReachOverMargin[i])
880 CPPUNIT_ASSERT_GREATER(nAvgLeft, nNewCursorPos);
881 else
882 CPPUNIT_ASSERT_LESS(nAvgLeft, nNewCursorPos);
886 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testContentControlPDF)
888 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
889 if (!pPDFium)
890 return;
892 // Given a file with a content control:
893 createSwDoc();
894 SwDoc* pDoc = getSwDoc();
895 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
896 pWrtShell->InsertContentControl(SwContentControlType::RICH_TEXT);
897 pWrtShell->SttEndDoc(/*bStt=*/true);
898 pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
899 sal_Int32 nPlaceHolderLen = SwResId(STR_CONTENT_CONTROL_PLACEHOLDER).getLength();
900 pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, nPlaceHolderLen,
901 /*bBasicCall=*/false);
902 pWrtShell->Insert("mycontent");
903 const SwPosition* pStart = pWrtShell->GetCursor()->Start();
904 SwTextNode* pTextNode = pStart->GetNode().GetTextNode();
905 sal_Int32 nIndex = pStart->GetContentIndex();
906 SwTextAttr* pAttr
907 = pTextNode->GetTextAttrAt(nIndex, RES_TXTATR_CONTENTCONTROL, sw::GetTextAttrMode::Parent);
908 auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr);
909 const SwFormatContentControl& rFormatContentControl = pTextContentControl->GetContentControl();
910 std::shared_ptr<SwContentControl> pContentControl = rFormatContentControl.GetContentControl();
911 // Alias/title, to be mapped to PDF's description.
912 pContentControl->SetAlias("mydesc");
914 // When exporting to PDF:
915 save("writer_pdf_Export");
917 // Then make sure that a fillable form widget is emitted:
918 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
919 std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0);
920 // Without the accompanying fix in place, this test would have failed with:
921 // - Expected: 1
922 // - Actual : 0
923 // i.e. the content control was just exported as normal text.
924 CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationCount());
925 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(0);
926 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Widget, pAnnotation->getSubType());
928 // Also verify that the widget description is correct, it was empty:
929 CPPUNIT_ASSERT_EQUAL(OUString("mydesc"),
930 pAnnotation->getFormFieldAlternateName(pPdfDocument.get()));
933 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testContentControlPlaceholderPDF)
935 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
936 if (!pPDFium)
937 return;
939 // Given a file with a content control, in placeholder mode:
940 createSwDoc();
941 SwDoc* pDoc = getSwDoc();
942 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
943 pWrtShell->InsertContentControl(SwContentControlType::RICH_TEXT);
945 // When exporting to PDF:
946 save("writer_pdf_Export");
948 // Then make sure that a fillable form widget is emitted with the expected value:
949 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
950 std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0);
951 CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationCount());
952 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(0);
953 // Without the accompanying fix in place, this test would have failed with:
954 // - Expected: Click here to enter text
955 // - Actual :
956 // i.e. the value of the content control was empty, the placeholder value was lost.
957 CPPUNIT_ASSERT_EQUAL(SwResId(STR_CONTENT_CONTROL_PLACEHOLDER),
958 pAnnotation->getFormFieldValue(pPdfDocument.get()));
961 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testCheckboxContentControlPDF)
963 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
964 if (!pPDFium)
965 return;
967 // Given a file with a checkbox content control:
968 createSwDoc();
969 SwDoc* pDoc = getSwDoc();
970 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
971 pWrtShell->InsertContentControl(SwContentControlType::CHECKBOX);
973 // When exporting to PDF:
974 save("writer_pdf_Export");
976 // Then make sure that a checkbox form widget is emitted:
977 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
978 std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0);
979 // Without the accompanying fix in place, this test would have failed with:
980 // - Expected: 1
981 // - Actual : 0
982 // i.e. the checkbox content control was just exported as normal text.
983 CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationCount());
984 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(0);
985 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Widget, pAnnotation->getSubType());
986 // Also check the form widget type:
987 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::CheckBox,
988 pAnnotation->getFormFieldType(pPdfDocument.get()));
991 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testDropdownContentControlPDF)
993 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
994 if (!pPDFium)
995 return;
997 // Given a file with a dropdown content control:
998 createSwDoc();
999 SwDoc* pDoc = getSwDoc();
1000 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
1001 pWrtShell->InsertContentControl(SwContentControlType::DROP_DOWN_LIST);
1003 // When exporting to PDF:
1004 save("writer_pdf_Export");
1006 // Then make sure that a dropdown form widget is emitted:
1007 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1008 std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0);
1009 // Without the accompanying fix in place, this test would have failed with:
1010 // - Expected: 1
1011 // - Actual : 0
1012 // i.e. the dropdown content control was just exported as normal text.
1013 CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationCount());
1014 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(0);
1015 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Widget, pAnnotation->getSubType());
1016 // Also check the form widget type (our dropdown is called combo in PDF terms):
1017 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::ComboBox,
1018 pAnnotation->getFormFieldType(pPdfDocument.get()));
1021 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testDropdownContentControlPDF2)
1023 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
1024 if (!pPDFium)
1025 return;
1027 createSwDoc("tdf153040.docx");
1029 save("writer_pdf_Export");
1031 // Make sure that a dropdown form widget is emitted:
1032 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1033 std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0);
1035 CPPUNIT_ASSERT_EQUAL(4, pPage->getAnnotationCount());
1036 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(0);
1037 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Widget, pAnnotation->getSubType());
1038 // Also check the form widget type (our dropdown is called combo in PDF terms):
1039 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::ComboBox,
1040 pAnnotation->getFormFieldType(pPdfDocument.get()));
1041 // Without tdf#153040's fix, this would have been the empty OUString()
1042 CPPUNIT_ASSERT_EQUAL(OUString("Apfel"), pAnnotation->getFormFieldValue(pPdfDocument.get()));
1045 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testDateContentControlPDF)
1047 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
1048 if (!pPDFium)
1049 return;
1051 // Given a file with a date content control:
1052 createSwDoc();
1053 SwDoc* pDoc = getSwDoc();
1054 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
1055 pWrtShell->InsertContentControl(SwContentControlType::DATE);
1057 // When exporting to PDF:
1058 save("writer_pdf_Export");
1060 // Then make sure that a date form widget is emitted:
1061 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1062 std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0);
1063 // Without the accompanying fix in place, this test would have failed with:
1064 // - Expected: 1
1065 // - Actual : 0
1066 // i.e. the date content control was just exported as normal text.
1067 CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationCount());
1068 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(0);
1069 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Widget, pAnnotation->getSubType());
1070 // Also check the form widget type (our date is a mode of text in PDF terms):
1071 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::TextField,
1072 pAnnotation->getFormFieldType(pPdfDocument.get()));
1073 OUString aAction = pAnnotation->getFormAdditionalActionJavaScript(
1074 pPdfDocument.get(), vcl::pdf::PDFAnnotAActionType::KeyStroke);
1075 CPPUNIT_ASSERT_EQUAL(OUString("AFDate_KeystrokeEx(\"mm/dd/yy\");"), aAction);
1078 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testContentControlPDFFont)
1080 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
1081 if (!pPDFium)
1082 return;
1084 // Given a document with a custom 24pt font size and a content control:
1085 createSwDoc();
1086 SwDoc* pDoc = getSwDoc();
1087 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
1088 SfxItemSetFixed<RES_CHRATR_FONTSIZE, RES_CHRATR_FONTSIZE> aSet(pWrtShell->GetAttrPool());
1089 SvxFontHeightItem aItem(480, 100, RES_CHRATR_FONTSIZE);
1090 aSet.Put(aItem);
1091 pWrtShell->SetAttrSet(aSet);
1092 pWrtShell->InsertContentControl(SwContentControlType::RICH_TEXT);
1094 // When exporting that document to PDF:
1095 save("writer_pdf_Export");
1097 // Then make sure that the widget in the PDF result has that custom font size:
1098 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1099 std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0);
1100 pPage->onAfterLoadPage(pPdfDocument.get());
1101 CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationCount());
1102 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(0);
1103 // Without the accompanying fix in place, this test would have failed with:
1104 // - Expected: 24
1105 // - Actual : 8
1106 // i.e. i.e. the font size was some default, not the 24pt specified in the model.
1107 CPPUNIT_ASSERT_EQUAL(24.0f, pAnnotation->getFontSize(pPdfDocument.get()));
1110 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testComboContentControlPDF)
1112 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
1113 if (!pPDFium)
1114 return;
1116 // Given a file with a combo box content control:
1117 createSwDoc();
1118 SwDoc* pDoc = getSwDoc();
1119 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
1120 pWrtShell->InsertContentControl(SwContentControlType::COMBO_BOX);
1122 // When exporting to PDF:
1123 save("writer_pdf_Export");
1125 // Then make sure that a combo box form widget is emitted:
1126 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1127 std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0);
1128 // Without the accompanying fix in place, this test would have failed with:
1129 // - Expected: 1
1130 // - Actual : 0
1131 // i.e. the combo box content control was exported as plain text.
1132 CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationCount());
1133 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(0);
1134 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Widget, pAnnotation->getSubType());
1135 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFFormFieldType::ComboBox,
1136 pAnnotation->getFormFieldType(pPdfDocument.get()));
1137 // 19th bit: combo box, not dropdown.
1138 CPPUNIT_ASSERT(pAnnotation->getFormFieldFlags(pPdfDocument.get()) & 0x00040000);
1141 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testRichContentControlPDF)
1143 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
1144 if (!pPDFium)
1145 return;
1147 // Given a file with a rich content control, its value set to "xxx<b>yyy</b>":
1148 createSwDoc();
1149 SwDoc* pDoc = getSwDoc();
1150 SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
1151 pWrtShell->InsertContentControl(SwContentControlType::RICH_TEXT);
1152 pWrtShell->SttEndDoc(/*bStt=*/true);
1153 pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false);
1154 sal_Int32 nPlaceHolderLen = SwResId(STR_CONTENT_CONTROL_PLACEHOLDER).getLength();
1155 pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, nPlaceHolderLen,
1156 /*bBasicCall=*/false);
1157 pWrtShell->Insert("xxxyyy");
1158 pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/true, 3, /*bBasicCall=*/false);
1159 SfxItemSetFixed<RES_CHRATR_WEIGHT, RES_CHRATR_WEIGHT> aSet(pWrtShell->GetAttrPool());
1160 SvxWeightItem aItem(WEIGHT_BOLD, RES_CHRATR_WEIGHT);
1161 aSet.Put(aItem);
1162 pWrtShell->SetAttrSet(aSet);
1164 // When exporting to PDF:
1165 save("writer_pdf_Export");
1167 // Then make sure that a single fillable form widget is emitted:
1168 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1169 std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0);
1170 // Without the accompanying fix in place, this test would have failed with:
1171 // - Expected: 1
1172 // - Actual : 2
1173 // i.e. "xxx<b>yyy</b>" was exported as 2 widgets, not 1.
1174 CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationCount());
1177 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testPlaceholderFieldPDF)
1179 std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
1180 if (!pPDFium)
1181 return;
1183 // Given a file with a text-type placeholder field:
1184 createSwDoc("placeholder.fodt");
1186 // When exporting to PDF (default setting is "create a PDF form"):
1187 save("writer_pdf_Export");
1189 // Then make sure that a fillable form widget is emitted:
1190 std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument = parsePDFExport();
1191 std::unique_ptr<vcl::pdf::PDFiumPage> pPage = pPdfDocument->openPage(0);
1192 // Without the accompanying fix in place, this test would have failed with:
1193 // - Expected: 1
1194 // - Actual : 0
1195 // i.e. the placeholder field was just exported as normal text.
1196 CPPUNIT_ASSERT_EQUAL(1, pPage->getAnnotationCount());
1197 std::unique_ptr<vcl::pdf::PDFiumAnnotation> pAnnotation = pPage->getAnnotation(0);
1198 CPPUNIT_ASSERT_EQUAL(vcl::pdf::PDFAnnotationSubType::Widget, pAnnotation->getSubType());
1200 // Also verify that the widget description is correct:
1201 CPPUNIT_ASSERT_EQUAL(OUString("reference text"),
1202 pAnnotation->getFormFieldAlternateName(pPdfDocument.get()));
1205 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testNumberPortionFormat)
1207 // Given a document with a single paragraph, direct formatting asks 24pt font size for the
1208 // numbering and the text portion:
1209 createSwDoc("number-portion-format.odt");
1211 // When laying out that document:
1212 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
1214 // Then make sure that the numbering portion has the correct font size:
1215 // Without the accompanying fix in place, this test would have failed with:
1216 // - Expected: 480
1217 // - Actual : 240
1218 // i.e. the numbering portion font size was 12pt, not 24pt (but only when the doc had a
1219 // bookmark).
1220 assertXPath(pXmlDoc,
1221 "//SwParaPortion/SwLineLayout/child::*[@type='PortionType::Number']/SwFont",
1222 "height", "480");
1225 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testNumberPortionNoformat)
1227 // Given a document with a numbering and a single paragraph, the entire run is red:
1228 createSwDoc("number-portion-noformat.docx");
1230 // When laying out that document:
1231 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
1233 // Then make sure that just because the entire run is red, the numbering portion is not red:
1234 // Without the accompanying fix in place, this test would have failed with:
1235 // - Expected: ffffffff (COL_AUTO)
1236 // - Actual : 00ff0000 (COL_LIGHTRED)
1237 // i.e. the run color affected the color of the number portion in Writer, but not in Word.
1238 CPPUNIT_ASSERT_EQUAL(
1239 OUString("ffffffff"),
1240 getXPath(pXmlDoc, "//SwParaPortion/SwLineLayout/SwFieldPortion/SwFont", "color"));
1243 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testFloattableOverlap)
1245 // Given a document with 2 floating tables, not overlapping in Word's "Word 2010" compat mode,
1246 // because the first empty paragraph is below the first floating table:
1247 createSwDoc("floattable-overlap.docx");
1249 // When laying out that document:
1250 calcLayout();
1252 // Then make sure they don't overlap in Writer, either:
1253 SwDoc* pDoc = getSwDoc();
1254 SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
1255 auto pPage1 = dynamic_cast<SwPageFrame*>(pLayout->Lower());
1256 CPPUNIT_ASSERT(pPage1);
1257 CPPUNIT_ASSERT(pPage1->GetSortedObjs());
1258 const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs();
1259 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), rPage1Objs.size());
1260 SwAnchoredObject* pPage1Obj1 = rPage1Objs[0];
1261 const SwRect& rRect1 = pPage1Obj1->GetObjRectWithSpaces();
1262 SwAnchoredObject* pPage1Obj2 = rPage1Objs[1];
1263 const SwRect& rRect2 = pPage1Obj2->GetObjRectWithSpaces();
1264 // Without the accompanying fix in place, this test would have failed, the empty paragraph,
1265 // which is after the floating table in the document model went above the floating table in the
1266 // layout, which resulted in an overlap.
1267 CPPUNIT_ASSERT(!rRect1.Overlaps(rRect2));
1270 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testFloattableAnchorNextPage)
1272 // Given a document with 3 floating tables, the last one has a negative vertical offset, so the
1273 // floating table is on page 1, but its anchor frame is effectively on page 2:
1274 createSwDoc("floattable-anchor-next-page.docx");
1276 // When laying out that document:
1277 calcLayout();
1279 // Then make sure all 3 floating tables are on page 1:
1280 SwDoc* pDoc = getSwDoc();
1281 SwRootFrame* pLayout = pDoc->getIDocumentLayoutAccess().GetCurrentLayout();
1282 auto pPage1 = dynamic_cast<SwPageFrame*>(pLayout->Lower());
1283 CPPUNIT_ASSERT(pPage1);
1284 CPPUNIT_ASSERT(pPage1->GetSortedObjs());
1285 const SwSortedObjs& rPage1Objs = *pPage1->GetSortedObjs();
1286 // Without the accompanying fix in place, this test would have failed with:
1287 // - Expected: 3
1288 // - Actual : 2
1289 // i.e. the last floating table was on the wrong page (page 2).
1290 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), rPage1Objs.size());
1293 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testParaUpperMarginFlyIntersect)
1295 // Given a document with 2 paragraphs, the paragraphs have both upper and lower spacing of 567
1296 // twips:
1297 createSwDoc("para-upper-margin-fly-intersect.docx");
1299 // When laying out that document:
1300 calcLayout();
1302 // Then make sure that we shift down the text in the second paragraph only based on the 2nd para
1303 // upper margin, not based on the 1st para lower margin:
1304 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
1305 int nFlyCount
1306 = getXPathContent(pXmlDoc,
1307 "count(//SwParaPortion/SwLineLayout/child::*[@type='PortionType::Fly'])")
1308 .toInt32();
1309 int nHeight = 0;
1310 for (int i = 1; i <= nFlyCount; ++i)
1312 OString xPath = "(//SwParaPortion/SwLineLayout/child::*[@type='PortionType::Fly'])["
1313 + OString::number(i) + "]";
1314 nHeight += getXPath(pXmlDoc, xPath, "height").toInt32();
1316 // Without the accompanying fix in place, this test would have failed with:
1317 // - Expected: 521 (~500)
1318 // - Actual : 857 (~1000)
1319 // I.e. both upper and lower margin was taken into account.
1320 CPPUNIT_ASSERT_EQUAL(521, nHeight);
1323 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testTdf129810)
1325 // Load the document, which embeds a CJK font.
1326 createSwDoc("tdf129810.odt");
1328 // Render the document to a metafile.
1329 SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument*>(mxComponent.get());
1330 SwDocShell* pShell = pTextDoc->GetDocShell();
1331 std::shared_ptr<GDIMetaFile> xMetaFile = pShell->GetPreviewMetaFile();
1332 CPPUNIT_ASSERT(xMetaFile);
1334 // Find the fist text array action
1335 for (size_t nAction = 0; nAction < xMetaFile->GetActionSize(); nAction++)
1337 auto pAction = xMetaFile->GetAction(nAction);
1338 if (pAction->GetType() == MetaActionType::TEXTARRAY)
1340 auto pTextArrayAction = static_cast<MetaTextArrayAction*>(pAction);
1341 auto pDXArray = pTextArrayAction->GetDXArray();
1343 // There should be 13 chars on the first line
1344 CPPUNIT_ASSERT_GREATER(size_t(13), pDXArray.size());
1346 // Assert we are using the expected width for uncompressed chars
1347 CPPUNIT_ASSERT_EQUAL(sal_Int32(720), pDXArray[0]);
1348 // Assert we are using the expected width for compressed chars
1349 CPPUNIT_ASSERT_EQUAL(sal_Int32(500), pDXArray[6] - pDXArray[5]);
1350 break;
1355 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testScriptinfosurrogatePairs)
1357 createSwDoc("scriptinfo-surrogate-pairs.fodt");
1358 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
1360 // Test that a dotted circle is grouped with the mark after it, even if the
1361 // mark is outside Unicode’s Basic Multilingual Plan (i.e. a surrogate pair
1362 // in UTF-8)
1364 // Without the fix it fails with:
1365 // - Expected: 11
1366 // - Actual : 11◌
1367 assertXPath(pXmlDoc, "//txt[1]/SwParaPortion/SwLineLayout/SwLinePortion[1]", "portion", u"11");
1368 assertXPath(pXmlDoc, "//txt[1]/SwParaPortion/SwLineLayout/SwLinePortion[2]", "portion",
1369 u"\u25CC\U00010A01");
1371 // Without the fix this would crash because we got a lone surrogate that
1372 // can’t be converted to UTF-8, but if it were not for that it might fail
1373 // with something like:
1374 // - Expected: 11
1375 // - Actual : 11𝐀
1376 assertXPath(pXmlDoc, "//txt[2]/SwParaPortion/SwLineLayout/SwLinePortion[1]", "portion", u"11");
1377 assertXPath(pXmlDoc, "//txt[2]/SwParaPortion/SwLineLayout/SwLinePortion[2]", "portion",
1378 u"\U0001D400\u064E");
1381 CPPUNIT_TEST_FIXTURE(SwCoreTextTest, testTdf112594)
1383 createSwDoc("tdf112594.fodt");
1384 xmlDocUniquePtr pXmlDoc = parseLayoutDump();
1386 // Test that a NNBSP is grouped with the Mongolian characters after it
1388 // Without the fix it fails with:
1389 // - Expected: 11
1390 // - Actual : 11\u202F
1391 // (U+020F is a space, so might not be visible)
1392 assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout/SwLinePortion[1]", "portion", u"11");
1393 assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout/SwLinePortion[2]", "portion",
1394 u"\u202F\u1824");
1397 CPPUNIT_PLUGIN_IMPLEMENT();
1399 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */