drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx | 20 - editeng/source/editeng/impedit3.cxx | 2 filter/source/pdf/pdfexport.cxx | 8 include/vcl/pdfextoutdevdata.hxx | 11 include/vcl/pdfwriter.hxx | 10 sw/source/core/text/EnhancedPDFExportHelper.cxx | 2 sw/source/core/text/itrform2.cxx | 2 sw/source/uibase/docvw/AnnotationWin2.cxx | 2 vcl/inc/pdf/pdfwriter_impl.hxx | 7 vcl/source/gdi/pdfextoutdevdata.cxx | 98 ++++-- vcl/source/gdi/pdfwriter.cxx | 15 vcl/source/gdi/pdfwriter_impl.cxx | 200 +++++++++---- 12 files changed, 277 insertions(+), 100 deletions(-)
New commits: commit 4bebff384b986c48af2b7f157cc27e956acceab6 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed Jul 12 15:26:25 2023 +0200 Commit: خالد حسني <kha...@libreoffice.org> CommitDate: Wed Jul 26 14:39:34 2023 +0200 tdf#154982 vcl: PDF Export: split BeginStructureElement ... into 3 parts: EnsureStructureElement/InitStructureElement/BeginStructureElement So EnsureStructureElement and BeginStructureElement/EndStructureElement can be called multiple times for the same object, passing in a unique key and PDFExtOutDevData will only create the element once. InitStructureElement will be used exactly once for each object when its actual content is exported. In PDFExtOutDevData rely on the indexes being the same here and in PDFWriterImpl, because then only PDFExtOutDevData needs to maintain the map from key to index. Change-Id: Idea6e34627fe559038cf13cf01dafe84b759e3c8 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/154357 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 07d790ca473cd6e71f0343419b819fa6b485dc01) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/154426 Reviewed-by: خالد حسني <kha...@libreoffice.org> diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx index e55055024159..5e7b5523bad9 100644 --- a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx +++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx @@ -1126,7 +1126,7 @@ void VclMetafileProcessor2D::processControlPrimitive2D( mpOutputDevice->GetMapMode()); pPDFControl->TextFont.SetFontSize(aFontSize); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Form); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Form); vcl::PDFWriter::StructAttributeValue role; switch (pPDFControl->Type) { @@ -1173,7 +1173,7 @@ void VclMetafileProcessor2D::processControlPrimitive2D( if (mpPDFExtOutDevData) { // no corresponding PDF Form, use Figure instead - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Figure); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Figure); mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement, vcl::PDFWriter::Block); OUString const& rAltText(rControlPrimitive.GetAltText()); if (!rAltText.isEmpty()) @@ -1309,7 +1309,7 @@ void VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D( if (mbInListItem) { maListElements.push(vcl::PDFWriter::LILabel); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::LILabel); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::LILabel); } // process recursively and add MetaFile comment @@ -1348,7 +1348,7 @@ void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( { // No Tagged PDF -> Dump as Paragraph // Emulate data handling from old ImpEditEngine::Paint - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Paragraph); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph); // Process recursively and add MetaFile comment process(rParagraphPrimitive); @@ -1374,7 +1374,7 @@ void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( for (sal_Int16 a(mnCurrentOutlineLevel); a != nNewOutlineLevel; ++a) { maListElements.push(vcl::PDFWriter::List); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::List); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::List); } } else // if(nNewOutlineLevel < mnCurrentOutlineLevel) @@ -1405,13 +1405,13 @@ void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( { // Dump as ListItem maListElements.push(vcl::PDFWriter::ListItem); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::ListItem); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::ListItem); mbInListItem = true; } else { // Dump as Paragraph - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Paragraph); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph); } // Process recursively and add MetaFile comment @@ -1455,7 +1455,7 @@ void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( if (mbInListItem && mbBulletPresent) { maListElements.push(vcl::PDFWriter::LIBody); - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::LIBody); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::LIBody); } // directdraw of text simple portion; use default processing @@ -2546,7 +2546,7 @@ void VclMetafileProcessor2D::processStructureTagPrimitive2D( SAL_WARN("drawinglayer", "anchor structure element not found?"); } } - mpPDFExtOutDevData->BeginStructureElement(rTagElement); + mpPDFExtOutDevData->WrapBeginStructureElement(rTagElement); switch (rTagElement) { case vcl::PDFWriter::H1: @@ -2606,7 +2606,7 @@ void VclMetafileProcessor2D::processStructureTagPrimitive2D( { // background image: tag as artifact if (rStructureTagCandidate.isImage()) - mpPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::NonStructElement); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::NonStructElement); // any other background object: do not tag else assert(false); diff --git a/editeng/source/editeng/impedit3.cxx b/editeng/source/editeng/impedit3.cxx index 8a581988f679..ce79f8bfe1d2 100644 --- a/editeng/source/editeng/impedit3.cxx +++ b/editeng/source/editeng/impedit3.cxx @@ -3386,7 +3386,7 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po return; if ( pPDFExtOutDevData ) - pPDFExtOutDevData->BeginStructureElement( vcl::PDFWriter::Paragraph ); + pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph); const tools::Long nParaHeight = pPortion->GetHeight(); if ( pPortion->IsVisible() && ( diff --git a/filter/source/pdf/pdfexport.cxx b/filter/source/pdf/pdfexport.cxx index eac4128da177..1d3d9185b7a4 100644 --- a/filter/source/pdf/pdfexport.cxx +++ b/filter/source/pdf/pdfexport.cxx @@ -1263,7 +1263,9 @@ void PDFExport::ImplWriteWatermark( vcl::PDFWriter& rWriter, const Size& rPageSi rWriter.Push(); // tdf#152235 tag around the reference to the XObject on the page - rWriter.BeginStructureElement(vcl::PDFWriter::NonStructElement, ::std::u16string_view()); + sal_Int32 const id = rWriter.EnsureStructureElement(); + rWriter.InitStructureElement(id, vcl::PDFWriter::NonStructElement, ::std::u16string_view()); + rWriter.BeginStructureElement(id); rWriter.SetStructureAttribute(vcl::PDFWriter::Type, vcl::PDFWriter::Pagination); rWriter.SetStructureAttribute(vcl::PDFWriter::Subtype, vcl::PDFWriter::Watermark); // HACK: this should produce *nothing* itself but is necessary to output @@ -1359,7 +1361,9 @@ void PDFExport::ImplWriteTiledWatermark( vcl::PDFWriter& rWriter, const Size& rP rWriter.Push(); // tdf#152235 tag around the reference to the XObject on the page - rWriter.BeginStructureElement(vcl::PDFWriter::NonStructElement, ::std::u16string_view()); + sal_Int32 const id = rWriter.EnsureStructureElement(); + rWriter.InitStructureElement(id, vcl::PDFWriter::NonStructElement, ::std::u16string_view()); + rWriter.BeginStructureElement(id); rWriter.SetStructureAttribute(vcl::PDFWriter::Type, vcl::PDFWriter::Pagination); rWriter.SetStructureAttribute(vcl::PDFWriter::Subtype, vcl::PDFWriter::Watermark); // HACK: this should produce *nothing* itself but is necessary to output diff --git a/include/vcl/pdfextoutdevdata.hxx b/include/vcl/pdfextoutdevdata.hxx index 58088d235eff..987e2a1020b2 100644 --- a/include/vcl/pdfextoutdevdata.hxx +++ b/include/vcl/pdfextoutdevdata.hxx @@ -345,6 +345,11 @@ public: (e.g. a section can contain a heading and a paragraph). The structure hierarchy is build automatically from the Begin/EndStructureElement calls. + The easy way is to call WrapBeginStructureElement, but it's also possible + to call EnsureStructureElement/InitStructureElement/BeginStructureElement + (its 3 parts) manually for more control; this way a placeholder SE can be + inserted and initialised later. + A structural element need not be contained on one page; e.g. paragraphs often run from one page to the next. In this case the corresponding EndStructureElement must be called while drawing the next page. @@ -372,7 +377,11 @@ public: @returns the id of the newly created structural element */ - sal_Int32 BeginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias = OUString() ); + sal_Int32 WrapBeginStructureElement(PDFWriter::StructElement eType, const OUString& rAlias = OUString()); + sal_Int32 EnsureStructureElement(void const* key); + void InitStructureElement(sal_Int32 id, PDFWriter::StructElement eType, const OUString& rAlias); + void BeginStructureElement(sal_Int32 id); + /** end a logical structure element @see BeginStructureElement diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx index 0f4eccb42728..6b9c32707839 100644 --- a/include/vcl/pdfwriter.hxx +++ b/include/vcl/pdfwriter.hxx @@ -1056,6 +1056,11 @@ The following structure describes the permissions used in PDF security (e.g. a section can contain a heading and a paragraph). The structure hierarchy is build automatically from the Begin/EndStructureElement calls. + The easy way is to call WrapBeginStructureElement, but it's also possible + to call EnsureStructureElement/InitStructureElement/BeginStructureElement + (its 3 parts) manually for more control; this way a placeholder SE can be + inserted and initialised later. + A structural element need not be contained on one page; e.g. paragraphs often run from one page to the next. In this case the corresponding EndStructureElement must be called while drawing the next page. @@ -1090,7 +1095,10 @@ The following structure describes the permissions used in PDF security @returns the new structure element's id for use in SetCurrentStructureElement */ - sal_Int32 BeginStructureElement( enum StructElement eType, std::u16string_view rAlias ); + void BeginStructureElement(sal_Int32 id); + sal_Int32 EnsureStructureElement(); + void InitStructureElement(sal_Int32 id, PDFWriter::StructElement eType, std::u16string_view rAlias); + /** end the current logical structure element Close the current structure element. The current element's diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx b/sw/source/core/text/EnhancedPDFExportHelper.cxx index 431137a92257..295281f8ac74 100644 --- a/sw/source/core/text/EnhancedPDFExportHelper.cxx +++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx @@ -458,7 +458,7 @@ void SwTaggedPDFHelper::CheckRestoreTag() const void SwTaggedPDFHelper::BeginTag( vcl::PDFWriter::StructElement eType, const OUString& rString ) { // write new tag - const sal_Int32 nId = mpPDFExtOutDevData->BeginStructureElement( eType, rString ); + const sal_Int32 nId = mpPDFExtOutDevData->WrapBeginStructureElement( eType, rString ); ++m_nEndStructureElement; #if OSL_DEBUG_LEVEL > 1 diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx index 5895825bf668..2f5abb9a5c6b 100644 --- a/sw/source/core/text/itrform2.cxx +++ b/sw/source/core/text/itrform2.cxx @@ -1094,7 +1094,7 @@ bool SwContentControlPortion::DescribePDFControl(const SwTextPaintInfo& rInf) co aLocation.Union(aEndRect); pDescriptor->Location = aLocation.SVRect(); - pPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::Form); + pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Form); pPDFExtOutDevData->CreateControl(*pDescriptor); pPDFExtOutDevData->EndStructureElement(); diff --git a/sw/source/uibase/docvw/AnnotationWin2.cxx b/sw/source/uibase/docvw/AnnotationWin2.cxx index c683e69b4204..3f8a1752e890 100644 --- a/sw/source/uibase/docvw/AnnotationWin2.cxx +++ b/sw/source/uibase/docvw/AnnotationWin2.cxx @@ -165,7 +165,7 @@ void SwAnnotationWin::DrawForPage(OutputDevice* pDev, const Point& rPt) dynamic_cast<vcl::PDFExtOutDevData*>(pDev->GetExtOutDevData())); if (pPDFExtOutDevData && pPDFExtOutDevData->GetIsExportTaggedPDF()) { - pPDFExtOutDevData->BeginStructureElement(vcl::PDFWriter::NonStructElement, OUString()); + pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::NonStructElement, OUString()); } pDev->Push(); diff --git a/vcl/inc/pdf/pdfwriter_impl.hxx b/vcl/inc/pdf/pdfwriter_impl.hxx index a9438be8fc13..8c232589e9c7 100644 --- a/vcl/inc/pdf/pdfwriter_impl.hxx +++ b/vcl/inc/pdf/pdfwriter_impl.hxx @@ -582,7 +582,7 @@ struct PDFStructureElementKid // for Kids entries struct PDFStructureElement { sal_Int32 m_nObject; - PDFWriter::StructElement m_eType; + ::std::optional<PDFWriter::StructElement> m_oType; OString m_aAlias; sal_Int32 m_nOwnElement; // index into structure vector sal_Int32 m_nParentElement; // index into structure vector @@ -603,7 +603,6 @@ struct PDFStructureElement PDFStructureElement() : m_nObject( 0 ), - m_eType( PDFWriter::NonStructElement ), m_nOwnElement( -1 ), m_nParentElement( -1 ), m_nFirstPageObject( 0 ), @@ -1330,7 +1329,9 @@ public: // notes void createNote( const tools::Rectangle& rRect, const PDFNote& rNote, sal_Int32 nPageNr ); // structure elements - sal_Int32 beginStructureElement( PDFWriter::StructElement eType, std::u16string_view rAlias ); + sal_Int32 ensureStructureElement(); + void initStructureElement(sal_Int32 id, PDFWriter::StructElement eType, std::u16string_view rAlias); + void beginStructureElement(sal_Int32 id); void endStructureElement(); bool setCurrentStructureElement( sal_Int32 nElement ); bool setStructureAttribute( enum PDFWriter::StructAttribute eAttr, enum PDFWriter::StructAttributeValue eVal ); diff --git a/vcl/source/gdi/pdfextoutdevdata.cxx b/vcl/source/gdi/pdfextoutdevdata.cxx index ff99fdbbcd90..057cb4e07549 100644 --- a/vcl/source/gdi/pdfextoutdevdata.cxx +++ b/vcl/source/gdi/pdfextoutdevdata.cxx @@ -53,6 +53,8 @@ struct PDFExtOutDevDataSync CreateNote, SetPageTransition, + EnsureStructureElement, + InitStructureElement, BeginStructureElement, EndStructureElement, SetCurrentStructureElement, @@ -95,7 +97,6 @@ struct GlobalSyncData ::std::map< sal_Int32, PDFLinkDestination > mFutureDestinations; sal_Int32 GetMappedId(); - sal_Int32 GetMappedStructId( sal_Int32 ); /** the way this appears to work: (only) everything that increments mCurId at recording time must put an item into mParaIds at playback time, @@ -104,6 +105,7 @@ struct GlobalSyncData sal_Int32 mCurId; std::vector< sal_Int32 > mParaIds; std::vector< sal_Int32 > mStructIdMap; + std::map<void const*, sal_Int32> mSEMap; sal_Int32 mCurrentStructElement; std::vector< sal_Int32 > mStructParents; @@ -111,7 +113,7 @@ struct GlobalSyncData mCurId ( 0 ), mCurrentStructElement( 0 ) { - mStructParents.push_back( 0 ); + mStructParents.push_back(0); // because PDFWriterImpl has a dummy root mStructIdMap.push_back( 0 ); } void PlayGlobalActions( PDFWriter& rWriter ); @@ -138,18 +140,6 @@ sal_Int32 GlobalSyncData::GetMappedId() return nLinkId; } -sal_Int32 GlobalSyncData::GetMappedStructId( sal_Int32 nStructId ) -{ - if ( o3tl::make_unsigned(nStructId) < mStructIdMap.size() ) - nStructId = mStructIdMap[ nStructId ]; - else - nStructId = -1; - - SAL_WARN_IF( nStructId < 0, "vcl", "unmapped structure id in GlobalSyncData" ); - - return nStructId; -} - void GlobalSyncData::PlayGlobalActions( PDFWriter& rWriter ) { for (auto const& action : mActions) @@ -282,6 +272,8 @@ void GlobalSyncData::PlayGlobalActions( PDFWriter& rWriter ) mParaInts.pop_front(); } break; + case PDFExtOutDevDataSync::EnsureStructureElement: + case PDFExtOutDevDataSync::InitStructureElement: case PDFExtOutDevDataSync::BeginStructureElement: case PDFExtOutDevDataSync::EndStructureElement: case PDFExtOutDevDataSync::SetCurrentStructureElement: @@ -348,12 +340,28 @@ bool PageSyncData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAc mActions.pop_front(); switch( aDataSync.eAct ) { - case PDFExtOutDevDataSync::BeginStructureElement : + case PDFExtOutDevDataSync::EnsureStructureElement: + { +#ifndef NDEBUG + sal_Int32 const id = +#endif + rWriter.EnsureStructureElement(); + assert(id == -1 || id == mParaInts.front()); // identity mapping + mParaInts.pop_front(); + } + break; + case PDFExtOutDevDataSync::InitStructureElement: { - sal_Int32 nNewEl = rWriter.BeginStructureElement( mParaStructElements.front(), mParaOUStrings.front() ) ; + rWriter.InitStructureElement(mParaInts.front(), mParaStructElements.front(), mParaOUStrings.front()); + mParaInts.pop_front(); mParaStructElements.pop_front(); mParaOUStrings.pop_front(); - mpGlobalData->mStructIdMap.push_back( nNewEl ); + } + break; + case PDFExtOutDevDataSync::BeginStructureElement : + { + rWriter.BeginStructureElement(mParaInts.front()); + mParaInts.pop_front(); } break; case PDFExtOutDevDataSync::EndStructureElement : @@ -363,7 +371,7 @@ bool PageSyncData::PlaySyncPageAct( PDFWriter& rWriter, sal_uInt32& rCurGDIMtfAc break; case PDFExtOutDevDataSync::SetCurrentStructureElement: { - rWriter.SetCurrentStructureElement( mpGlobalData->GetMappedStructId( mParaInts.front() ) ); + rWriter.SetCurrentStructureElement(mParaInts.front()); mParaInts.pop_front(); } break; @@ -789,17 +797,57 @@ void PDFExtOutDevData::SetPageTransition( PDFWriter::PageTransition eType, sal_u /* local (page), actions have to be played synchronously to the actions of of the recorded metafile (created by each xRenderable->render()) */ - sal_Int32 PDFExtOutDevData::BeginStructureElement( PDFWriter::StructElement eType, const OUString& rAlias ) + +sal_Int32 PDFExtOutDevData::EnsureStructureElement(void const*const key) { - mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::BeginStructureElement ); + sal_Int32 id(-1); + if (key != nullptr) + { + auto const it(mpGlobalSyncData->mSEMap.find(key)); + if (it != mpGlobalSyncData->mSEMap.end()) + { + id = it->second; + } + } + if (id == -1) + { + mpPageSyncData->PushAction(mrOutDev, PDFExtOutDevDataSync::EnsureStructureElement); + id = mpGlobalSyncData->mStructParents.size(); + mpPageSyncData->mParaInts.push_back(id); + mpGlobalSyncData->mStructParents.push_back(mpGlobalSyncData->mCurrentStructElement); + if (key != nullptr) + { + mpGlobalSyncData->mSEMap.emplace(key, id); + } + } + return id; +} + +void PDFExtOutDevData::InitStructureElement(sal_Int32 const id, + PDFWriter::StructElement const eType, const OUString& rAlias) +{ + mpPageSyncData->PushAction(mrOutDev, PDFExtOutDevDataSync::InitStructureElement); + mpPageSyncData->mParaInts.push_back(id); mpPageSyncData->mParaStructElements.push_back( eType ); mpPageSyncData->mParaOUStrings.push_back( rAlias ); - // need a global id - sal_Int32 nNewId = mpGlobalSyncData->mStructParents.size(); - mpGlobalSyncData->mStructParents.push_back( mpGlobalSyncData->mCurrentStructElement ); - mpGlobalSyncData->mCurrentStructElement = nNewId; - return nNewId; } + +void PDFExtOutDevData::BeginStructureElement(sal_Int32 const id) +{ + mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::BeginStructureElement ); + mpPageSyncData->mParaInts.push_back(id); + mpGlobalSyncData->mCurrentStructElement = id; +} + +sal_Int32 PDFExtOutDevData::WrapBeginStructureElement( + PDFWriter::StructElement const eType, const OUString& rAlias) +{ + sal_Int32 const id = EnsureStructureElement(nullptr); + InitStructureElement(id, eType, rAlias); + BeginStructureElement(id); + return id; +} + void PDFExtOutDevData::EndStructureElement() { mpPageSyncData->PushAction( mrOutDev, PDFExtOutDevDataSync::EndStructureElement ); diff --git a/vcl/source/gdi/pdfwriter.cxx b/vcl/source/gdi/pdfwriter.cxx index dda44d2f4adb..ac50b0821c73 100644 --- a/vcl/source/gdi/pdfwriter.cxx +++ b/vcl/source/gdi/pdfwriter.cxx @@ -389,9 +389,20 @@ void PDFWriter::CreateNote( const tools::Rectangle& rRect, const PDFNote& rNote, xImplementation->createNote( rRect, rNote, nPageNr ); } -sal_Int32 PDFWriter::BeginStructureElement( PDFWriter::StructElement eType, std::u16string_view rAlias ) +sal_Int32 PDFWriter::EnsureStructureElement() { - return xImplementation->beginStructureElement( eType, rAlias ); + return xImplementation->ensureStructureElement(); +} + +void PDFWriter::InitStructureElement(sal_Int32 const id, + PDFWriter::StructElement const eType, std::u16string_view const rAlias) +{ + return xImplementation->initStructureElement(id, eType, rAlias); +} + +void PDFWriter::BeginStructureElement(sal_Int32 const id) +{ + return xImplementation->beginStructureElement(id); } void PDFWriter::EndStructureElement() diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index b5653748ac8e..93ff4c100619 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -297,6 +297,8 @@ GEOMETRY lcl_convert( const MapMode& _rSource, const MapMode& _rDest, OutputDevi return aPoint; } +void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle); + } // end anonymous namespace void PDFWriter::AppendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer) @@ -2088,19 +2090,20 @@ OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle ) sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) { - if( + assert(rEle.m_nOwnElement == 0 || rEle.m_oType); + if (rEle.m_nOwnElement != rEle.m_nParentElement // emit the struct tree root // do not emit NonStruct and its children - rEle.m_eType == PDFWriter::NonStructElement && - rEle.m_nOwnElement != rEle.m_nParentElement // but of course emit the struct tree root - ) + && *rEle.m_oType == PDFWriter::NonStructElement) + { return 0; + } for (auto const& child : rEle.m_aChildren) { if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() ) { PDFStructureElement& rChild = m_aStructure[ child ]; - if( rChild.m_eType != PDFWriter::NonStructElement ) + if (*rChild.m_oType != PDFWriter::NonStructElement) { if( rChild.m_nParentElement == rEle.m_nOwnElement ) emitStructure( rChild ); @@ -2157,7 +2160,7 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) if( !rEle.m_aAlias.isEmpty() ) aLine.append( rEle.m_aAlias ); else - aLine.append( getStructureTag( rEle.m_eType ) ); + aLine.append( getStructureTag(*rEle.m_oType) ); if (m_StructElemObjsWithID.find(rEle.m_nObject) != m_StructElemObjsWithID.end()) { aLine.append("\n/ID "); @@ -2223,7 +2226,7 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle ) auto const it(m_aLinkPropertyMap.find(id)); assert(it != m_aLinkPropertyMap.end()); - if (rEle.m_eType == PDFWriter::Form) + if (*rEle.m_oType == PDFWriter::Form) { assert(0 <= it->second && o3tl::make_unsigned(it->second) < m_aWidgets.size()); AppendAnnotKid(rEle, m_aWidgets[it->second]); @@ -5187,6 +5190,7 @@ bool PDFWriterImpl::emitCatalog() sal_Int32 nStructureDict = 0; if(m_aStructure.size() > 1) { + removePlaceholderSE(m_aStructure, m_aStructure[0]); // check if dummy structure containers are needed addInternalStructureContainer(m_aStructure[0]); nStructureDict = m_aStructure[0].m_nObject = createObject(); @@ -10687,11 +10691,13 @@ void PDFWriterImpl::addRoleMap(OString aAlias, PDFWriter::StructElement eType) void PDFWriterImpl::beginStructureElementMCSeq() { + assert(m_nCurrentStructElement == 0 || m_aStructure[m_nCurrentStructElement].m_oType); if( m_bEmitStructure && m_nCurrentStructElement > 0 && // StructTreeRoot // Document = SwPageFrame => this is not *inside* the page content // stream so do not emit MCID! - m_aStructure[m_nCurrentStructElement].m_eType != PDFWriter::Document && + m_aStructure[m_nCurrentStructElement].m_oType && + *m_aStructure[m_nCurrentStructElement].m_oType != PDFWriter::Document && ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence ) { @@ -10702,7 +10708,7 @@ void PDFWriterImpl::beginStructureElementMCSeq() if( !rEle.m_aAlias.isEmpty() ) aLine.append( rEle.m_aAlias ); else - aLine.append( getStructureTag( rEle.m_eType ) ); + aLine.append( getStructureTag(*rEle.m_oType) ); aLine.append( "<</MCID " ); aLine.append( nMCID ); aLine.append( ">>BDC\n" ); @@ -10721,7 +10727,8 @@ void PDFWriterImpl::beginStructureElementMCSeq() // handle artifacts else if( ! m_bEmitStructure && m_aContext.Tagged && m_nCurrentStructElement > 0 && - m_aStructure[ m_nCurrentStructElement ].m_eType == PDFWriter::NonStructElement && + m_aStructure[m_nCurrentStructElement].m_oType && + *m_aStructure[m_nCurrentStructElement].m_oType == PDFWriter::NonStructElement && ! m_aStructure[ m_nCurrentStructElement ].m_bOpenMCSeq // already opened sequence ) { @@ -10751,9 +10758,10 @@ void PDFWriterImpl::beginStructureElementMCSeq() void PDFWriterImpl::endStructureElementMCSeq(EndMode const endMode) { if (m_nCurrentStructElement > 0 // not StructTreeRoot + && m_aStructure[m_nCurrentStructElement].m_oType && (m_bEmitStructure || (endMode != EndMode::OnlyStruct - && m_aStructure[m_nCurrentStructElement].m_eType == PDFWriter::NonStructElement)) + && m_aStructure[m_nCurrentStructElement].m_oType == PDFWriter::NonStructElement)) && m_aStructure[m_nCurrentStructElement].m_bOpenMCSeq) { writeBuffer( "EMC\n" ); @@ -10770,7 +10778,8 @@ bool PDFWriterImpl::checkEmitStructure() sal_Int32 nEle = m_nCurrentStructElement; while( nEle > 0 && o3tl::make_unsigned(nEle) < m_aStructure.size() ) { - if( m_aStructure[ nEle ].m_eType == PDFWriter::NonStructElement ) + if (m_aStructure[nEle].m_oType + && *m_aStructure[nEle].m_oType == PDFWriter::NonStructElement) { bEmit = false; break; @@ -10781,16 +10790,28 @@ bool PDFWriterImpl::checkEmitStructure() return bEmit; } -sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, std::u16string_view rAlias ) +sal_Int32 PDFWriterImpl::ensureStructureElement() { - if( m_nCurrentPage < 0 ) - return -1; - if( ! m_aContext.Tagged ) return -1; - // close eventual current MC sequence - endStructureElementMCSeq(EndMode::OnlyStruct); + sal_Int32 nNewId = sal_Int32(m_aStructure.size()); + m_aStructure.emplace_back(); + PDFStructureElement& rEle = m_aStructure.back(); + // leave rEle.m_oType uninitialised + rEle.m_nOwnElement = nNewId; + // temporary parent + rEle.m_nParentElement = m_nCurrentStructElement; + rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject; + m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId ); + return nNewId; +} + +void PDFWriterImpl::initStructureElement(sal_Int32 const id, + PDFWriter::StructElement const eType, std::u16string_view const rAlias) +{ + if( ! m_aContext.Tagged ) + return; if( m_nCurrentStructElement == 0 && eType != PDFWriter::Document && eType != PDFWriter::NonStructElement ) @@ -10802,7 +10823,9 @@ sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, { const std::vector< sal_Int32 >& rRootChildren = m_aStructure[0].m_aChildren; auto it = std::find_if(rRootChildren.begin(), rRootChildren.end(), - [&](sal_Int32 nElement) { return m_aStructure[ nElement ].m_eType == PDFWriter::Document; }); + [&](sal_Int32 nElement) { + return m_aStructure[nElement].m_oType + && *m_aStructure[nElement].m_oType == PDFWriter::Document; }); if( it != rRootChildren.end() ) { m_nCurrentStructElement = *it; @@ -10817,15 +10840,17 @@ sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, } } - sal_Int32 nNewId = sal_Int32(m_aStructure.size()); - m_aStructure.emplace_back( ); - PDFStructureElement& rEle = m_aStructure.back(); - rEle.m_eType = eType; - rEle.m_nOwnElement = nNewId; + PDFStructureElement& rEle = m_aStructure[id]; + assert(!rEle.m_oType); + rEle.m_oType.emplace(eType); + // remove it from its possibly placeholder parent; append to real parent + auto const it(std::find(m_aStructure[rEle.m_nParentElement].m_aChildren.begin(), + m_aStructure[rEle.m_nParentElement].m_aChildren.end(), id)); + assert(it != m_aStructure[rEle.m_nParentElement].m_aChildren.end()); + m_aStructure[rEle.m_nParentElement].m_aChildren.erase(it); rEle.m_nParentElement = m_nCurrentStructElement; rEle.m_nFirstPageObject = m_aPages[ m_nCurrentPage ].m_nPageObject; - m_aStructure[ m_nCurrentStructElement ].m_aChildren.push_back( nNewId ); - m_nCurrentStructElement = nNewId; + m_aStructure[m_nCurrentStructElement].m_aChildren.push_back(id); // handle alias names if( !rAlias.empty() && eType != PDFWriter::NonStructElement ) @@ -10837,12 +10862,41 @@ sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, addRoleMap(aAliasName, eType); } + if (m_bEmitStructure && eType != PDFWriter::NonStructElement) // don't create nonexistent objects + { + rEle.m_nObject = createObject(); + // update parent's kids list + m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(rEle.m_nObject); + // ISO 14289-1:2014, Clause: 7.9 + if (*rEle.m_oType == PDFWriter::Note) + { + m_StructElemObjsWithID.insert(rEle.m_nObject); + } + } +} + +void PDFWriterImpl::beginStructureElement(sal_Int32 const id) +{ + if( m_nCurrentPage < 0 ) + return; + + if( ! m_aContext.Tagged ) + return; + + // close eventual current MC sequence + endStructureElementMCSeq(EndMode::OnlyStruct); + + PDFStructureElement& rEle = m_aStructure[id]; + m_nCurrentStructElement = id; + if (g_bDebugDisableCompression) { OStringBuffer aLine( "beginStructureElement " ); aLine.append( m_nCurrentStructElement ); aLine.append( ": " ); - aLine.append( getStructureTag( eType ) ); + aLine.append( rEle.m_oType + ? getStructureTag(*rEle.m_oType) + : "<placeholder>" ); if( !rEle.m_aAlias.isEmpty() ) { aLine.append( " aliased as \"" ); @@ -10854,19 +10908,6 @@ sal_Int32 PDFWriterImpl::beginStructureElement( PDFWriter::StructElement eType, // check whether to emit structure henceforth m_bEmitStructure = checkEmitStructure(); - - if( m_bEmitStructure ) // don't create nonexistent objects - { - rEle.m_nObject = createObject(); - // update parent's kids list - m_aStructure[ rEle.m_nParentElement ].m_aKids.emplace_back(rEle.m_nObject); - // ISO 14289-1:2014, Clause: 7.9 - if (rEle.m_eType == PDFWriter::Note) - { - m_StructElemObjsWithID.insert(rEle.m_nObject); - } - } - return nNewId; } void PDFWriterImpl::endStructureElement() @@ -10893,7 +10934,9 @@ void PDFWriterImpl::endStructureElement() aLine.append( "endStructureElement " ); aLine.append( m_nCurrentStructElement ); aLine.append( ": " ); - aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) ); + aLine.append( m_aStructure[m_nCurrentStructElement].m_oType + ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType) + : "<placeholder>" ); if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() ) { aLine.append( " aliased as \"" ); @@ -10914,6 +10957,49 @@ void PDFWriterImpl::endStructureElement() } } +namespace { + +void removePlaceholderSEImpl(std::vector<PDFStructureElement> & rStructure, + std::vector<sal_Int32>::iterator & rParentIt) +{ + PDFStructureElement& rEle(rStructure[*rParentIt]); + removePlaceholderSE(rStructure, rEle); + + if (!rEle.m_oType) + { + // Placeholder was not initialised - should not happen when printing + // a full page, but might if a selection is printed, which can be only + // a shape without its anchor. + // Handle this by moving the children to the parent SE. + PDFStructureElement & rParent(rStructure[rEle.m_nParentElement]); + rParentIt = rParent.m_aChildren.erase(rParentIt); + std::vector<sal_Int32> children; + for (auto const child : rEle.m_aChildren) + { + PDFStructureElement& rChild = rStructure[child]; + rChild.m_nParentElement = rEle.m_nParentElement; + children.push_back(rChild.m_nOwnElement); + } + rParentIt = rParent.m_aChildren.insert(rParentIt, children.begin(), children.end()) + + children.size(); + } + else + { + ++rParentIt; + } + +} + +void removePlaceholderSE(std::vector<PDFStructureElement> & rStructure, PDFStructureElement& rEle) +{ + for (auto it = rEle.m_aChildren.begin(); it != rEle.m_aChildren.end(); ) + { + removePlaceholderSEImpl(rStructure, it); + } +} + +} // end anonymous namespace + /* * This function adds an internal structure list container to overcome the 8191 elements array limitation * in kids element emission. @@ -10922,18 +11008,22 @@ void PDFWriterImpl::endStructureElement() */ void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle ) { - if( rEle.m_eType == PDFWriter::NonStructElement && - rEle.m_nOwnElement != rEle.m_nParentElement ) + if (rEle.m_nOwnElement != rEle.m_nParentElement + && *rEle.m_oType == PDFWriter::NonStructElement) + { return; + } for (auto const& child : rEle.m_aChildren) { + assert(child > 0 && o3tl::make_unsigned(child) < m_aStructure.size()); if( child > 0 && o3tl::make_unsigned(child) < m_aStructure.size() ) { PDFStructureElement& rChild = m_aStructure[ child ]; - if( rChild.m_eType != PDFWriter::NonStructElement ) + if (*rChild.m_oType != PDFWriter::NonStructElement) { //triggered when a child of the rEle element is found + assert(rChild.m_nParentElement == rEle.m_nOwnElement); if( rChild.m_nParentElement == rEle.m_nOwnElement ) addInternalStructureContainer( rChild );//examine the child else @@ -10974,7 +11064,7 @@ void PDFWriterImpl::addInternalStructureContainer( PDFStructureElement& rEle ) m_aStructure.emplace_back( ); PDFStructureElement& rEleNew = m_aStructure.back(); rEleNew.m_aAlias = aAliasName; - rEleNew.m_eType = PDFWriter::Division; // a new Div type container + rEleNew.m_oType.emplace(PDFWriter::Division); // a new Div type container rEleNew.m_nOwnElement = nNewId; rEleNew.m_nParentElement = nCurrentStructElement; //inherit the same page as the first child to be reparented @@ -11025,7 +11115,9 @@ bool PDFWriterImpl::setCurrentStructureElement( sal_Int32 nEle ) OStringBuffer aLine( "setCurrentStructureElement " ); aLine.append( m_nCurrentStructElement ); aLine.append( ": " ); - aLine.append( getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) ); + aLine.append( m_aStructure[m_nCurrentStructElement].m_oType + ? getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType) + : "<placeholder>" ); if( !m_aStructure[ m_nCurrentStructElement ].m_aAlias.isEmpty() ) { aLine.append( " aliased as \"" ); @@ -11047,15 +11139,17 @@ bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr if( !m_aContext.Tagged ) return false; + assert(m_aStructure[m_nCurrentStructElement].m_oType); bool bInsert = false; if (m_nCurrentStructElement > 0 && (m_bEmitStructure // allow it for topmost non-structured element || (m_aContext.Tagged && (0 == m_aStructure[m_nCurrentStructElement].m_nParentElement - || m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_eType != PDFWriter::NonStructElement)))) + || !m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType + || *m_aStructure[m_aStructure[m_nCurrentStructElement].m_nParentElement].m_oType != PDFWriter::NonStructElement)))) { - PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType; + PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType; switch( eAttr ) { case PDFWriter::Placement: @@ -11274,7 +11368,7 @@ bool PDFWriterImpl::setStructureAttribute( enum PDFWriter::StructAttribute eAttr SAL_INFO("vcl.pdfwriter", "rejecting setStructureAttribute( " << getAttributeTag( eAttr ) << ", " << getAttributeValueTag( eVal ) - << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) + << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType) << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias << ") element"); @@ -11286,6 +11380,7 @@ bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttrib if( ! m_aContext.Tagged ) return false; + assert(m_aStructure[m_nCurrentStructElement].m_oType); bool bInsert = false; if( m_nCurrentStructElement > 0 && m_bEmitStructure ) { @@ -11295,7 +11390,7 @@ bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttrib return true; } - PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType; + PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType; switch( eAttr ) { case PDFWriter::SpaceBefore: @@ -11406,7 +11501,7 @@ bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttrib SAL_INFO("vcl.pdfwriter", "rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr ) << ", " << static_cast<int>(nValue) - << " ) on " << getStructureTag( m_aStructure[ m_nCurrentStructElement ].m_eType ) + << " ) on " << getStructureTag(*m_aStructure[m_nCurrentStructElement].m_oType) << " (" << m_aStructure[ m_nCurrentStructElement ].m_aAlias << ") element"); @@ -11422,7 +11517,8 @@ void PDFWriterImpl::setStructureBoundingBox( const tools::Rectangle& rRect ) if( !(m_nCurrentStructElement > 0 && m_bEmitStructure) ) return; - PDFWriter::StructElement eType = m_aStructure[ m_nCurrentStructElement ].m_eType; + assert(m_aStructure[m_nCurrentStructElement].m_oType); + PDFWriter::StructElement const eType = *m_aStructure[m_nCurrentStructElement].m_oType; if( eType == PDFWriter::Figure || eType == PDFWriter::Formula || eType == PDFWriter::Form ||