drawinglayer/source/primitive2d/structuretagprimitive2d.cxx | 7 drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx | 5 include/drawinglayer/primitive2d/structuretagprimitive2d.hxx | 6 include/svx/sdr/contact/objectcontact.hxx | 5 include/svx/sdr/contact/objectcontactofpageview.hxx | 1 include/vcl/pdfextoutdevdata.hxx | 14 + include/vcl/pdfwriter.hxx | 7 sd/source/ui/unoidl/unomodel.cxx | 3 svx/inc/sdr/contact/objectcontactofobjlistpainter.hxx | 1 svx/source/sdr/contact/objectcontact.cxx | 5 svx/source/sdr/contact/objectcontactofobjlistpainter.cxx | 11 svx/source/sdr/contact/objectcontactofpageview.cxx | 11 svx/source/sdr/contact/viewobjectcontact.cxx | 14 + svx/source/sdr/contact/viewobjectcontactofpageobj.cxx | 4 sw/source/core/text/EnhancedPDFExportHelper.cxx | 2 vcl/inc/pdf/pdfwriter_impl.hxx | 7 vcl/qa/cppunit/pdfexport/data/vid.odt |binary vcl/qa/cppunit/pdfexport/pdfexport.cxx | 130 +++++++++++ vcl/source/gdi/pdfextoutdevdata.cxx | 44 +++ vcl/source/gdi/pdfwriter.cxx | 5 vcl/source/gdi/pdfwriter_impl.cxx | 85 +++++-- 21 files changed, 336 insertions(+), 31 deletions(-)
New commits: commit e84b310b59825fd572d79def98c3d21566aac603 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Thu Mar 16 19:48:31 2023 +0100 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Mar 22 11:53:38 2023 +0000 vcl,drawinglayer,svx,sw,sd: PDF/UA export: Annot StructElem for SdrMediaObj veraPDF complains: Specification: ISO 14289-1:2014, Clause: 7.18.1, Test number: 1 An annotation, excluding annotations of subtype Widget, Popup or Link, shall be nested within an Annot tag This is very similar to Link annotations, that is to say, extremely complicated to get it thought the convoluted PDF export code, with additional complication that the StructElem is produced by drawinglayer and the page annotation by sw. Put another map into PDFExtOutDevData where sw code puts stuff for the SdrObject that drawinglayer can find. The test had the problem that PDFObjectParser::parse() could not handle: <</Nums[ 0 [ 6 0 R ] 1 6 0 R ]>> Fix dropping the "1". Change-Id: If5bf7c552e26ebb7e631030b8aaecd4281e77acc (cherry picked from commit c78e90bd28cc4d6d3bde473535107784b12d9c0d) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/149008 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx b/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx index 7e4de87ed64a..62da91ecc00f 100644 --- a/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx +++ b/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx @@ -31,13 +31,18 @@ namespace drawinglayer::primitive2d bool bBackground, bool bIsImage, Primitive2DContainer&& aChildren, - sal_Int32 const nAnchorStructureElementId) + sal_Int32 const nAnchorStructureElementId, + ::std::vector<sal_Int32> const*const pAnnotIds) : GroupPrimitive2D(std::move(aChildren)), maStructureElement(rStructureElement), mbBackground(bBackground), mbIsImage(bIsImage) , m_nAnchorStructureElementId(nAnchorStructureElementId) { + if (pAnnotIds) + { + m_AnnotIds = *pAnnotIds; + } } bool StructureTagPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx index d75b67716de8..da99da610883 100644 --- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx @@ -2460,6 +2460,7 @@ void VclMetafileProcessor2D::processStructureTagPrimitive2D( case vcl::PDFWriter::Table: case vcl::PDFWriter::Formula: case vcl::PDFWriter::Figure: + case vcl::PDFWriter::Annot: { auto const range(rStructureTagCandidate.getB2DRange(getViewInformation2D())); tools::Rectangle const aLogicRect( @@ -2471,6 +2472,10 @@ void VclMetafileProcessor2D::processStructureTagPrimitive2D( default: break; } + if (rTagElement == vcl::PDFWriter::Annot) + { + mpPDFExtOutDevData->SetStructureAnnotIds(rStructureTagCandidate.GetAnnotIds()); + } if (rTagElement == vcl::PDFWriter::TableHeader) { mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope, diff --git a/include/drawinglayer/primitive2d/structuretagprimitive2d.hxx b/include/drawinglayer/primitive2d/structuretagprimitive2d.hxx index 40ad422c2b9a..8ee2b267b57f 100644 --- a/include/drawinglayer/primitive2d/structuretagprimitive2d.hxx +++ b/include/drawinglayer/primitive2d/structuretagprimitive2d.hxx @@ -51,6 +51,8 @@ namespace drawinglayer::primitive2d bool mbIsImage; /// anchor structure element (Writer) sal_Int32 m_nAnchorStructureElementId; + /// for Annot structure element, the ids of the annotations + ::std::vector<sal_Int32> m_AnnotIds; public: /// constructor @@ -59,7 +61,8 @@ namespace drawinglayer::primitive2d bool bBackground, bool bIsImage, Primitive2DContainer&& aChildren, - sal_Int32 nAnchorStructureElementId = -1); + sal_Int32 nAnchorStructureElementId = -1, + ::std::vector<sal_Int32> const* pAnnotIds = nullptr); /// data read access const vcl::PDFWriter::StructElement& getStructureElement() const { return maStructureElement; } @@ -67,6 +70,7 @@ namespace drawinglayer::primitive2d bool isImage() const { return mbIsImage; } bool isTaggedSdrObject() const; sal_Int32 GetAnchorStructureElementId() const { return m_nAnchorStructureElementId; } + ::std::vector<sal_Int32> GetAnnotIds() const { return m_AnnotIds; } /// compare operator virtual bool operator==(const BasePrimitive2D& rPrimitive) const override; diff --git a/include/svx/sdr/contact/objectcontact.hxx b/include/svx/sdr/contact/objectcontact.hxx index 3e9b1b76d05d..40d631f3fae8 100644 --- a/include/svx/sdr/contact/objectcontact.hxx +++ b/include/svx/sdr/contact/objectcontact.hxx @@ -30,6 +30,10 @@ namespace tools { class Rectangle; } class SdrPageView; class OutputDevice; +namespace vcl { + class PDFExtOutDevData; +} + namespace basegfx { class B2DRange; class B2DHomMatrix; @@ -138,6 +142,7 @@ public: // pdf export? Default is false virtual bool isOutputToPDFFile() const; virtual bool isExportTaggedPDF() const; + virtual ::vcl::PDFExtOutDevData const* GetPDFExtOutDevData() const; // gray display mode virtual bool isDrawModeGray() const; diff --git a/include/svx/sdr/contact/objectcontactofpageview.hxx b/include/svx/sdr/contact/objectcontactofpageview.hxx index 8d18083b99b8..1512542dc395 100644 --- a/include/svx/sdr/contact/objectcontactofpageview.hxx +++ b/include/svx/sdr/contact/objectcontactofpageview.hxx @@ -93,6 +93,7 @@ namespace sdr::contact // pdf export? Default is false virtual bool isOutputToPDFFile() const override; virtual bool isExportTaggedPDF() const override; + virtual ::vcl::PDFExtOutDevData const* GetPDFExtOutDevData() const override; // gray display mode virtual bool isDrawModeGray() const override; diff --git a/include/vcl/pdfextoutdevdata.hxx b/include/vcl/pdfextoutdevdata.hxx index 81ea37b86d9b..2f0360d1fdd5 100644 --- a/include/vcl/pdfextoutdevdata.hxx +++ b/include/vcl/pdfextoutdevdata.hxx @@ -25,10 +25,12 @@ #include <vcl/pdfwriter.hxx> #include <vcl/extoutdevdata.hxx> #include <vector> +#include <map> #include <memory> class Graphic; class GDIMetaFile; +class SdrObject; namespace vcl { @@ -91,6 +93,8 @@ class VCL_DLLPUBLIC PDFExtOutDevData final : public ExtOutDevData std::vector< PDFExtOutDevBookmarkEntry > maBookmarks; std::vector<OUString> maChapterNames; + // map from annotation SdrObject to annotation index + ::std::map<SdrObject const*, ::std::vector<sal_Int32>> m_ScreenAnnotations; public: @@ -266,7 +270,10 @@ public: sal_Int32 CreateLink(const tools::Rectangle& rRect, OUString const& rAltText, sal_Int32 nPageNr = -1); /// Create a Screen annotation. - sal_Int32 CreateScreen(const tools::Rectangle& rRect, OUString const& rAltText, sal_Int32 nPageNr); + sal_Int32 CreateScreen(const tools::Rectangle& rRect, OUString const& rAltText, sal_Int32 nPageNr, SdrObject const* pObj); + + /// Get back the annotations created for one SdrObject. + ::std::vector<sal_Int32> const& GetScreenAnnotIds(SdrObject const* pObj) const; /** Set the destination for a link <p>will change a URL type link to a dest link if necessary</p> @@ -438,6 +445,11 @@ public: */ void SetStructureBoundingBox( const tools::Rectangle& rRect ); + /** set the annotations that should be referenced as children of the + current structural element. + */ + void SetStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds); + /** set the ActualText attribute of a structural element ActualText contains the Unicode text without layout artifacts that is shown by diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx index 1319909961a9..b16e7120b398 100644 --- a/include/vcl/pdfwriter.hxx +++ b/include/vcl/pdfwriter.hxx @@ -127,7 +127,7 @@ public: Table, TableRow, TableHeader, TableData, // inline level elements - Span, Quote, Note, Reference, BibEntry, Code, Link, + Span, Quote, Note, Reference, BibEntry, Code, Link, Annot, // illustration elements Figure, Formula, Form @@ -1147,6 +1147,11 @@ The following structure describes the permissions used in PDF security */ void SetStructureBoundingBox( const tools::Rectangle& rRect ); + /** set the annotations that should be referenced as children of the + current structural element. + */ + void SetStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds); + /** set the ActualText attribute of a structural element ActualText contains the Unicode text without layout artifacts that is shown by diff --git a/sd/source/ui/unoidl/unomodel.cxx b/sd/source/ui/unoidl/unomodel.cxx index 3a89e7728e89..bab827c6be5f 100644 --- a/sd/source/ui/unoidl/unomodel.cxx +++ b/sd/source/ui/unoidl/unomodel.cxx @@ -1658,7 +1658,8 @@ static void ImplPDFExportShapeInteraction( const uno::Reference< drawing::XShape xShapePropSet->getPropertyValue("MediaURL") >>= aMediaURL; if (!aMediaURL.isEmpty()) { - sal_Int32 nScreenId = rPDFExtOutDevData.CreateScreen(aLinkRect, altText, rPDFExtOutDevData.GetCurrentPageNumber()); + SdrObject const*const pSdrObj(SdrObject::getSdrObjectFromXShape(xShape)); + sal_Int32 nScreenId = rPDFExtOutDevData.CreateScreen(aLinkRect, altText, rPDFExtOutDevData.GetCurrentPageNumber(), pSdrObj); if (aMediaURL.startsWith("vnd.sun.star.Package:")) { OUString aTempFileURL; diff --git a/svx/inc/sdr/contact/objectcontactofobjlistpainter.hxx b/svx/inc/sdr/contact/objectcontactofobjlistpainter.hxx index af72593adef2..c977e04a9f69 100644 --- a/svx/inc/sdr/contact/objectcontactofobjlistpainter.hxx +++ b/svx/inc/sdr/contact/objectcontactofobjlistpainter.hxx @@ -78,6 +78,7 @@ public: // pdf export? Default is false virtual bool isOutputToPDFFile() const override; virtual bool isExportTaggedPDF() const override; + virtual ::vcl::PDFExtOutDevData const* GetPDFExtOutDevData() const override; virtual OutputDevice* TryToGetOutputDevice() const override; }; diff --git a/svx/source/sdr/contact/objectcontact.cxx b/svx/source/sdr/contact/objectcontact.cxx index d135a2a29336..4555068ccf01 100644 --- a/svx/source/sdr/contact/objectcontact.cxx +++ b/svx/source/sdr/contact/objectcontact.cxx @@ -176,6 +176,11 @@ bool ObjectContact::isExportTaggedPDF() const return false; } +::vcl::PDFExtOutDevData const* ObjectContact::GetPDFExtOutDevData() const +{ + return nullptr; +} + // gray display mode bool ObjectContact::isDrawModeGray() const { diff --git a/svx/source/sdr/contact/objectcontactofobjlistpainter.cxx b/svx/source/sdr/contact/objectcontactofobjlistpainter.cxx index b4727ce30b12..d20b1426e63b 100644 --- a/svx/source/sdr/contact/objectcontactofobjlistpainter.cxx +++ b/svx/source/sdr/contact/objectcontactofobjlistpainter.cxx @@ -152,6 +152,17 @@ bool ObjectContactOfObjListPainter::isExportTaggedPDF() const return false; } +::vcl::PDFExtOutDevData const* ObjectContactOfObjListPainter::GetPDFExtOutDevData() const +{ + if (!isOutputToPDFFile()) + { + return nullptr; + } + vcl::PDFExtOutDevData *const pPDFExtOutDevData( + dynamic_cast<vcl::PDFExtOutDevData*>(mrTargetOutputDevice.GetExtOutDevData())); + return pPDFExtOutDevData; +} + OutputDevice* ObjectContactOfObjListPainter::TryToGetOutputDevice() const { return &mrTargetOutputDevice; diff --git a/svx/source/sdr/contact/objectcontactofpageview.cxx b/svx/source/sdr/contact/objectcontactofpageview.cxx index 9d47bab76dd2..26c295472e0b 100644 --- a/svx/source/sdr/contact/objectcontactofpageview.cxx +++ b/svx/source/sdr/contact/objectcontactofpageview.cxx @@ -392,6 +392,17 @@ namespace sdr::contact return false; } + ::vcl::PDFExtOutDevData const* ObjectContactOfPageView::GetPDFExtOutDevData() const + { + if (!isOutputToPDFFile()) + { + return nullptr; + } + vcl::PDFExtOutDevData* pPDFExtOutDevData(dynamic_cast<vcl::PDFExtOutDevData*>( + mrPageWindow.GetPaintWindow().GetOutputDevice().GetExtOutDevData())); + return pPDFExtOutDevData; + } + // gray display mode bool ObjectContactOfPageView::isDrawModeGray() const { diff --git a/svx/source/sdr/contact/viewobjectcontact.cxx b/svx/source/sdr/contact/viewobjectcontact.cxx index cae227438061..00f30f4248c5 100644 --- a/svx/source/sdr/contact/viewobjectcontact.cxx +++ b/svx/source/sdr/contact/viewobjectcontact.cxx @@ -35,6 +35,7 @@ #include <svx/svdpage.hxx> #include <svx/svdotext.hxx> #include <vcl/pdfwriter.hxx> +#include <vcl/pdfextoutdevdata.hxx> using namespace com::sun::star; @@ -404,6 +405,8 @@ drawinglayer::primitive2d::Primitive2DContainer const & ViewObjectContact::getPr eElement = vcl::PDFWriter::Section; else if (nIdentifier == SdrObjKind::Table) eElement = vcl::PDFWriter::Table; + else if (nIdentifier == SdrObjKind::Media) + eElement = vcl::PDFWriter::Annot; else if ( nIdentifier == SdrObjKind::TitleText ) eElement = vcl::PDFWriter::Heading; else if ( nIdentifier == SdrObjKind::OutlineText ) @@ -429,13 +432,22 @@ drawinglayer::primitive2d::Primitive2DContainer const & ViewObjectContact::getPr nAnchorId = pUserCall->GetPDFAnchorStructureElementId(*pSdrObj); } + ::std::vector<sal_Int32> annotIds; + if (eElement == vcl::PDFWriter::Annot) + { + auto const pPDFExtOutDevData(GetObjectContact().GetPDFExtOutDevData()); + assert(pPDFExtOutDevData); + annotIds = pPDFExtOutDevData->GetScreenAnnotIds(pSdrObj); + } + drawinglayer::primitive2d::Primitive2DReference xReference( new drawinglayer::primitive2d::StructureTagPrimitive2D( eElement, bBackground, bImage, std::move(xNewPrimitiveSequence), - nAnchorId)); + nAnchorId, + &annotIds)); xNewPrimitiveSequence = drawinglayer::primitive2d::Primitive2DContainer { xReference }; } } diff --git a/svx/source/sdr/contact/viewobjectcontactofpageobj.cxx b/svx/source/sdr/contact/viewobjectcontactofpageobj.cxx index fb60e6b55965..d66d3852791f 100644 --- a/svx/source/sdr/contact/viewobjectcontactofpageobj.cxx +++ b/svx/source/sdr/contact/viewobjectcontactofpageobj.cxx @@ -67,6 +67,8 @@ public: virtual bool isOutputToPrinter() const override; virtual bool isOutputToRecordingMetaFile() const override; virtual bool isOutputToPDFFile() const override; + virtual bool isExportTaggedPDF() const override; + virtual ::vcl::PDFExtOutDevData const* GetPDFExtOutDevData() const override; virtual bool isDrawModeGray() const override; virtual bool isDrawModeHighContrast() const override; virtual SdrPageView* TryToGetSdrPageView() const override; @@ -177,6 +179,8 @@ void PagePrimitiveExtractor::InvalidatePartOfView(const basegfx::B2DRange& rRang bool PagePrimitiveExtractor::isOutputToPrinter() const { return mrViewObjectContactOfPageObj.GetObjectContact().isOutputToPrinter(); } bool PagePrimitiveExtractor::isOutputToRecordingMetaFile() const { return mrViewObjectContactOfPageObj.GetObjectContact().isOutputToRecordingMetaFile(); } bool PagePrimitiveExtractor::isOutputToPDFFile() const { return mrViewObjectContactOfPageObj.GetObjectContact().isOutputToPDFFile(); } +bool PagePrimitiveExtractor::isExportTaggedPDF() const { return mrViewObjectContactOfPageObj.GetObjectContact().isExportTaggedPDF(); } +::vcl::PDFExtOutDevData const* PagePrimitiveExtractor::GetPDFExtOutDevData() const { return mrViewObjectContactOfPageObj.GetObjectContact().GetPDFExtOutDevData(); } bool PagePrimitiveExtractor::isDrawModeGray() const { return mrViewObjectContactOfPageObj.GetObjectContact().isDrawModeGray(); } bool PagePrimitiveExtractor::isDrawModeHighContrast() const { return mrViewObjectContactOfPageObj.GetObjectContact().isDrawModeHighContrast(); } SdrPageView* PagePrimitiveExtractor::TryToGetSdrPageView() const { return mrViewObjectContactOfPageObj.GetObjectContact().TryToGetSdrPageView(); } diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx b/sw/source/core/text/EnhancedPDFExportHelper.cxx index 7c518f8b4594..26970499ca40 100644 --- a/sw/source/core/text/EnhancedPDFExportHelper.cxx +++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx @@ -2040,7 +2040,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport() tools::Rectangle aPDFRect(SwRectToPDFRect(pCurrPage, aSnapRect.SVRect())); for (sal_Int32 nScreenPageNum : aScreenPageNums) { - sal_Int32 nScreenId = pPDFExtOutDevData->CreateScreen(aPDFRect, altText, nScreenPageNum); + sal_Int32 nScreenId = pPDFExtOutDevData->CreateScreen(aPDFRect, altText, nScreenPageNum, pObject); if (aMediaURL.startsWith("vnd.sun.star.Package:")) { // Embedded media. diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx index cc1c23a974a2..bb48f7c08e9f 100644 --- a/vcl/inc/pdf/pdfwriter_impl.hxx +++ b/vcl/inc/pdf/pdfwriter_impl.hxx @@ -479,10 +479,12 @@ struct PDFScreen : public PDFAnnotation sal_Int32 m_nTempFileObject; /// alternative text description OUString m_AltText; + sal_Int32 m_nStructParent; PDFScreen(OUString const& rAltText) : m_nTempFileObject(0) , m_AltText(rAltText) + , m_nStructParent(-1) { } }; @@ -586,6 +588,7 @@ struct PDFStructureElement std::list< PDFStructureElementKid > m_aKids; std::map<PDFWriter::StructAttribute, PDFStructureAttribute > m_aAttributes; + ::std::vector<sal_Int32> m_AnnotIds; tools::Rectangle m_aBBox; OUString m_aActualText; OUString m_aAltText; @@ -687,7 +690,7 @@ class PDFWriterImpl final : public VirtualDevice, public PDFObjectContainer public: friend struct vcl::pdf::PDFPage; - static const char* getStructureTag( PDFWriter::StructElement ); + const char* getStructureTag( PDFWriter::StructElement ); static const char* getAttributeTag( PDFWriter::StructAttribute eAtr ); static const char* getAttributeValueTag( PDFWriter::StructAttributeValue eVal ); @@ -986,6 +989,7 @@ i12626 sal_Int32 emitNamedDestinations();//i56629 // writes outline dict and tree sal_Int32 emitOutline(); + template<typename T> void AppendAnnotKid(PDFStructureElement& i_rEle, T & rAnnot); // puts the attribute objects of a structure element into the returned string, // helper for emitStructure OString emitStructureAttributes( PDFStructureElement& rEle ); @@ -1329,6 +1333,7 @@ public: bool setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal ); bool setStructureAttributeNumerical( enum PDFWriter::StructAttribute eAttr, sal_Int32 nValue ); void setStructureBoundingBox( const tools::Rectangle& rRect ); + void setStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds); void setActualText( const OUString& rText ); void setAlternateText( const OUString& rText ); diff --git a/vcl/qa/cppunit/pdfexport/data/vid.odt b/vcl/qa/cppunit/pdfexport/data/vid.odt new file mode 100644 index 000000000000..730358e261fa Binary files /dev/null and b/vcl/qa/cppunit/pdfexport/data/vid.odt differ diff --git a/vcl/qa/cppunit/pdfexport/pdfexport.cxx b/vcl/qa/cppunit/pdfexport/pdfexport.cxx index d04f3e98f75f..4304846c9834 100644 --- a/vcl/qa/cppunit/pdfexport/pdfexport.cxx +++ b/vcl/qa/cppunit/pdfexport/pdfexport.cxx @@ -3567,6 +3567,136 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf135192) CPPUNIT_ASSERT_EQUAL(int(1), nTable); } +CPPUNIT_TEST_FIXTURE(PdfExportTest, testMediaShapeAnnot) +{ + aMediaDescriptor["FilterName"] <<= OUString("writer_pdf_Export"); + + // Enable PDF/UA + uno::Sequence<beans::PropertyValue> aFilterData( + comphelper::InitPropertySequence({ { "PDFUACompliance", uno::Any(true) } })); + aMediaDescriptor["FilterData"] <<= aFilterData; + + saveAsPDF(u"vid.odt"); + + vcl::filter::PDFDocument aDocument; + SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ); + CPPUNIT_ASSERT(aDocument.Read(aStream)); + + // The document has one page. + std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aPages.size()); + + auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots")); + CPPUNIT_ASSERT(pAnnots); + + // There should be one annotation + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pAnnots->GetElements().size()); + auto pAnnotReference + = dynamic_cast<vcl::filter::PDFReferenceElement*>(pAnnots->GetElements()[0]); + CPPUNIT_ASSERT(pAnnotReference); + // check /Annot - produced by sw + vcl::filter::PDFObjectElement* pAnnot = pAnnotReference->LookupObject(); + CPPUNIT_ASSERT(pAnnot); + CPPUNIT_ASSERT_EQUAL( + OString("Annot"), + static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Type"))->GetValue()); + CPPUNIT_ASSERT_EQUAL( + OString("Screen"), + static_cast<vcl::filter::PDFNameElement*>(pAnnot->Lookup("Subtype"))->GetValue()); + + auto pA = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pAnnot->Lookup("A")); + CPPUNIT_ASSERT(pA); + auto pR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pA->LookupElement("R")); + CPPUNIT_ASSERT(pR); + auto pC = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pR->LookupElement("C")); + CPPUNIT_ASSERT(pC); + auto pAlt = dynamic_cast<vcl::filter::PDFArrayElement*>(pC->LookupElement("Alt")); + CPPUNIT_ASSERT(pAlt); + auto pLang = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pAlt->GetElement(0)); + CPPUNIT_ASSERT_EQUAL(OString(""), pLang->GetValue()); + auto pAltText = dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pAlt->GetElement(1)); + CPPUNIT_ASSERT_EQUAL(OString("alternativloser text\\nand some description"), + pAltText->GetValue()); + + auto pStructParent + = dynamic_cast<vcl::filter::PDFNumberElement*>(pAnnot->Lookup("StructParent")); + CPPUNIT_ASSERT(pStructParent); + + vcl::filter::PDFReferenceElement* pStructElemRef(nullptr); + + // check ParentTree to find StructElem + auto nRoots(0); + for (const auto& rDocElement : aDocument.GetElements()) + { + auto pObject1 = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get()); + if (!pObject1) + continue; + auto pType1 = dynamic_cast<vcl::filter::PDFNameElement*>(pObject1->Lookup("Type")); + if (pType1 && pType1->GetValue() == "StructTreeRoot") + { + ++nRoots; + auto pParentTree + = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObject1->Lookup("ParentTree")); + CPPUNIT_ASSERT(pParentTree); + auto pNumTree = pParentTree->LookupObject(); + CPPUNIT_ASSERT(pNumTree); + auto pNums = dynamic_cast<vcl::filter::PDFArrayElement*>(pNumTree->Lookup("Nums")); + CPPUNIT_ASSERT(pNums); + auto nFound(0); + for (size_t i = 0; i < pNums->GetElements().size(); i += 2) + { + auto pI = dynamic_cast<vcl::filter::PDFNumberElement*>(pNums->GetElement(i)); + if (pI->GetValue() == pStructParent->GetValue()) + { + ++nFound; + CPPUNIT_ASSERT(i < pNums->GetElements().size() - 1); + pStructElemRef + = dynamic_cast<vcl::filter::PDFReferenceElement*>(pNums->GetElement(i + 1)); + CPPUNIT_ASSERT(pStructElemRef); + } + } + CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nFound)>(1), nFound); + } + } + CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRoots)>(1), nRoots); + + // check /StructElem - produced by drawinglayer + CPPUNIT_ASSERT(pStructElemRef); + auto pStructElem(pStructElemRef->LookupObject()); + CPPUNIT_ASSERT(pStructElem); + + auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("Type")); + CPPUNIT_ASSERT_EQUAL(OString("StructElem"), pType->GetValue()); + auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pStructElem->Lookup("S")); + CPPUNIT_ASSERT_EQUAL(OString("Annot"), pS->GetValue()); + auto pSEAlt = dynamic_cast<vcl::filter::PDFHexStringElement*>(pStructElem->Lookup("Alt")); + CPPUNIT_ASSERT_EQUAL(OUString("alternativloser text - and some description"), + ::vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pSEAlt)); + auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pStructElem->Lookup("K")); + auto nMCID(0); + auto nRef(0); + for (size_t i = 0; i < pKids->GetElements().size(); ++i) + { + auto pNum = dynamic_cast<vcl::filter::PDFNumberElement*>(pKids->GetElement(i)); + auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pKids->GetElement(i)); + if (pNum) + { + ++nMCID; + } + if (pRef) + { + ++nRef; + auto pObjR = pRef->LookupObject(); + auto pOType = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->Lookup("Type")); + CPPUNIT_ASSERT_EQUAL(OString("OBJR"), pOType->GetValue()); + auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pObjR->Lookup("Obj")); + CPPUNIT_ASSERT_EQUAL(pAnnot, pAnnotRef->LookupObject()); + } + } + CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nMCID)>(1), nMCID); + CPPUNIT_ASSERT_EQUAL(static_cast<decltype(nRef)>(1), nRef); +} + CPPUNIT_TEST_FIXTURE(PdfExportTest, testTdf142129) { loadFromURL(u"master.odm"); diff --git a/vcl/source/gdi/pdfextoutdevdata.cxx b/vcl/source/gdi/pdfextoutdevdata.cxx index c40e9a73fba3..5dfcc5b19d6e 100644 --- a/vcl/source/gdi/pdfextoutdevdata.cxx +++ b/vcl/source/gdi/pdfextoutdevdata.cxx @@ -59,6 +59,7 @@ struct PDFExtOutDevDataSync SetStructureAttribute, SetStructureAttributeNumerical, SetStructureBoundingBox, + SetStructureAnnotIds, SetActualText, SetAlternateText, CreateControl, @@ -196,6 +197,8 @@ void GlobalSyncData::PlayGlobalActions( PDFWriter& rWriter ) rWriter.SetMapMode(mParaMapModes.front()); mParaMapModes.pop_front(); mParaIds.push_back(rWriter.CreateScreen(mParaRects.front(), mParaInts.front(), mParaOUStrings.front())); + // resolve AnnotIds structural attribute + rWriter.SetLinkPropertyID(mParaIds.back(), sal_Int32(mParaIds.size()-1)); mParaRects.pop_front(); mParaInts.pop_front(); mParaOUStrings.pop_front(); @@ -278,6 +281,7 @@ void GlobalSyncData::PlayGlobalActions( PDFWriter& rWriter ) case PDFExtOutDevDataSync::SetStructureAttribute: case PDFExtOutDevDataSync::SetStructureAttributeNumerical: case PDFExtOutDevDataSync::SetStructureBoundingBox: + case PDFExtOutDevDataSync::SetStructureAnnotIds: case PDFExtOutDevDataSync::SetActualText: case PDFExtOutDevDataSync::SetAlternateText: case PDFExtOutDevDataSync::CreateControl: @@ -376,6 +380,19 @@ bool PageSyncData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAc mParaRects.pop_front(); } break; + case PDFExtOutDevDataSync::SetStructureAnnotIds: + { + ::std::vector<sal_Int32> annotIds; + auto size(mParaInts.front()); + mParaInts.pop_front(); + for (auto i = 0; i < size; ++i) + { + annotIds.push_back(mParaInts.front()); + mParaInts.pop_front(); + } + rWriter.SetStructureAnnotIds(annotIds); + } + break; case PDFExtOutDevDataSync::SetActualText : { rWriter.SetActualText( mParaOUStrings.front() ); @@ -676,14 +693,26 @@ sal_Int32 PDFExtOutDevData::CreateLink(const tools::Rectangle& rRect, OUString c return mpGlobalSyncData->mCurId++; } -sal_Int32 PDFExtOutDevData::CreateScreen(const tools::Rectangle& rRect, OUString const& rAltText, sal_Int32 nPageNr) +sal_Int32 PDFExtOutDevData::CreateScreen(const tools::Rectangle& rRect, OUString const& rAltText, sal_Int32 nPageNr, SdrObject const*const pObj) { mpGlobalSyncData->mActions.push_back(PDFExtOutDevDataSync::CreateScreen); mpGlobalSyncData->mParaRects.push_back(rRect); mpGlobalSyncData->mParaMapModes.push_back(mrOutDev.GetMapMode()); mpGlobalSyncData->mParaInts.push_back(nPageNr); mpGlobalSyncData->mParaOUStrings.push_back(rAltText); - return mpGlobalSyncData->mCurId++; + auto const ret(mpGlobalSyncData->mCurId++); + m_ScreenAnnotations[pObj].push_back(ret); + return ret; +} + +::std::vector<sal_Int32> const& PDFExtOutDevData::GetScreenAnnotIds(SdrObject const*const pObj) const +{ + auto const it(m_ScreenAnnotations.find(pObj)); + if (it == m_ScreenAnnotations.end()) + { + assert(false); // expected? + } + return it->second; } void PDFExtOutDevData::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId ) @@ -792,6 +821,17 @@ void PDFExtOutDevData::SetStructureBoundingBox( const tools::Rectangle& rRect ) mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetStructureBoundingBox ); mpPageSyncData->mParaRects.push_back( rRect ); } + +void PDFExtOutDevData::SetStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds) +{ + mpPageSyncData->PushAction(mrOutDev, PDFExtOutDevDataSync::SetStructureAnnotIds); + mpPageSyncData->mParaInts.push_back(rAnnotIds.size()); + for (sal_Int32 const id : rAnnotIds) + { + mpPageSyncData->mParaInts.push_back(id); + } +} + void PDFExtOutDevData::SetActualText( const OUString& rText ) { mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::SetActualText ); diff --git a/vcl/source/gdi/pdfwriter.cxx b/vcl/source/gdi/pdfwriter.cxx index d1399ee0912b..f0314ce0fe82 100644 --- a/vcl/source/gdi/pdfwriter.cxx +++ b/vcl/source/gdi/pdfwriter.cxx @@ -419,6 +419,11 @@ void PDFWriter::SetStructureBoundingBox( const tools::Rectangle& rRect ) xImplementation->setStructureBoundingBox( rRect ); } +void PDFWriter::SetStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds) +{ + xImplementation->setStructureAnnotIds(rAnnotIds); +} + void PDFWriter::SetActualText( const OUString& rText ) { xImplementation->setActualText( rText ); diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 4ff97a5c0988..f6776c1d560b 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -1964,6 +1964,31 @@ static void appendStructureAttributeLine( PDFWriter::StructAttribute i_eAttr, co o_rLine.append( "\n" ); } +template<typename T> +void PDFWriterImpl::AppendAnnotKid(PDFStructureElement& i_rEle, T & rAnnot) +{ + // update struct parent of link + OString const aStructParentEntry(OString::number(i_rEle.m_nObject) + " 0 R"); + m_aStructParentTree.push_back( aStructParentEntry ); + rAnnot.m_nStructParent = m_aStructParentTree.size()-1; + sal_Int32 const nAnnotObj(rAnnot.m_nObject); + + sal_Int32 const nRefObject = createObject(); + if (updateObject(nRefObject)) + { + OString aRef = + OString::number( nRefObject ) + + " 0 obj\n" + "<</Type/OBJR/Obj " + + OString::number(nAnnotObj) + + " 0 R>>\n" + "endobj\n\n"; + writeBuffer( aRef ); + } + + i_rEle.m_aKids.emplace_back( nRefObject ); +} + OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) { // create layout, list and table attribute sets @@ -1987,27 +2012,7 @@ OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) nLink = link_it->second; if( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() ) { - // update struct parent of link - OString aStructParentEntry = - OString::number( i_rEle.m_nObject ) + - " 0 R"; - m_aStructParentTree.push_back( aStructParentEntry ); - m_aLinks[ nLink ].m_nStructParent = m_aStructParentTree.size()-1; - - sal_Int32 nRefObject = createObject(); - if (updateObject(nRefObject)) - { - OString aRef = - OString::number( nRefObject ) + - " 0 obj\n" - "<</Type/OBJR/Obj " + - OString::number( m_aLinks[ nLink ].m_nObject ) + - " 0 R>>\n" - "endobj\n\n"; - writeBuffer( aRef ); - } - - i_rEle.m_aKids.emplace_back( nRefObject ); + AppendAnnotKid(i_rEle, m_aLinks[nLink]); } else { @@ -2231,6 +2236,17 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) aLine.append( "\n" ); } } + if (!rEle.m_AnnotIds.empty()) + { + for (auto const id : rEle.m_AnnotIds) + { + auto const it(m_aLinkPropertyMap.find(id)); + assert(it != m_aLinkPropertyMap.end()); + + assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aScreens.size()); + AppendAnnotKid(rEle, m_aScreens[it->second]); + } + } if( ! rEle.m_aKids.empty() ) { unsigned int i = 0; @@ -3621,6 +3637,13 @@ bool PDFWriterImpl::emitScreenAnnotations() // End Action dictionary. aLine.append("/OP 0 >>"); + if (0 < rScreen.m_nStructParent) + { + aLine.append("\n/StructParent "); + aLine.append(rScreen.m_nStructParent); + aLine.append("\n"); + } + // End Annot dictionary. aLine.append("/P "); aLine.append(m_aPages[rScreen.m_nPage].m_nPageObject); @@ -10558,11 +10581,19 @@ const char* PDFWriterImpl::getStructureTag( PDFWriter::StructElement eType ) aTagStrings[ PDFWriter::BibEntry ] = "BibEntry"; aTagStrings[ PDFWriter::Code ] = "Code"; aTagStrings[ PDFWriter::Link ] = "Link"; + aTagStrings[ PDFWriter::Annot ] = "Annot"; aTagStrings[ PDFWriter::Figure ] = "Figure"; aTagStrings[ PDFWriter::Formula ] = "Formula"; aTagStrings[ PDFWriter::Form ] = "Form"; } + if (eType == PDFWriter::Annot + && (m_aContext.Version == PDFWriter::PDFVersion::PDF_A_1 + || m_aContext.Version < PDFWriter::PDFVersion::PDF_1_5)) + { + return "Figure"; // fallback + } + std::map< PDFWriter::StructElement, const char* >::const_iterator it = aTagStrings.find( eType ); return it != aTagStrings.end() ? it->second : "Div"; @@ -11315,6 +11346,18 @@ void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect ) } } +void PDFWriterImpl::setStructureAnnotIds(::std::vector<sal_Int32> const& rAnnotIds) +{ + assert(!(m_nCurrentPage < 0 || m_aPages.size() <= o3tl::make_unsigned(m_nCurrentPage))); + + if (!m_aContext.Tagged || m_nCurrentStructElement <= 0 || !m_bEmitStructure) + { + return; + } + + m_aStructure[m_nCurrentStructElement].m_AnnotIds = rAnnotIds; +} + void PDFWriterImpl::setActualText( const OUString& rText ) { if( m_aContext.Tagged && m_nCurrentStructElement > 0 && m_bEmitStructure )