diff --git a/sw/inc/EnhancedPDFExportHelper.hxx b/sw/inc/EnhancedPDFExportHelper.hxx index 229e8c6df0e9..3182f1354bb2 100644 --- a/sw/inc/EnhancedPDFExportHelper.hxx +++ b/sw/inc/EnhancedPDFExportHelper.hxx @@ -180,8 +180,6 @@ class SwTaggedPDFHelper SwTaggedPDFHelper( const Num_Info* pNumInfo, const Frame_Info* pFrameInfo, const Por_Info* pPorInfo, OutputDevice const & rOut ); ~SwTaggedPDFHelper(); - - static void EndCurrentLink(OutputDevice const&); }; /* diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx b/sw/source/core/text/EnhancedPDFExportHelper.cxx index f4edc4482352..22d6d50f85ab 100644 --- a/sw/source/core/text/EnhancedPDFExportHelper.cxx +++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx @@ -19,6 +19,8 @@ #include +#include + #include #include #include @@ -164,8 +166,16 @@ struct SwEnhancedPDFState OUString StyleName; }; + struct Link + { + const SwTextAttr* pAttr; + sal_Int32 nStructElement; + // the annotations already nested in it + std::vector AnnotIds; + }; + ::std::optional m_oCurrentSpan; - ::std::optional m_oCurrentLink; + std::optional m_oCurrentLink; SwEnhancedPDFState(LanguageType const eLanguageDefault) : m_eLanguageDefault(eLanguageDefault) @@ -722,15 +732,34 @@ namespace { // link the link annotation to the link structured element void LinkLinkLink(vcl::PDFExtOutDevData & rPDFExtOutDevData, SwRect const& rRect) { + const sal_Int32 nCurrentSE(rPDFExtOutDevData.GetCurrentStructureElement()); + auto& roCurrentLink(rPDFExtOutDevData.GetSwPDFState()->m_oCurrentLink); + if (roCurrentLink && roCurrentLink->nStructElement == -1) + { + // the first call after opening a link is for the link's own SE + roCurrentLink->nStructElement = nCurrentSE; + } + const LinkIdMap& rLinkIdMap(rPDFExtOutDevData.GetSwPDFState()->m_LinkIdMap); const Point aCenter = rRect.Center(); auto aIter = std::find_if(rLinkIdMap.begin(), rLinkIdMap.end(), [&aCenter](const IdMapEntry& rEntry) { return rEntry.first.Contains(aCenter); }); - if (aIter != rLinkIdMap.end()) + if (aIter == rLinkIdMap.end()) + return; + + sal_Int32 nLinkId = (*aIter).second; + if (roCurrentLink && roCurrentLink->nStructElement == nCurrentSE) // not a nested SE { - sal_Int32 nLinkId = (*aIter).second; - rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::pdf::PDFWriter::LinkAnnotation, nLinkId); + // every portion of a line finds that line's annotation again + if (std::find(roCurrentLink->AnnotIds.begin(), roCurrentLink->AnnotIds.end(), nLinkId) + != roCurrentLink->AnnotIds.end()) + { + return; + } + roCurrentLink->AnnotIds.push_back(nLinkId); } + rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::pdf::PDFWriter::LinkAnnotation, + nLinkId); } } @@ -1900,20 +1929,6 @@ void SwTaggedPDFHelper::EndStructureElements() CheckRestoreTag(); } -void SwTaggedPDFHelper::EndCurrentLink(OutputDevice const& rOut) -{ - vcl::PDFExtOutDevData *const pPDFExtOutDevData( - dynamic_cast(rOut.GetExtOutDevData())); - if (pPDFExtOutDevData && pPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) - { - pPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset(); - pPDFExtOutDevData->EndStructureElement(); -#if OSL_DEBUG_LEVEL > 1 - aStructStack.pop_back(); -#endif - } -} - void SwTaggedPDFHelper::EndCurrentAll() { if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan) @@ -1960,7 +1975,8 @@ bool SwTaggedPDFHelper::CheckContinueSpan( || !mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink); if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) { - if (pInetFormatAttr && pInetFormatAttr == *mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) + if (pInetFormatAttr + && pInetFormatAttr == mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink->pAttr) { return true; } @@ -2050,17 +2066,6 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() aPDFType = aSpanString; break; - case PortionType::Fly: - // if a link is split by a fly overlap, then there will be multiple - // annotations for the link, and hence there must be multiple SEs, - // so every annotation has its own SE. - if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) - { - mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset(); - EndTag(); - } - break; - case PortionType::Lay : case PortionType::Text : case PortionType::Para : @@ -2077,10 +2082,18 @@ void SwTaggedPDFHelper::BeginInlineStructureElements() nPDFType = sal_uInt16(vcl::pdf::StructElement::Link); aPDFType = aLinkString; assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink); - mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace(pInetFormatAttr); + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace( + SwEnhancedPDFState::Link{ pInetFormatAttr, -1, {} }); // leave it open to let next portion decide to merge or close --m_nEndStructureElement; } + else + { + // attach this portion's annotation + SwRect aPorRect; + rInf.CalcRect(*pPor, &aPorRect); + LinkLinkLink(*mpPDFExtOutDevData, aPorRect); + } } // Emphasis else if (sStyleName == constEmphasisStyleName) diff --git a/sw/source/core/text/itrpaint.cxx b/sw/source/core/text/itrpaint.cxx index 9b420a0d70d4..b8ac8567edaa 100644 --- a/sw/source/core/text/itrpaint.cxx +++ b/sw/source/core/text/itrpaint.cxx @@ -263,8 +263,6 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip, roTaggedParagraph.emplace(nullptr, &aFrameInfo, nullptr, *GetInfo().GetOut()); } - SwTaggedPDFHelper::EndCurrentLink(*GetInfo().GetOut()); - // Optimization! SwTwips nMaxRight = std::min( rPaint.Right(), Right() ); const SwTwips nTmpLeft = GetInfo().X(); diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx index 1ff576003243..200a3aa074e0 100644 --- a/vcl/inc/pdf/pdfwriter_impl.hxx +++ b/vcl/inc/pdf/pdfwriter_impl.hxx @@ -602,6 +602,8 @@ struct PDFStructureElement std::map m_aAttributes; ::std::vector m_AnnotIds; + // a Link has one annotation per line, and per fly gap + std::vector m_LinkAnnotIds; tools::Rectangle m_aBBox; OUString m_aActualText; OUString m_aAltText; diff --git a/vcl/qa/cppunit/pdfexport/data/LinkMultiLine.fodt b/vcl/qa/cppunit/pdfexport/data/LinkMultiLine.fodt new file mode 100644 index 000000000000..9847c9d32e4a --- /dev/null +++ b/vcl/qa/cppunit/pdfexport/data/LinkMultiLine.fodt @@ -0,0 +1,29 @@ + + + Link on several lines + + + + + + + + + + + + + + + + + + + + + + + This is a hyperlink whose text is deliberately long enough that the Writer layout has to break it onto a second line of the same paragraph, which used to produce one Link structure element per line. + + + diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx index 047385af1762..ca473538fccb 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx @@ -9,6 +9,7 @@ #include +#include #include #include @@ -2598,7 +2599,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD22->GetValue()); { auto pKids = dynamic_cast(pObjectD22->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -2635,7 +2636,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(95.143, pNumL->GetValue(), 1e-3); @@ -2665,7 +2666,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD23->GetValue()); { auto pKids = dynamic_cast(pObjectD23->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -2702,7 +2703,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); @@ -2731,7 +2732,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD24->GetValue()); { auto pKids = dynamic_cast(pObjectD24->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -2768,7 +2769,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(146.043, pNumL->GetValue(), 1e-3); @@ -2797,7 +2798,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD25->GetValue()); { auto pKids = dynamic_cast(pObjectD25->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -2834,7 +2835,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); @@ -2863,7 +2864,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD26->GetValue()); { auto pKids = dynamic_cast(pObjectD26->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -2900,7 +2901,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); @@ -3074,6 +3075,155 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testIndexMarkWithSpan) CPPUNIT_ASSERT_EQUAL("de-DE"_ostr, pSpanLang->GetValue()); } +CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf163240) +{ + // Enable PDF/UA + uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) }; + + vcl::filter::PDFDocument aDocument; + loadFromFile(u"LinkMultiLine.fodt"); + save(TestFilter::PDF_WRITER, + { comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) }); + + // Parse the export result. + SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); + CPPUNIT_ASSERT(aDocument.Read(aStream)); + + std::vector aPages = aDocument.GetPages(); + CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size()); + + // the link is broken across lines, so there is one annotation per line + auto pAnnots = dynamic_cast(aPages[0]->Lookup("Annots"_ostr)); + CPPUNIT_ASSERT(pAnnots); + std::vector aAnnots; + for (const auto pElement : pAnnots->GetElements()) + { + auto pReference = dynamic_cast(pElement); + CPPUNIT_ASSERT(pReference); + auto pAnnot = pReference->LookupObject(); + CPPUNIT_ASSERT(pAnnot); + auto pType = dynamic_cast(pAnnot->Lookup("Type"_ostr)); + CPPUNIT_ASSERT(pType); + CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pType->GetValue()); + auto pSubtype = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); + CPPUNIT_ASSERT(pSubtype); + CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSubtype->GetValue()); + aAnnots.push_back(pAnnot); + } + // how many lines the link takes does not matter, only that it takes several + CPPUNIT_ASSERT_GREATER(size_t(1), aAnnots.size()); + + // ... but only one Link structure element for the whole link + vcl::filter::PDFObjectElement* pLinkSE(nullptr); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + auto pS = dynamic_cast(pObject->Lookup("S"_ostr)); + if (pType && pType->GetValue() == "StructElem" && pS && pS->GetValue() == "Link") + { + CPPUNIT_ASSERT_MESSAGE("one Link SE per link, not per line", !pLinkSE); + pLinkSE = pObject; + } + } + + CPPUNIT_ASSERT(pLinkSE); + // in the paragraph, not orphaned + auto pParentRef = dynamic_cast(pLinkSE->Lookup("P"_ostr)); + CPPUNIT_ASSERT(pParentRef); + auto pParent = pParentRef->LookupObject(); + CPPUNIT_ASSERT(pParent); + auto pParentS = dynamic_cast(pParent->Lookup("S"_ostr)); + CPPUNIT_ASSERT(pParentS); + CPPUNIT_ASSERT_EQUAL("Standard"_ostr, pParentS->GetValue()); + auto pParentKids = dynamic_cast(pParent->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pParentKids); + bool isKid(false); + for (const auto pElement : pParentKids->GetElements()) + { + auto pRef = dynamic_cast(pElement); + isKid = isKid || (pRef && pRef->LookupObject() == pLinkSE); + } + CPPUNIT_ASSERT(isKid); + + // every annotation is nested in it, and the text stayed inside it too + auto pKids = dynamic_cast(pLinkSE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pKids); + // the fixture is one page and the link contains no nested SE, so every kid + // is either the MCID of a line or the OBJR of that line's annotation + std::vector aNestedAnnots; + size_t nMCID(0); + for (const auto pElement : pKids->GetElements()) + { + if (dynamic_cast(pElement)) + { + ++nMCID; + continue; + } + auto pObjR = dynamic_cast(pElement); + CPPUNIT_ASSERT(pObjR); + auto pOType = dynamic_cast(pObjR->LookupElement("Type"_ostr)); + CPPUNIT_ASSERT(pOType); + CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); + auto pAnnotRef + = dynamic_cast(pObjR->LookupElement("Obj"_ostr)); + CPPUNIT_ASSERT(pAnnotRef); + aNestedAnnots.push_back(pAnnotRef->LookupObject()); + } + CPPUNIT_ASSERT_EQUAL(aAnnots.size(), nMCID); + CPPUNIT_ASSERT_EQUAL(aAnnots.size(), aNestedAnnots.size()); + for (const auto pAnnot : aAnnots) + { + CPPUNIT_ASSERT(std::find(aNestedAnnots.begin(), aNestedAnnots.end(), pAnnot) + != aNestedAnnots.end()); + } + + // ... and each of them resolves to it through the parent tree + vcl::filter::PDFArrayElement* pNums(nullptr); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject = dynamic_cast(rDocElement.get()); + if (!pObject) + continue; + auto pType = dynamic_cast(pObject->Lookup("Type"_ostr)); + if (pType && pType->GetValue() == "StructTreeRoot") + { + auto pParentTree = dynamic_cast( + pObject->Lookup("ParentTree"_ostr)); + CPPUNIT_ASSERT(pParentTree); + auto pNumTree = pParentTree->LookupObject(); + CPPUNIT_ASSERT(pNumTree); + pNums = dynamic_cast(pNumTree->Lookup("Nums"_ostr)); + } + } + CPPUNIT_ASSERT(pNums); + CPPUNIT_ASSERT_EQUAL(size_t(0), pNums->GetElements().size() % 2); + std::set aStructParents; + for (const auto pAnnot : aAnnots) + { + auto pStructParent + = dynamic_cast(pAnnot->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(pStructParent); // every link must have it! + // an annotation cannot share the entry of another one + CPPUNIT_ASSERT(aStructParents.insert(pStructParent->GetValue()).second); + int nFound(0); + for (size_t i = 0; i < pNums->GetElements().size(); i += 2) + { + auto pI = dynamic_cast(pNums->GetElement(i)); + CPPUNIT_ASSERT(pI); + if (pI->GetValue() != pStructParent->GetValue()) + continue; + ++nFound; + auto pSERef = dynamic_cast(pNums->GetElement(i + 1)); + CPPUNIT_ASSERT(pSERef); + CPPUNIT_ASSERT_EQUAL(pLinkSE, pSERef->LookupObject()); + } + CPPUNIT_ASSERT_EQUAL(1, nFound); + } +} + CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) { // Enable PDF/UA @@ -3128,6 +3278,19 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) auto pKidsD0 = dynamic_cast(pObjectD0->Lookup("K"_ostr)); CPPUNIT_ASSERT(pKidsD0); auto pKidsD0v = pKidsD0->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(2), pKidsD0v.size()); + + // the annotations of one link cannot share a parent tree entry + auto CheckDistinct = [](vcl::filter::PDFObjectElement* pOne, + vcl::filter::PDFObjectElement* pOther) { + auto pStructParentOne + = dynamic_cast(pOne->Lookup("StructParent"_ostr)); + auto pStructParentOther + = dynamic_cast(pOther->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(pStructParentOne); + CPPUNIT_ASSERT(pStructParentOther); + CPPUNIT_ASSERT(pStructParentOne->GetValue() != pStructParentOther->GetValue()); + }; auto pRefKidD00 = dynamic_cast(pKidsD0v[0]); CPPUNIT_ASSERT(pRefKidD00); @@ -3138,9 +3301,10 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) auto pSD00 = dynamic_cast(pObjectD00->Lookup("S"_ostr)); CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD00->GetValue()); { + // the fly splits the link into 2 annotations, which share the one Link SE auto pKids = dynamic_cast(pObjectD00->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); + size_t nMCID(0); + std::vector aAnnots; for (size_t i = 0; i < pKids->GetElements().size(); ++i) { auto pNum = dynamic_cast(pKids->GetElement(i)); @@ -3151,49 +3315,53 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) } if (pObjR) { - ++nRef; auto pOType = dynamic_cast(pObjR->LookupElement("Type"_ostr)); CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); auto pAnnotRef = dynamic_cast( pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"Firefox119"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - // this changed to the end of the text, not the start of the fly - CPPUNIT_ASSERT_DOUBLES_EQUAL(191.657, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(785.189, pNumB->GetValue(), 1e-3); + CPPUNIT_ASSERT(pAnnotRef); + aAnnots.push_back(pAnnotRef->LookupObject()); } } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); + CPPUNIT_ASSERT_EQUAL(size_t(2), nMCID); + CPPUNIT_ASSERT_EQUAL(size_t(2), aAnnots.size()); + + auto CheckAnnot = [](vcl::filter::PDFObjectElement* pAnnot, double fLeft, double fRight) { + CPPUNIT_ASSERT(pAnnot); + auto pAType = dynamic_cast(pAnnot->Lookup("Type"_ostr)); + CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); + auto pASubtype + = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); + CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); + auto pAContents + = dynamic_cast(pAnnot->Lookup("Contents"_ostr)); + CPPUNIT_ASSERT_EQUAL(u"Firefox119"_ustr, + vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); + auto pStructParent + = dynamic_cast(pAnnot->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(pStructParent); // every link must have it! + auto pARect = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); + CPPUNIT_ASSERT(pARect); + const auto& rElements = pARect->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); + const auto* pNumL = dynamic_cast(rElements[0]); + CPPUNIT_ASSERT(pNumL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fLeft, pNumL->GetValue(), 1e-3); + const auto* pNumT = dynamic_cast(rElements[1]); + CPPUNIT_ASSERT(pNumT); + CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumT->GetValue(), 1e-3); + const auto* pNumR = dynamic_cast(rElements[2]); + CPPUNIT_ASSERT(pNumR); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fRight, pNumR->GetValue(), 1e-3); + const auto* pNumB = dynamic_cast(rElements[3]); + CPPUNIT_ASSERT(pNumB); + CPPUNIT_ASSERT_DOUBLES_EQUAL(785.189, pNumB->GetValue(), 1e-3); + }; + CheckDistinct(aAnnots[0], aAnnots[1]); + // the first rect ends at the end of the text, not at the start of the fly + CheckAnnot(aAnnots[0], 56.693, 191.657); + CheckAnnot(aAnnots[1], 387.843, 534.407); } auto pRefKidD01 = dynamic_cast(pKidsD0v[1]); @@ -3203,74 +3371,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) auto pTypeD01 = dynamic_cast(pObjectD01->Lookup("Type"_ostr)); CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD01->GetValue()); auto pSD01 = dynamic_cast(pObjectD01->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD01->GetValue()); - { - auto pKids = dynamic_cast(pObjectD01->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); - for (size_t i = 0; i < pKids->GetElements().size(); ++i) - { - auto pNum = dynamic_cast(pKids->GetElement(i)); - auto pObjR = dynamic_cast(pKids->GetElement(i)); - if (pNum) - { - ++nMCID; - } - if (pObjR) - { - ++nRef; - auto pOType - = dynamic_cast(pObjR->LookupElement("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); - auto pAnnotRef = dynamic_cast( - pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"Firefox119"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(387.843, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - // this changed to the end of the text, not the start of the fly - CPPUNIT_ASSERT_DOUBLES_EQUAL(534.407, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(785.189, pNumB->GetValue(), 1e-3); - } - } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); - } - - auto pRefKidD02 = dynamic_cast(pKidsD0v[2]); - CPPUNIT_ASSERT(pRefKidD02); - auto pObjectD02 = pRefKidD02->LookupObject(); - CPPUNIT_ASSERT(pObjectD02); - auto pTypeD02 = dynamic_cast(pObjectD02->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD02->GetValue()); - auto pSD02 = dynamic_cast(pObjectD02->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Figure"_ostr, pSD02->GetValue()); + CPPUNIT_ASSERT_EQUAL("Figure"_ostr, pSD01->GetValue()); auto pRefKidD1 = dynamic_cast(pKidsDv[1]); CPPUNIT_ASSERT(pRefKidD1); @@ -3284,6 +3385,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) auto pKidsD1 = dynamic_cast(pObjectD1->Lookup("K"_ostr)); CPPUNIT_ASSERT(pKidsD1); auto pKidsD1v = pKidsD1->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(1), pKidsD1v.size()); auto pRefKidD10 = dynamic_cast(pKidsD1v[0]); CPPUNIT_ASSERT(pRefKidD10); @@ -3294,9 +3396,10 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) auto pSD10 = dynamic_cast(pObjectD10->Lookup("S"_ostr)); CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD10->GetValue()); { + // the fly splits this link too auto pKids = dynamic_cast(pObjectD10->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); + size_t nMCID(0); + std::vector aAnnots; for (size_t i = 0; i < pKids->GetElements().size(); ++i) { auto pNum = dynamic_cast(pKids->GetElement(i)); @@ -3307,116 +3410,53 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf157816Link) } if (pObjR) { - ++nRef; auto pOType = dynamic_cast(pObjR->LookupElement("Type"_ostr)); CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); auto pAnnotRef = dynamic_cast( pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"Firefox118"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(757.589, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - // this changed to the end of the text, not the start of the fly - CPPUNIT_ASSERT_DOUBLES_EQUAL(191.657, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumB->GetValue(), 1e-3); + CPPUNIT_ASSERT(pAnnotRef); + aAnnots.push_back(pAnnotRef->LookupObject()); } } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); - } + CPPUNIT_ASSERT_EQUAL(size_t(2), nMCID); + CPPUNIT_ASSERT_EQUAL(size_t(2), aAnnots.size()); - auto pRefKidD11 = dynamic_cast(pKidsD1v[1]); - CPPUNIT_ASSERT(pRefKidD11); - auto pObjectD11 = pRefKidD11->LookupObject(); - CPPUNIT_ASSERT(pObjectD11); - auto pTypeD11 = dynamic_cast(pObjectD11->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD11->GetValue()); - auto pSD11 = dynamic_cast(pObjectD11->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD11->GetValue()); - { - auto pKids = dynamic_cast(pObjectD11->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); - for (size_t i = 0; i < pKids->GetElements().size(); ++i) - { - auto pNum = dynamic_cast(pKids->GetElement(i)); - auto pObjR = dynamic_cast(pKids->GetElement(i)); - if (pNum) - { - ++nMCID; - } - if (pObjR) - { - ++nRef; - auto pOType - = dynamic_cast(pObjR->LookupElement("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); - auto pAnnotRef = dynamic_cast( - pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"Firefox118"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(387.843, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(757.589, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - // this changed to the end of the text, not the start of the fly - CPPUNIT_ASSERT_DOUBLES_EQUAL(534.407, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumB->GetValue(), 1e-3); - } - } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); + auto CheckAnnot = [](vcl::filter::PDFObjectElement* pAnnot, double fLeft, double fRight) { + CPPUNIT_ASSERT(pAnnot); + auto pAType = dynamic_cast(pAnnot->Lookup("Type"_ostr)); + CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); + auto pASubtype + = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); + CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); + auto pAContents + = dynamic_cast(pAnnot->Lookup("Contents"_ostr)); + CPPUNIT_ASSERT_EQUAL(u"Firefox118"_ustr, + vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); + auto pStructParent + = dynamic_cast(pAnnot->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(pStructParent); // every link must have it! + auto pARect = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); + CPPUNIT_ASSERT(pARect); + const auto& rElements = pARect->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); + const auto* pNumL = dynamic_cast(rElements[0]); + CPPUNIT_ASSERT(pNumL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fLeft, pNumL->GetValue(), 1e-3); + const auto* pNumT = dynamic_cast(rElements[1]); + CPPUNIT_ASSERT(pNumT); + CPPUNIT_ASSERT_DOUBLES_EQUAL(757.589, pNumT->GetValue(), 1e-3); + const auto* pNumR = dynamic_cast(rElements[2]); + CPPUNIT_ASSERT(pNumR); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fRight, pNumR->GetValue(), 1e-3); + const auto* pNumB = dynamic_cast(rElements[3]); + CPPUNIT_ASSERT(pNumB); + CPPUNIT_ASSERT_DOUBLES_EQUAL(771.389, pNumB->GetValue(), 1e-3); + }; + CheckDistinct(aAnnots[0], aAnnots[1]); + // the first rect ends at the end of the text, not at the start of the fly + CheckAnnot(aAnnots[0], 56.693, 191.657); + CheckAnnot(aAnnots[1], 387.843, 534.407); } // the problem was that in addition to the 4 links with SE there was 1 more @@ -3518,19 +3558,44 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) auto pKidsD0 = dynamic_cast(pObjectD0->Lookup("K"_ostr)); CPPUNIT_ASSERT(pKidsD0); auto pKidsD0v = pKidsD0->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(2), pKidsD0v.size()); - auto pRefKidD00 = dynamic_cast(pKidsD0v[0]); - CPPUNIT_ASSERT(pRefKidD00); - auto pObjectD00 = pRefKidD00->LookupObject(); - CPPUNIT_ASSERT(pObjectD00); - auto pTypeD00 = dynamic_cast(pObjectD00->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD00->GetValue()); - auto pSD00 = dynamic_cast(pObjectD00->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD00->GetValue()); - { - auto pKids = dynamic_cast(pObjectD00->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); + auto CheckAnnot = [](vcl::filter::PDFObjectElement* pAnnot, double fLeft, double fTop, + double fRight, double fBottom) { + CPPUNIT_ASSERT(pAnnot); + auto pAType = dynamic_cast(pAnnot->Lookup("Type"_ostr)); + CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); + auto pASubtype = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); + CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); + auto pAContents + = dynamic_cast(pAnnot->Lookup("Contents"_ostr)); + CPPUNIT_ASSERT_EQUAL(u"quadfoo"_ustr, + vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); + auto pStructParent + = dynamic_cast(pAnnot->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(pStructParent); // every link must have it! + auto pARect = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); + CPPUNIT_ASSERT(pARect); + const auto& rElements = pARect->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); + const auto* pNumL = dynamic_cast(rElements[0]); + CPPUNIT_ASSERT(pNumL); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fLeft, pNumL->GetValue(), 1e-3); + const auto* pNumT = dynamic_cast(rElements[1]); + CPPUNIT_ASSERT(pNumT); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fTop, pNumT->GetValue(), 1e-3); + const auto* pNumR = dynamic_cast(rElements[2]); + CPPUNIT_ASSERT(pNumR); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fRight, pNumR->GetValue(), 1e-3); + const auto* pNumB = dynamic_cast(rElements[3]); + CPPUNIT_ASSERT(pNumB); + CPPUNIT_ASSERT_DOUBLES_EQUAL(fBottom, pNumB->GetValue(), 1e-3); + }; + auto GetLinkAnnots = [](vcl::filter::PDFObjectElement* pLinkSE) { + auto pKids = dynamic_cast(pLinkSE->Lookup("K"_ostr)); + CPPUNIT_ASSERT(pKids); + size_t nMCID(0); + std::vector aAnnots; for (size_t i = 0; i < pKids->GetElements().size(); ++i) { auto pNum = dynamic_cast(pKids->GetElement(i)); @@ -3541,48 +3606,42 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) } if (pObjR) { - ++nRef; auto pOType = dynamic_cast(pObjR->LookupElement("Type"_ostr)); CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); auto pAnnotRef = dynamic_cast( pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"quadfoo"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(240.455, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - CPPUNIT_ASSERT_DOUBLES_EQUAL(241.007, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(350.855, pNumB->GetValue(), 1e-3); + CPPUNIT_ASSERT(pAnnotRef); + aAnnots.push_back(pAnnotRef->LookupObject()); } } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); + CPPUNIT_ASSERT_EQUAL(size_t(2), nMCID); + CPPUNIT_ASSERT_EQUAL(size_t(2), aAnnots.size()); + // the 2 annotations cannot share one parent tree entry + auto pStructParent0 + = dynamic_cast(aAnnots[0]->Lookup("StructParent"_ostr)); + auto pStructParent1 + = dynamic_cast(aAnnots[1]->Lookup("StructParent"_ostr)); + CPPUNIT_ASSERT(pStructParent0); + CPPUNIT_ASSERT(pStructParent1); + CPPUNIT_ASSERT(pStructParent0->GetValue() != pStructParent1->GetValue()); + return aAnnots; + }; + + // the link is split over 2 frames, and broken over 2 lines in each of them, + // which gives one annotation per line but only one Link SE per frame + auto pRefKidD00 = dynamic_cast(pKidsD0v[0]); + CPPUNIT_ASSERT(pRefKidD00); + auto pObjectD00 = pRefKidD00->LookupObject(); + CPPUNIT_ASSERT(pObjectD00); + auto pTypeD00 = dynamic_cast(pObjectD00->Lookup("Type"_ostr)); + CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD00->GetValue()); + auto pSD00 = dynamic_cast(pObjectD00->Lookup("S"_ostr)); + CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD00->GetValue()); + { + auto aAnnots = GetLinkAnnots(pObjectD00); + CheckAnnot(aAnnots[0], 56.693, 240.455, 241.007, 350.855); + CheckAnnot(aAnnots[1], 56.643, 130.055, 241.007, 240.455); } auto pRefKidD01 = dynamic_cast(pKidsD0v[1]); @@ -3594,194 +3653,11 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) auto pSD01 = dynamic_cast(pObjectD01->Lookup("S"_ostr)); CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD01->GetValue()); { - auto pKids = dynamic_cast(pObjectD01->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); - for (size_t i = 0; i < pKids->GetElements().size(); ++i) - { - auto pNum = dynamic_cast(pKids->GetElement(i)); - auto pObjR = dynamic_cast(pKids->GetElement(i)); - if (pNum) - { - ++nMCID; - } - if (pObjR) - { - ++nRef; - auto pOType - = dynamic_cast(pObjR->LookupElement("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); - auto pAnnotRef = dynamic_cast( - pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"quadfoo"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(130.055, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - CPPUNIT_ASSERT_DOUBLES_EQUAL(241.007, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(240.455, pNumB->GetValue(), 1e-3); - } - } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); + auto aAnnots = GetLinkAnnots(pObjectD01); + CheckAnnot(aAnnots[0], 56.643, 252.455, 241.007, 362.855); + CheckAnnot(aAnnots[1], 56.643, 142.055, 206.007, 252.455); } - auto pRefKidD02 = dynamic_cast(pKidsD0v[2]); - CPPUNIT_ASSERT(pRefKidD02); - auto pObjectD02 = pRefKidD02->LookupObject(); - CPPUNIT_ASSERT(pObjectD02); - auto pTypeD02 = dynamic_cast(pObjectD02->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD02->GetValue()); - auto pSD02 = dynamic_cast(pObjectD02->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD02->GetValue()); - { - auto pKids = dynamic_cast(pObjectD02->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); - for (size_t i = 0; i < pKids->GetElements().size(); ++i) - { - auto pNum = dynamic_cast(pKids->GetElement(i)); - auto pObjR = dynamic_cast(pKids->GetElement(i)); - if (pNum) - { - ++nMCID; - } - if (pObjR) - { - ++nRef; - auto pOType - = dynamic_cast(pObjR->LookupElement("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); - auto pAnnotRef = dynamic_cast( - pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"quadfoo"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(252.455, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - CPPUNIT_ASSERT_DOUBLES_EQUAL(241.007, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(362.855, pNumB->GetValue(), 1e-3); - } - } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); - } - - auto pRefKidD03 = dynamic_cast(pKidsD0v[3]); - CPPUNIT_ASSERT(pRefKidD03); - auto pObjectD03 = pRefKidD03->LookupObject(); - CPPUNIT_ASSERT(pObjectD03); - auto pTypeD03 = dynamic_cast(pObjectD03->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pTypeD03->GetValue()); - auto pSD03 = dynamic_cast(pObjectD03->Lookup("S"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD03->GetValue()); - { - auto pKids = dynamic_cast(pObjectD03->Lookup("K"_ostr)); - auto nMCID(0); - auto nRef(0); - for (size_t i = 0; i < pKids->GetElements().size(); ++i) - { - auto pNum = dynamic_cast(pKids->GetElement(i)); - auto pObjR = dynamic_cast(pKids->GetElement(i)); - if (pNum) - { - ++nMCID; - } - if (pObjR) - { - ++nRef; - auto pOType - = dynamic_cast(pObjR->LookupElement("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue()); - auto pAnnotRef = dynamic_cast( - pObjR->LookupElement("Obj"_ostr)); - auto pAnnot = pAnnotRef->LookupObject(); - auto pAType - = dynamic_cast(pAnnot->Lookup("Type"_ostr)); - CPPUNIT_ASSERT_EQUAL("Annot"_ostr, pAType->GetValue()); - auto pASubtype - = dynamic_cast(pAnnot->Lookup("Subtype"_ostr)); - CPPUNIT_ASSERT_EQUAL("Link"_ostr, pASubtype->GetValue()); - auto pAContents = dynamic_cast( - pAnnot->Lookup("Contents"_ostr)); - CPPUNIT_ASSERT_EQUAL( - u"quadfoo"_ustr, - ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAContents)); - auto pStructParent = dynamic_cast( - pAnnot->Lookup("StructParent"_ostr)); - CPPUNIT_ASSERT(pStructParent); // every link must have it! - auto pARect - = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); - CPPUNIT_ASSERT(pARect); - const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); - const auto* pNumL = dynamic_cast(rElements[0]); - CPPUNIT_ASSERT(pNumL); - CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3); - const auto* pNumT = dynamic_cast(rElements[1]); - CPPUNIT_ASSERT(pNumT); - CPPUNIT_ASSERT_DOUBLES_EQUAL(142.055, pNumT->GetValue(), 1e-3); - const auto* pNumR = dynamic_cast(rElements[2]); - CPPUNIT_ASSERT(pNumR); - CPPUNIT_ASSERT_DOUBLES_EQUAL(206.007, pNumR->GetValue(), 1e-3); - const auto* pNumB = dynamic_cast(rElements[3]); - CPPUNIT_ASSERT(pNumB); - CPPUNIT_ASSERT_DOUBLES_EQUAL(252.455, pNumB->GetValue(), 1e-3); - } - } - CPPUNIT_ASSERT_EQUAL(static_cast(1), nMCID); - CPPUNIT_ASSERT_EQUAL(static_cast(1), nRef); - } auto pRefKidD1 = dynamic_cast(pKidsDv[1]); CPPUNIT_ASSERT(pRefKidD1); auto pObjectD1 = pRefKidD1->LookupObject(); @@ -3794,6 +3670,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) auto pKidsD1 = dynamic_cast(pObjectD1->Lookup("K"_ostr)); CPPUNIT_ASSERT(pKidsD1); auto pKidsD1v = pKidsD1->GetElements(); + CPPUNIT_ASSERT_EQUAL(size_t(4), pKidsD1v.size()); auto pRefKidD10 = dynamic_cast(pKidsD1v[0]); CPPUNIT_ASSERT(pRefKidD10); @@ -3805,7 +3682,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD10->GetValue()); { auto pKids = dynamic_cast(pObjectD10->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -3842,7 +3719,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.693, pNumL->GetValue(), 1e-3); @@ -3871,7 +3748,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD11->GetValue()); { auto pKids = dynamic_cast(pObjectD11->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -3908,7 +3785,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3); @@ -3937,7 +3814,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD12->GetValue()); { auto pKids = dynamic_cast(pObjectD12->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -3974,7 +3851,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3); @@ -4003,7 +3880,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) CPPUNIT_ASSERT_EQUAL("Link"_ostr, pSD13->GetValue()); { auto pKids = dynamic_cast(pObjectD13->Lookup("K"_ostr)); - auto nMCID(0); + size_t nMCID(0); auto nRef(0); for (size_t i = 0; i < pKids->GetElements().size(); ++i) { @@ -4040,7 +3917,7 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142806) = dynamic_cast(pAnnot->Lookup("Rect"_ostr)); CPPUNIT_ASSERT(pARect); const auto& rElements = pARect->GetElements(); - CPPUNIT_ASSERT_EQUAL(static_cast(4), rElements.size()); + CPPUNIT_ASSERT_EQUAL(size_t(4), rElements.size()); const auto* pNumL = dynamic_cast(rElements[0]); CPPUNIT_ASSERT(pNumL); CPPUNIT_ASSERT_DOUBLES_EQUAL(56.643, pNumL->GetValue(), 1e-3); diff --git a/vcl/source/pdf/pdfwriter_impl.cxx b/vcl/source/pdf/pdfwriter_impl.cxx index 8c781ee4c1f6..2da2b01a7e89 100644 --- a/vcl/source/pdf/pdfwriter_impl.cxx +++ b/vcl/source/pdf/pdfwriter_impl.cxx @@ -940,30 +940,6 @@ OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) { appendStructureAttributeLine( attribute.first, attribute.second, aTable, false ); } - else if( attribute.first == PDFWriter::LinkAnnotation ) - { - sal_Int32 nLink = attribute.second.nValue; - std::map< sal_Int32, sal_Int32 >::const_iterator link_it = - m_aLinkPropertyMap.find( nLink ); - if( link_it != m_aLinkPropertyMap.end() ) - nLink = link_it->second; - if( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() ) - { - AppendAnnotKid(i_rEle, m_aLinks[nLink]); - } - else - { - OSL_FAIL( "unresolved link id for Link structure" ); - SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure"); - if (g_bDebugDisableCompression) - { - OString aLine = "unresolved link id " + - OString::number( nLink ) + - " for Link structure"; - emitComment( aLine.getStr() ); - } - } - } else if (attribute.first == PDFWriter::NoteAnnotation) { sal_Int32 nNote = attribute.second.nValue; @@ -1250,6 +1226,27 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) } } } + for (const auto id : rEle.m_LinkAnnotIds) + { + sal_Int32 nLink(id); + const auto it(m_aLinkPropertyMap.find(nLink)); + if (it != m_aLinkPropertyMap.end()) + nLink = it->second; + if (0 <= nLink && o3tl::make_unsigned(nLink) < m_aLinks.size()) + { + AppendAnnotKid(rEle, m_aLinks[nLink]); + } + else + { + SAL_WARN("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure"); + if (g_bDebugDisableCompression) + { + const OString aComment + = "unresolved link id " + OString::number(nLink) + " for Link structure"; + emitComment(aComment.getStr()); + } + } + } if( ! rEle.m_aKids.empty() ) { unsigned int i = 0; @@ -10997,8 +10994,18 @@ bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttrib } } - if( bInsert ) - m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue ); + if (bInsert) + { + if (eAttr == PDFWriter::LinkAnnotation) + { + m_aStructure[m_nCurrentStructElement].m_LinkAnnotIds.push_back(nValue); + } + else + { + m_aStructure[m_nCurrentStructElement].m_aAttributes[eAttr] + = PDFStructureAttribute(nValue); + } + } else if( m_nCurrentStructElement > 0 && m_bEmitStructure ) SAL_INFO("vcl.pdfwriter", "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )