diff options
Diffstat (limited to 'sw/source/core/text/EnhancedPDFExportHelper.cxx')
-rw-r--r-- | sw/source/core/text/EnhancedPDFExportHelper.cxx | 3061 |
1 files changed, 3061 insertions, 0 deletions
diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx b/sw/source/core/text/EnhancedPDFExportHelper.cxx new file mode 100644 index 0000000000..499dcc2417 --- /dev/null +++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx @@ -0,0 +1,3061 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <EnhancedPDFExportHelper.hxx> + +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/drawing/XShape.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <hintids.hxx> + +#include <sot/exchange.hxx> +#include <vcl/outdev.hxx> +#include <vcl/pdfextoutdevdata.hxx> +#include <tools/multisel.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/langitem.hxx> +#include <tools/urlobj.hxx> +#include <svl/languageoptions.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <swatrset.hxx> +#include <frmatr.hxx> +#include <paratr.hxx> +#include <ndtxt.hxx> +#include <ndole.hxx> +#include <section.hxx> +#include <tox.hxx> +#include <fmtfld.hxx> +#include <txtinet.hxx> +#include <fmtinfmt.hxx> +#include <fchrfmt.hxx> +#include <charfmt.hxx> +#include <fmtanchr.hxx> +#include <fmturl.hxx> +#include <editsh.hxx> +#include <viscrs.hxx> +#include <txtfld.hxx> +#include <reffld.hxx> +#include <doc.hxx> +#include <IDocumentOutlineNodes.hxx> +#include <mdiexp.hxx> +#include <docufld.hxx> +#include <ftnidx.hxx> +#include <txtftn.hxx> +#include <rootfrm.hxx> +#include <pagefrm.hxx> +#include <txtfrm.hxx> +#include <tabfrm.hxx> +#include <rowfrm.hxx> +#include <cellfrm.hxx> +#include <sectfrm.hxx> +#include <ftnfrm.hxx> +#include <flyfrm.hxx> +#include <notxtfrm.hxx> +#include "porfld.hxx" +#include "pormulti.hxx" +#include <SwStyleNameMapper.hxx> +#include "itrpaint.hxx" +#include <i18nlangtag/languagetag.hxx> +#include <IMark.hxx> +#include <printdata.hxx> +#include <vprint.hxx> +#include <SwNodeNum.hxx> +#include <calbck.hxx> +#include <frmtool.hxx> +#include <strings.hrc> +#include <frameformats.hxx> +#include <tblafmt.hxx> +#include <authfld.hxx> +#include <dcontact.hxx> + +#include <tools/globname.hxx> +#include <svx/svdobj.hxx> + +#include <stack> +#include <map> +#include <set> +#include <optional> + +using namespace ::com::sun::star; + +#if OSL_DEBUG_LEVEL > 1 + +static std::vector< sal_uInt16 > aStructStack; + +void lcl_DBGCheckStack() +{ + /* NonStructElement = 0 Document = 1 Part = 2 + * Article = 3 Section = 4 Division = 5 + * BlockQuote = 6 Caption = 7 TOC = 8 + * TOCI = 9 Index = 10 Paragraph = 11 + * Heading = 12 H1-6 = 13 - 18 List = 19 + * ListItem = 20 LILabel = 21 LIBody = 22 + * Table = 23 TableRow = 24 TableHeader = 25 + * TableData = 26 Span = 27 Quote = 28 + * Note = 29 Reference = 30 BibEntry = 31 + * Code = 32 Link = 33 Figure = 34 + * Formula = 35 Form = 36 Continued frame = 99 + */ + + sal_uInt16 nElement; + for ( const auto& rItem : aStructStack ) + { + nElement = rItem; + } + (void)nElement; +}; + +#endif + +typedef std::set< tools::Long, lt_TableColumn > TableColumnsMapEntry; +typedef std::pair< SwRect, sal_Int32 > IdMapEntry; +typedef std::vector< IdMapEntry > LinkIdMap; +typedef std::map< const SwTable*, TableColumnsMapEntry > TableColumnsMap; +typedef std::map< const SwNumberTreeNode*, sal_Int32 > NumListIdMap; +typedef std::map< const SwNumberTreeNode*, sal_Int32 > NumListBodyIdMap; +typedef std::set<const void*> FrameTagSet; + +struct SwEnhancedPDFState +{ + TableColumnsMap m_TableColumnsMap; + LinkIdMap m_LinkIdMap; + NumListIdMap m_NumListIdMap; + NumListBodyIdMap m_NumListBodyIdMap; + FrameTagSet m_FrameTagSet; + + LanguageType m_eLanguageDefault; + + struct Span + { + FontLineStyle eUnderline; + FontLineStyle eOverline; + FontStrikeout eStrikeout; + FontEmphasisMark eFontEmphasis; + short nEscapement; + SwFontScript nScript; + LanguageType nLang; + OUString StyleName; + }; + + ::std::optional<Span> m_oCurrentSpan; + ::std::optional<SwTextAttr const*> m_oCurrentLink; + + SwEnhancedPDFState(LanguageType const eLanguageDefault) + : m_eLanguageDefault(eLanguageDefault) + { + } +}; + +namespace +{ +// ODF Style Names: +const char aTableHeadingName[] = "Table Heading"; +const char aQuotations[] = "Quotations"; +const char aCaption[] = "Caption"; +const char aHeading[] = "Heading"; +const char aQuotation[] = "Quotation"; +const char aSourceText[] = "Source Text"; + +// PDF Tag Names: +constexpr OUStringLiteral aDocumentString = u"Document"; +constexpr OUString aDivString = u"Div"_ustr; +constexpr OUStringLiteral aSectString = u"Sect"; +constexpr OUStringLiteral aHString = u"H"; +constexpr OUStringLiteral aH1String = u"H1"; +constexpr OUStringLiteral aH2String = u"H2"; +constexpr OUStringLiteral aH3String = u"H3"; +constexpr OUStringLiteral aH4String = u"H4"; +constexpr OUStringLiteral aH5String = u"H5"; +constexpr OUStringLiteral aH6String = u"H6"; +constexpr OUStringLiteral aH7String = u"H7"; +constexpr OUStringLiteral aH8String = u"H8"; +constexpr OUStringLiteral aH9String = u"H9"; +constexpr OUStringLiteral aH10String = u"H10"; +constexpr OUStringLiteral aListString = u"L"; +constexpr OUStringLiteral aListItemString = u"LI"; +constexpr OUStringLiteral aListLabelString = u"Lbl"; +constexpr OUString aListBodyString = u"LBody"_ustr; +constexpr OUStringLiteral aBlockQuoteString = u"BlockQuote"; +constexpr OUString aCaptionString = u"Caption"_ustr; +constexpr OUStringLiteral aIndexString = u"Index"; +constexpr OUStringLiteral aTOCString = u"TOC"; +constexpr OUStringLiteral aTOCIString = u"TOCI"; +constexpr OUStringLiteral aTableString = u"Table"; +constexpr OUStringLiteral aTRString = u"TR"; +constexpr OUStringLiteral aTDString = u"TD"; +constexpr OUStringLiteral aTHString = u"TH"; +constexpr OUStringLiteral aBibEntryString = u"BibEntry"; +constexpr OUStringLiteral aQuoteString = u"Quote"; +constexpr OUString aSpanString = u"Span"_ustr; +constexpr OUStringLiteral aCodeString = u"Code"; +constexpr OUStringLiteral aFigureString = u"Figure"; +constexpr OUStringLiteral aFormulaString = u"Formula"; +constexpr OUString aLinkString = u"Link"_ustr; +constexpr OUStringLiteral aNoteString = u"Note"; + +// returns true if first paragraph in cell frame has 'table heading' style +bool lcl_IsHeadlineCell( const SwCellFrame& rCellFrame ) +{ + bool bRet = false; + + const SwContentFrame *pCnt = rCellFrame.ContainsContent(); + if ( pCnt && pCnt->IsTextFrame() ) + { + SwTextNode const*const pTextNode = static_cast<const SwTextFrame*>(pCnt)->GetTextNodeForParaProps(); + const SwFormat* pTextFormat = pTextNode->GetFormatColl(); + + OUString sStyleName; + SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl ); + bRet = sStyleName == aTableHeadingName; + } + + // tdf#153935 wild guessing for 1st row based on table autoformat + if (!bRet && !rCellFrame.GetUpper()->GetPrev()) + { + SwTable const*const pTable(rCellFrame.FindTabFrame()->GetTable()); + assert(pTable); + OUString const& rStyleName(pTable->GetTableStyleName()); + if (!rStyleName.isEmpty()) + { + if (SwTableAutoFormat const*const pTableAF = + pTable->GetFrameFormat()->GetDoc()->GetTableStyles().FindAutoFormat(rStyleName)) + { + bRet |= pTableAF->HasHeaderRow(); + } + } + } + + return bRet; +} + +// List all frames for which the NonStructElement tag is set: +bool lcl_IsInNonStructEnv( const SwFrame& rFrame ) +{ + bool bRet = false; + + if ( nullptr != rFrame.FindFooterOrHeader() && + !rFrame.IsHeaderFrame() && !rFrame.IsFooterFrame() ) + { + bRet = true; + } + else if ( rFrame.IsInTab() && !rFrame.IsTabFrame() ) + { + const SwTabFrame* pTabFrame = rFrame.FindTabFrame(); + if ( rFrame.GetUpper() != pTabFrame && + pTabFrame->IsFollow() && pTabFrame->IsInHeadline( rFrame ) ) + bRet = true; + } + + return bRet; +} + +// Generate key from frame for reopening tags: +void const* lcl_GetKeyFromFrame( const SwFrame& rFrame ) +{ + void const* pKey = nullptr; + + if ( rFrame.IsPageFrame() ) + pKey = static_cast<void const *>(&(static_cast<const SwPageFrame&>(rFrame).GetFormat()->getIDocumentSettingAccess())); + else if ( rFrame.IsTextFrame() ) + pKey = static_cast<void const *>(static_cast<const SwTextFrame&>(rFrame).GetTextNodeFirst()); + else if ( rFrame.IsSctFrame() ) + pKey = static_cast<void const *>(static_cast<const SwSectionFrame&>(rFrame).GetSection()); + else if ( rFrame.IsTabFrame() ) + pKey = static_cast<void const *>(static_cast<const SwTabFrame&>(rFrame).GetTable()); + else if ( rFrame.IsRowFrame() ) + pKey = static_cast<void const *>(static_cast<const SwRowFrame&>(rFrame).GetTabLine()); + else if ( rFrame.IsCellFrame() ) + { + const SwTabFrame* pTabFrame = rFrame.FindTabFrame(); + const SwTable* pTable = pTabFrame->GetTable(); + pKey = static_cast<void const *>(& static_cast<const SwCellFrame&>(rFrame).GetTabBox()->FindStartOfRowSpan(*pTable)); + } + else if (rFrame.IsFootnoteFrame()) + { + pKey = static_cast<void const*>(static_cast<SwFootnoteFrame const&>(rFrame).GetAttr()); + } + + return pKey; +} + +bool lcl_HasPreviousParaSameNumRule(SwTextFrame const& rTextFrame, const SwTextNode& rNode) +{ + bool bRet = false; + SwNodeIndex aIdx( rNode ); + const SwDoc& rDoc = rNode.GetDoc(); + const SwNodes& rNodes = rDoc.GetNodes(); + const SwNode* pNode = &rNode; + const SwNumRule* pNumRule = rNode.GetNumRule(); + + while (pNode != rNodes.DocumentSectionStartNode(const_cast<SwNode*>(static_cast<SwNode const *>(&rNode))) ) + { + sw::GotoPrevLayoutTextFrame(aIdx, rTextFrame.getRootFrame()); + + if (aIdx.GetNode().IsTextNode()) + { + const SwTextNode *const pPrevTextNd = sw::GetParaPropsNode( + *rTextFrame.getRootFrame(), *aIdx.GetNode().GetTextNode()); + const SwNumRule * pPrevNumRule = pPrevTextNd->GetNumRule(); + + // We find the previous text node. Now check, if the previous text node + // has the same numrule like rNode: + if ( (pPrevNumRule == pNumRule) && + (!pPrevTextNd->IsOutline() == !rNode.IsOutline())) + bRet = true; + + break; + } + + pNode = &aIdx.GetNode(); + } + return bRet; +} + +bool lcl_TryMoveToNonHiddenField(SwEditShell& rShell, const SwTextNode& rNd, const SwFormatField& rField) +{ + // 1. Check if the whole paragraph is hidden + // 2. Move to the field + // 3. Check for hidden text attribute + if(rNd.IsHidden()) + return false; + if(!rShell.GotoFormatField(rField) || rShell.IsInHiddenRange(/*bSelect=*/false)) + { + rShell.SwCursorShell::ClearMark(); + return false; + } + return true; +}; + +// tdf#157816: try to check if the rectangle contains actual text +::std::vector<SwRect> GetCursorRectsContainingText(SwCursorShell const& rShell) +{ + ::std::vector<SwRect> ret; + SwRects rects; + rShell.GetLayout()->CalcFrameRects(*rShell.GetCursor_(), rects, SwRootFrame::RectsMode::NoAnchoredFlys); + for (SwRect const& rRect : rects) + { + Point center(rRect.Center()); + SwSpecialPos special; + SwCursorMoveState cms(CursorMoveState::NONE); + cms.m_pSpecialPos = &special; + cms.m_bFieldInfo = true; + SwPosition pos(rShell.GetDoc()->GetNodes()); + auto const [pStart, pEnd] = rShell.GetCursor_()->StartEnd(); + if (rShell.GetLayout()->GetModelPositionForViewPoint(&pos, center, &cms) + && *pStart <= pos && pos <= *pEnd) + { + SwRect charRect; + if (rShell.GetCurrFrame(false)->GetCharRect(charRect, pos, &cms, false) + && rRect.Overlaps(charRect)) + { + ret.push_back(rRect); + } + } + // reset stupid static var that may have gotten set now + SwTextCursor::SetRightMargin(false); // WTF is this crap + } + return ret; +} + +} // end namespace + +SwTaggedPDFHelper::SwTaggedPDFHelper( const Num_Info* pNumInfo, + const Frame_Info* pFrameInfo, + const Por_Info* pPorInfo, + OutputDevice const & rOut ) + : m_nEndStructureElement( 0 ), + m_nRestoreCurrentTag( -1 ), + mpNumInfo( pNumInfo ), + mpFrameInfo( pFrameInfo ), + mpPorInfo( pPorInfo ) +{ + mpPDFExtOutDevData = + dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() ); + + if ( !(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()) ) + return; + +#if OSL_DEBUG_LEVEL > 1 + sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement(); + lcl_DBGCheckStack(); +#endif + if ( mpNumInfo ) + BeginNumberedListStructureElements(); + else if ( mpFrameInfo ) + BeginBlockStructureElements(); + else if ( mpPorInfo ) + BeginInlineStructureElements(); + else + BeginTag( vcl::PDFWriter::NonStructElement, OUString() ); + +#if OSL_DEBUG_LEVEL > 1 + nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement(); + lcl_DBGCheckStack(); + (void)nCurrentStruct; +#endif +} + +SwTaggedPDFHelper::~SwTaggedPDFHelper() +{ + if ( !(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()) ) + return; + +#if OSL_DEBUG_LEVEL > 1 + sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement(); + lcl_DBGCheckStack(); +#endif + EndStructureElements(); + +#if OSL_DEBUG_LEVEL > 1 + nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement(); + lcl_DBGCheckStack(); + (void)nCurrentStruct; +#endif +} + +void const* SwDrawContact::GetPDFAnchorStructureElementKey(SdrObject const& rObj) +{ + SwFrame const*const pAnchorFrame(GetAnchoredObj(&rObj)->GetAnchorFrame()); + return pAnchorFrame ? lcl_GetKeyFromFrame(*pAnchorFrame) : nullptr; +} + +bool SwTaggedPDFHelper::CheckReopenTag() +{ + bool bRet = false; + void const* pReopenKey(nullptr); + bool bContinue = false; // in some cases we just have to reopen a tag without early returning + + if ( mpFrameInfo ) + { + const SwFrame& rFrame = mpFrameInfo->mrFrame; + const SwFrame* pKeyFrame = nullptr; + + // Reopen an existing structure element if + // - rFrame is not the first page frame (reopen Document tag) + // - rFrame is a follow frame (reopen Master tag) + // - rFrame is a fly frame anchored at content (reopen Anchor paragraph tag) + // - rFrame is a fly frame anchored at page (reopen Document tag) + // - rFrame is a follow flow row (reopen TableRow tag) + // - rFrame is a cell frame in a follow flow row (reopen TableData tag) + if ( ( rFrame.IsPageFrame() && static_cast<const SwPageFrame&>(rFrame).GetPrev() ) || + ( rFrame.IsFlowFrame() && SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() ) || + (rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetMaster()) || + ( rFrame.IsRowFrame() && rFrame.IsInFollowFlowRow() ) || + ( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetPrevCellLeaf() ) ) + { + pKeyFrame = &rFrame; + } + else if (rFrame.IsFlyFrame() && !mpFrameInfo->m_isLink) + { + const SwFormatAnchor& rAnchor = + static_cast<const SwFlyFrame*>(&rFrame)->GetFormat()->GetAnchor(); + if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) || + (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId())) + { + pKeyFrame = static_cast<const SwFlyFrame&>(rFrame).GetAnchorFrame(); + bContinue = true; + } + } + + if ( pKeyFrame ) + { + void const*const pKey = lcl_GetKeyFromFrame(*pKeyFrame); + FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet); + if (rFrameTagSet.find(pKey) != rFrameTagSet.end() + || rFrame.IsFlyFrame()) // for hell layer flys + { + pReopenKey = pKey; + } + } + } + + if (pReopenKey) + { + // note: it would be possible to get rid of the SetCurrentStructureElement() + // - which is quite ugly - for most cases by recreating the parents until the + // current ancestor, but there are special cases cell frame rowspan > 1 follow + // and footnote frame follow where the parent of the follow is different from + // the parent of the first one, and so in PDFExtOutDevData the wrong parent + // would be restored and used for next elements. + m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement(); + sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pReopenKey); + mpPDFExtOutDevData->SetCurrentStructureElement(id); + + bRet = true; + } + + return bRet && !bContinue; +} + +void SwTaggedPDFHelper::CheckRestoreTag() const +{ + if ( m_nRestoreCurrentTag != -1 ) + { + mpPDFExtOutDevData->SetCurrentStructureElement( m_nRestoreCurrentTag ); + +#if OSL_DEBUG_LEVEL > 1 + aStructStack.pop_back(); +#endif + } +} + +void SwTaggedPDFHelper::OpenTagImpl(void const*const pKey) +{ + sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pKey); + mpPDFExtOutDevData->BeginStructureElement(id); + ++m_nEndStructureElement; + +#if OSL_DEBUG_LEVEL > 1 + aStructStack.push_back( 99 ); +#endif +} + +sal_Int32 SwTaggedPDFHelper::BeginTagImpl(void const*const pKey, + vcl::PDFWriter::StructElement const eType, const OUString& rString) +{ + // write new tag + const sal_Int32 nId = mpPDFExtOutDevData->EnsureStructureElement(pKey); + mpPDFExtOutDevData->InitStructureElement(nId, eType, rString); + mpPDFExtOutDevData->BeginStructureElement(nId); + ++m_nEndStructureElement; + +#if OSL_DEBUG_LEVEL > 1 + aStructStack.push_back( o3tl::narrowing<sal_uInt16>(eType) ); +#endif + + return nId; +} + +void SwTaggedPDFHelper::BeginTag( vcl::PDFWriter::StructElement eType, const OUString& rString ) +{ + void const* pKey(nullptr); + + if (mpFrameInfo) + { + const SwFrame& rFrame = mpFrameInfo->mrFrame; + + if ( ( rFrame.IsPageFrame() && !static_cast<const SwPageFrame&>(rFrame).GetPrev() ) || + ( rFrame.IsFlowFrame() && !SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() && SwFlowFrame::CastFlowFrame(&rFrame)->HasFollow() ) || + rFrame.IsSctFrame() || // all of them, so that opening parent sections works + ( rFrame.IsTextFrame() && rFrame.GetDrawObjs() ) || + (rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetFollow()) || + ( rFrame.IsRowFrame() && rFrame.IsInSplitTableRow() ) || + ( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetNextCellLeaf() ) ) + { + pKey = lcl_GetKeyFromFrame(rFrame); + + if (pKey) + { + FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet); + assert(rFrameTagSet.find(pKey) == rFrameTagSet.end()); + rFrameTagSet.emplace(pKey); + } + } + } + + sal_Int32 const nId = BeginTagImpl(pKey, eType, rString); + + // Store the id of the current structure element if + // - it is a list structure element + // - it is a list body element with children + // - rFrame is the first page frame + // - rFrame is a master frame + // - rFrame has objects anchored to it + // - rFrame is a row frame or cell frame in a split table row + + if ( mpNumInfo ) + { + const SwTextFrame& rTextFrame = mpNumInfo->mrFrame; + SwTextNode const*const pTextNd = rTextFrame.GetTextNodeForParaProps(); + const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame()); + + if ( vcl::PDFWriter::List == eType ) + { + NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap); + rNumListIdMap[ pNodeNum ] = nId; + } + else if ( vcl::PDFWriter::LIBody == eType ) + { + NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap); + rNumListBodyIdMap[ pNodeNum ] = nId; + } + } + + SetAttributes( eType ); +} + +void SwTaggedPDFHelper::EndTag() +{ + mpPDFExtOutDevData->EndStructureElement(); + +#if OSL_DEBUG_LEVEL > 1 + aStructStack.pop_back(); +#endif +} + +namespace { + + // link the link annotation to the link structured element + void LinkLinkLink(vcl::PDFExtOutDevData & rPDFExtOutDevData, SwRect const& rRect) + { + 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()) + { + sal_Int32 nLinkId = (*aIter).second; + rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::PDFWriter::LinkAnnotation, nLinkId); + } + } +} + +// Sets the attributes according to the structure type. +void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType ) +{ + sal_Int32 nVal; + + /* + * ATTRIBUTES FOR BLSE + */ + if ( mpFrameInfo ) + { + vcl::PDFWriter::StructAttributeValue eVal; + const SwFrame* pFrame = &mpFrameInfo->mrFrame; + SwRectFnSet aRectFnSet(pFrame); + + bool bPlacement = false; + bool bWritingMode = false; + bool bSpaceBefore = false; + bool bSpaceAfter = false; + bool bStartIndent = false; + bool bEndIndent = false; + bool bTextIndent = false; + bool bTextAlign = false; + bool bWidth = false; + bool bHeight = false; + bool bBox = false; + bool bRowSpan = false; + bool bAltText = false; + + // Check which attributes to set: + + switch ( eType ) + { + case vcl::PDFWriter::Document : + bWritingMode = true; + break; + + case vcl::PDFWriter::Note: + bPlacement = true; + break; + + case vcl::PDFWriter::Table : + bPlacement = + bWritingMode = + bSpaceBefore = + bSpaceAfter = + bStartIndent = + bEndIndent = + bWidth = + bHeight = + bBox = true; + break; + + case vcl::PDFWriter::TableRow : + bPlacement = + bWritingMode = true; + break; + + case vcl::PDFWriter::TableHeader : + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope, vcl::PDFWriter::Column); + [[fallthrough]]; + case vcl::PDFWriter::TableData : + bPlacement = + bWritingMode = + bWidth = + bHeight = + bRowSpan = true; + break; + + case vcl::PDFWriter::Caption: + if (pFrame->IsSctFrame()) + { + break; + } + [[fallthrough]]; + case vcl::PDFWriter::H1 : + case vcl::PDFWriter::H2 : + case vcl::PDFWriter::H3 : + case vcl::PDFWriter::H4 : + case vcl::PDFWriter::H5 : + case vcl::PDFWriter::H6 : + case vcl::PDFWriter::Paragraph : + case vcl::PDFWriter::Heading : + case vcl::PDFWriter::BlockQuote : + + bPlacement = + bWritingMode = + bSpaceBefore = + bSpaceAfter = + bStartIndent = + bEndIndent = + bTextIndent = + bTextAlign = true; + break; + + case vcl::PDFWriter::Formula : + case vcl::PDFWriter::Figure : + bAltText = + bPlacement = + bWidth = + bHeight = + bBox = true; + break; + + case vcl::PDFWriter::Division: + if (pFrame->IsFlyFrame()) // this can be something else too + { + bAltText = true; + bBox = true; + } + break; + + case vcl::PDFWriter::NonStructElement: + if (pFrame->IsHeaderFrame() || pFrame->IsFooterFrame()) + { + // ISO 14289-1:2014, Clause: 7.8 + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Type, vcl::PDFWriter::Pagination); + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Subtype, + pFrame->IsHeaderFrame() + ? vcl::PDFWriter::Header + : vcl::PDFWriter::Footer); + } + break; + + default : + break; + } + + // Set the attributes: + + if ( bPlacement ) + { + eVal = vcl::PDFWriter::TableHeader == eType || + vcl::PDFWriter::TableData == eType ? + vcl::PDFWriter::Inline : + vcl::PDFWriter::Block; + + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::Placement, eVal ); + } + + if ( bWritingMode ) + { + eVal = pFrame->IsVertical() ? + vcl::PDFWriter::TbRl : + pFrame->IsRightToLeft() ? + vcl::PDFWriter::RlTb : + vcl::PDFWriter::LrTb; + + if ( vcl::PDFWriter::LrTb != eVal ) + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::WritingMode, eVal ); + } + + if ( bSpaceBefore ) + { + nVal = aRectFnSet.GetTopMargin(*pFrame); + if ( 0 != nVal ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::SpaceBefore, nVal ); + } + + if ( bSpaceAfter ) + { + nVal = aRectFnSet.GetBottomMargin(*pFrame); + if ( 0 != nVal ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::SpaceAfter, nVal ); + } + + if ( bStartIndent ) + { + nVal = aRectFnSet.GetLeftMargin(*pFrame); + if ( 0 != nVal ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::StartIndent, nVal ); + } + + if ( bEndIndent ) + { + nVal = aRectFnSet.GetRightMargin(*pFrame); + if ( 0 != nVal ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::EndIndent, nVal ); + } + + if ( bTextIndent ) + { + OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" ); + const SvxFirstLineIndentItem& rFirstLine( + static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent()); + nVal = rFirstLine.GetTextFirstLineOffset(); + if ( 0 != nVal ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::TextIndent, nVal ); + } + + if ( bTextAlign ) + { + OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" ); + const SwAttrSet& aSet = static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet(); + const SvxAdjust nAdjust = aSet.GetAdjust().GetAdjust(); + if ( SvxAdjust::Block == nAdjust || SvxAdjust::Center == nAdjust || + ( (pFrame->IsRightToLeft() && SvxAdjust::Left == nAdjust) || + (!pFrame->IsRightToLeft() && SvxAdjust::Right == nAdjust) ) ) + { + eVal = SvxAdjust::Block == nAdjust ? + vcl::PDFWriter::Justify : + SvxAdjust::Center == nAdjust ? + vcl::PDFWriter::Center : + vcl::PDFWriter::End; + + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextAlign, eVal ); + } + } + + // ISO 14289-1:2014, Clause: 7.3 + // ISO 14289-1:2014, Clause: 7.7 + // For images (but not embedded objects), an ObjectInfoPrimitive2D is + // created, but it's not evaluated by VclMetafileProcessor2D any more; + // that would require producing StructureTagPrimitive2D here but that + // looks impossible so instead duplicate the code that sets the Alt + // text here again. + if (bAltText) + { + SwFlyFrameFormat const& rFly(*static_cast<SwFlyFrame const*>(pFrame)->GetFormat()); + OUString const sep( + (rFly.GetObjTitle().isEmpty() || rFly.GetObjDescription().isEmpty()) + ? OUString() : OUString(" - ")); + OUString const altText(rFly.GetObjTitle() + sep + rFly.GetObjDescription()); + if (!altText.isEmpty()) + { + mpPDFExtOutDevData->SetAlternateText(altText); + } + } + + if ( bWidth ) + { + nVal = aRectFnSet.GetWidth(pFrame->getFrameArea()); + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Width, nVal ); + } + + if ( bHeight ) + { + nVal = aRectFnSet.GetHeight(pFrame->getFrameArea()); + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Height, nVal ); + } + + if ( bBox ) + { + // BBox only for non-split tables: + if ( vcl::PDFWriter::Table != eType || + ( pFrame->IsTabFrame() && + !static_cast<const SwTabFrame*>(pFrame)->IsFollow() && + !static_cast<const SwTabFrame*>(pFrame)->HasFollow() ) ) + { + mpPDFExtOutDevData->SetStructureBoundingBox(pFrame->getFrameArea().SVRect()); + } + } + + if ( bRowSpan ) + { + if ( pFrame->IsCellFrame() ) + { + const SwCellFrame* pThisCell = static_cast<const SwCellFrame*>(pFrame); + nVal = pThisCell->GetTabBox()->getRowSpan(); + if ( nVal > 1 ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::RowSpan, nVal ); + + // calculate colspan: + const SwTabFrame* pTabFrame = pThisCell->FindTabFrame(); + const SwTable* pTable = pTabFrame->GetTable(); + + SwRectFnSet fnRectX(pTabFrame); + + const TableColumnsMapEntry& rCols(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap[pTable]); + + const tools::Long nLeft = fnRectX.GetLeft(pThisCell->getFrameArea()); + const tools::Long nRight = fnRectX.GetRight(pThisCell->getFrameArea()); + const TableColumnsMapEntry::const_iterator aLeftIter = rCols.find( nLeft ); + const TableColumnsMapEntry::const_iterator aRightIter = rCols.find( nRight ); + + OSL_ENSURE( aLeftIter != rCols.end() && aRightIter != rCols.end(), "Colspan trouble" ); + if ( aLeftIter != rCols.end() && aRightIter != rCols.end() ) + { + nVal = std::distance( aLeftIter, aRightIter ); + if ( nVal > 1 ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::ColSpan, nVal ); + } + } + } + + if (mpFrameInfo->m_isLink) + { + SwRect const aRect(mpFrameInfo->mrFrame.getFrameArea()); + LinkLinkLink(*mpPDFExtOutDevData, aRect); + } + } + + /* + * ATTRIBUTES FOR ILSE + */ + else if ( mpPorInfo ) + { + const SwLinePortion* pPor = &mpPorInfo->mrPor; + const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo(); + + bool bActualText = false; + bool bBaselineShift = false; + bool bTextDecorationType = false; + bool bLinkAttribute = false; + bool bLanguage = false; + + // Check which attributes to set: + + switch ( eType ) + { + case vcl::PDFWriter::Span : + case vcl::PDFWriter::Quote : + case vcl::PDFWriter::Code : + if( PortionType::HyphenStr == pPor->GetWhichPor() || PortionType::SoftHyphenStr == pPor->GetWhichPor() || + PortionType::Hyphen == pPor->GetWhichPor() || PortionType::SoftHyphen == pPor->GetWhichPor() ) + bActualText = true; + else + { + bBaselineShift = + bTextDecorationType = + bLanguage = true; + } + break; + + case vcl::PDFWriter::Link : + bTextDecorationType = + bBaselineShift = + bLinkAttribute = + bLanguage = true; + break; + + case vcl::PDFWriter::BibEntry : + bTextDecorationType = + bBaselineShift = + bLinkAttribute = + bLanguage = true; + break; + + case vcl::PDFWriter::RT: + { + SwRubyPortion const*const pRuby(static_cast<SwRubyPortion const*>(pPor)); + vcl::PDFWriter::StructAttributeValue nAlign = {}; + switch (pRuby->GetAdjustment()) + { + case text::RubyAdjust_LEFT: + nAlign = vcl::PDFWriter::RStart; + break; + case text::RubyAdjust_CENTER: + nAlign = vcl::PDFWriter::RCenter; + break; + case text::RubyAdjust_RIGHT: + nAlign = vcl::PDFWriter::REnd; + break; + case text::RubyAdjust_BLOCK: + nAlign = vcl::PDFWriter::RJustify; + break; + case text::RubyAdjust_INDENT_BLOCK: + nAlign = vcl::PDFWriter::RDistribute; + break; + default: + assert(false); + break; + } + ::std::optional<vcl::PDFWriter::StructAttributeValue> oPos; + switch (pRuby->GetRubyPosition()) + { + case RubyPosition::ABOVE: + oPos = vcl::PDFWriter::RBefore; + break; + case RubyPosition::BELOW: + oPos = vcl::PDFWriter::RAfter; + break; + case RubyPosition::RIGHT: + break; // no such thing??? + } + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::RubyAlign, nAlign); + if (oPos) + { + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::RubyPosition, *oPos); + } + } + break; + + default: + break; + } + + if ( bActualText ) + { + OUString aActualText; + if (pPor->GetWhichPor() == PortionType::SoftHyphen || pPor->GetWhichPor() == PortionType::Hyphen) + aActualText = OUString(u'\x00ad'); // soft hyphen + else + aActualText = rInf.GetText().copy(sal_Int32(rInf.GetIdx()), sal_Int32(pPor->GetLen())); + mpPDFExtOutDevData->SetActualText( aActualText ); + } + + if ( bBaselineShift ) + { + // TODO: Calculate correct values! + nVal = rInf.GetFont()->GetEscapement(); + if ( nVal > 0 ) nVal = 33; + else if ( nVal < 0 ) nVal = -33; + + if ( 0 != nVal ) + { + nVal = nVal * pPor->Height() / 100; + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::BaselineShift, nVal ); + } + } + + if ( bTextDecorationType ) + { + if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() ) + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Underline ); + if ( LINESTYLE_NONE != rInf.GetFont()->GetOverline() ) + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Overline ); + if ( STRIKEOUT_NONE != rInf.GetFont()->GetStrikeout() ) + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::LineThrough ); + if ( FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() ) + mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Overline ); + } + + if ( bLanguage ) + { + + const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage(); + const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault); + + if ( nDefaultLang != nCurrentLanguage ) + mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Language, static_cast<sal_uInt16>(nCurrentLanguage) ); + } + + if ( bLinkAttribute ) + { + SwRect aPorRect; + rInf.CalcRect( *pPor, &aPorRect ); + LinkLinkLink(*mpPDFExtOutDevData, aPorRect); + } + } + else if (mpNumInfo && eType == vcl::PDFWriter::List) + { + SwTextFrame const& rFrame(mpNumInfo->mrFrame); + SwTextNode const& rNode(*rFrame.GetTextNodeForParaProps()); + SwNumRule const*const pNumRule = rNode.GetNumRule(); + assert(pNumRule); // was required for List + + auto ToPDFListNumbering = [](SvxNumberFormat const& rFormat) { + switch (rFormat.GetNumberingType()) + { + case css::style::NumberingType::CHARS_UPPER_LETTER: + return vcl::PDFWriter::UpperAlpha; + case css::style::NumberingType::CHARS_LOWER_LETTER: + return vcl::PDFWriter::LowerAlpha; + case css::style::NumberingType::ROMAN_UPPER: + return vcl::PDFWriter::UpperRoman; + case css::style::NumberingType::ROMAN_LOWER: + return vcl::PDFWriter::LowerRoman; + case css::style::NumberingType::ARABIC: + return vcl::PDFWriter::Decimal; + case css::style::NumberingType::CHAR_SPECIAL: + switch (rFormat.GetBulletChar()) + { + case u'\u2022': case u'\uE12C': case u'\uE01E': case u'\uE437': + return vcl::PDFWriter::Disc; + case u'\u2218': case u'\u25CB': case u'\u25E6': + return vcl::PDFWriter::Circle; + case u'\u25A0': case u'\u25AA': case u'\uE00A': + return vcl::PDFWriter::Square; + default: + return vcl::PDFWriter::NONE; + } + default: // the other 50 types + return vcl::PDFWriter::NONE; + } + }; + + // Note: for every level, BeginNumberedListStructureElements() produces + // a separate List element, so even though in PDF this is limited to + // the whole List we can just export the current level here. + vcl::PDFWriter::StructAttributeValue const value( + ToPDFListNumbering(pNumRule->Get(rNode.GetActualListLevel()))); + // ISO 14289-1:2014, Clause: 7.6 + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::ListNumbering, value); + } +} + +void SwTaggedPDFHelper::BeginNumberedListStructureElements() +{ + OSL_ENSURE( mpNumInfo, "List without mpNumInfo?" ); + if ( !mpNumInfo ) + return; + + const SwFrame& rFrame = mpNumInfo->mrFrame; + assert(rFrame.IsTextFrame()); + const SwTextFrame& rTextFrame = static_cast<const SwTextFrame&>(rFrame); + + // Lowers of NonStructureElements should not be considered: + if (lcl_IsInNonStructEnv(rTextFrame)) + return; + + // do it for the first one in the follow chain that has content + for (SwFlowFrame const* pPrecede = rTextFrame.GetPrecede(); pPrecede; pPrecede = pPrecede->GetPrecede()) + { + SwTextFrame const*const pText(static_cast<SwTextFrame const*>(pPrecede)); + if (!pText->HasPara() || pText->GetPara()->HasContentPortions()) + { + return; + } + } + + const SwTextNode *const pTextNd = rTextFrame.GetTextNodeForParaProps(); + const SwNumRule* pNumRule = pTextNd->GetNumRule(); + const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame()); + + const bool bNumbered = !pTextNd->IsOutline() && pNodeNum && pNodeNum->GetParent() && pNumRule; + + // Check, if we have to reopen a list or a list body: + // First condition: + // Paragraph is numbered/bulleted + if ( !bNumbered ) + return; + + const SwNumberTreeNode* pParent = pNodeNum->GetParent(); + const bool bSameNumbering = lcl_HasPreviousParaSameNumRule(rTextFrame, *pTextNd); + + // Second condition: current numbering is not 'interrupted' + if ( bSameNumbering ) + { + sal_Int32 nReopenTag = -1; + + // Two cases: + // 1. We have to reopen an existing list body tag: + // - If the current node is either the first child of its parent + // and its level > 1 or + // - Numbering should restart at the current node and its level > 1 + // - The current item has no label + const bool bNewSubListStart = pParent->GetParent() && (pParent->IsFirst( pNodeNum ) || pTextNd->IsListRestart() ); + const bool bNoLabel = !pTextNd->IsCountedInList() && !pTextNd->IsListRestart(); + if ( bNewSubListStart || bNoLabel ) + { + // Fine, we try to reopen the appropriate list body + NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap); + + if ( bNewSubListStart ) + { + // The list body tag associated with the parent has to be reopened + // to start a new list inside the list body + NumListBodyIdMap::const_iterator aIter; + + do + aIter = rNumListBodyIdMap.find( pParent ); + while ( aIter == rNumListBodyIdMap.end() && nullptr != ( pParent = pParent->GetParent() ) ); + + if ( aIter != rNumListBodyIdMap.end() ) + nReopenTag = (*aIter).second; + } + else // if(bNoLabel) + { + // The list body tag of a 'counted' predecessor has to be reopened + const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true); + while ( pPrevious ) + { + if ( pPrevious->IsCounted()) + { + // get id of list body tag + const NumListBodyIdMap::const_iterator aIter = rNumListBodyIdMap.find( pPrevious ); + if ( aIter != rNumListBodyIdMap.end() ) + { + nReopenTag = (*aIter).second; + break; + } + } + pPrevious = pPrevious->GetPred(true); + } + } + } + // 2. We have to reopen an existing list tag: + else if ( !pParent->IsFirst( pNodeNum ) && !pTextNd->IsListRestart() ) + { + // any other than the first node in a list level has to reopen the current + // list. The current list is associated in a map with the first child of the list: + NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap); + + // Search backwards and check if any of the previous nodes has a list associated with it: + const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true); + while ( pPrevious ) + { + // get id of list tag + const NumListIdMap::const_iterator aIter = rNumListIdMap.find( pPrevious ); + if ( aIter != rNumListIdMap.end() ) + { + nReopenTag = (*aIter).second; + break; + } + + pPrevious = pPrevious->GetPred(true); + } + } + + if ( -1 != nReopenTag ) + { + m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement(); + mpPDFExtOutDevData->SetCurrentStructureElement( nReopenTag ); + +#if OSL_DEBUG_LEVEL > 1 + aStructStack.push_back( 99 ); +#endif + } + } + else + { + // clear list maps in case a list has been interrupted + NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap); + rNumListIdMap.clear(); + NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap); + rNumListBodyIdMap.clear(); + } + + // New tags: + const bool bNewListTag = (pNodeNum->GetParent()->IsFirst( pNodeNum ) || pTextNd->IsListRestart() || !bSameNumbering); + const bool bNewItemTag = bNewListTag || pTextNd->IsCountedInList(); // If the text node is not counted, we do not start a new list item: + + if ( bNewListTag ) + BeginTag( vcl::PDFWriter::List, aListString ); + + if ( bNewItemTag ) + { + BeginTag( vcl::PDFWriter::ListItem, aListItemString ); + assert(rTextFrame.GetPara()); + // check whether to open LBody now or delay until after Lbl + if (!rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering)) + { + BeginTag(vcl::PDFWriter::LIBody, aListBodyString); + } + } +} + +void SwTaggedPDFHelper::BeginBlockStructureElements() +{ + const SwFrame* pFrame = &mpFrameInfo->mrFrame; + + // Lowers of NonStructureElements should not be considered: + + if (lcl_IsInNonStructEnv(*pFrame) && !pFrame->IsFlyFrame()) + return; + + // Check if we have to reopen an existing structure element. + // This has to be done e.g., if pFrame is a follow frame. + if ( CheckReopenTag() ) + return; + + sal_uInt16 nPDFType = USHRT_MAX; + OUString aPDFType; + + switch ( pFrame->GetType() ) + { + /* + * GROUPING ELEMENTS + */ + + case SwFrameType::Page : + + // Document: Document + + nPDFType = vcl::PDFWriter::Document; + aPDFType = aDocumentString; + break; + + case SwFrameType::Header : + case SwFrameType::Footer : + + // Header, Footer: NonStructElement + + nPDFType = vcl::PDFWriter::NonStructElement; + break; + + case SwFrameType::FtnCont : + + // Footnote container: Division + + nPDFType = vcl::PDFWriter::Division; + aPDFType = aDivString; + break; + + case SwFrameType::Ftn : + + // Footnote frame: Note + + // Note: vcl::PDFWriter::Note is actually a ILSE. Nevertheless + // we treat it like a grouping element! + nPDFType = vcl::PDFWriter::Note; + aPDFType = aNoteString; + break; + + case SwFrameType::Section : + + // Section: TOX, Index, or Sect + + { + const SwSection* pSection = + static_cast<const SwSectionFrame*>(pFrame)->GetSection(); + + // open all parent sections, so that the SEs of sections + // are nested in the same way as their SwSectionNodes + std::vector<SwSection const*> parents; + for (SwSection const* pParent = pSection->GetParent(); + pParent != nullptr; pParent = pParent->GetParent()) + { + parents.push_back(pParent); + } + for (auto it = parents.rbegin(); it != parents.rend(); ++it) + { + // key is the SwSection - see lcl_GetKeyFromFrame() + OpenTagImpl(*it); + } + + FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet); + if (rFrameTagSet.find(pSection) != rFrameTagSet.end()) + { + // special case: section may have *multiple* master frames, + // when it is interrupted by nested section - reopen! + OpenTagImpl(pSection); + break; + } + else if (SectionType::ToxHeader == pSection->GetType()) + { + nPDFType = vcl::PDFWriter::Caption; + aPDFType = aCaptionString; + } + else if (SectionType::ToxContent == pSection->GetType()) + { + const SwTOXBase* pTOXBase = pSection->GetTOXBase(); + if ( pTOXBase ) + { + if ( TOX_INDEX == pTOXBase->GetType() ) + { + nPDFType = vcl::PDFWriter::Index; + aPDFType = aIndexString; + } + else + { + nPDFType = vcl::PDFWriter::TOC; + aPDFType = aTOCString; + } + } + } + else if ( SectionType::Content == pSection->GetType() ) + { + nPDFType = vcl::PDFWriter::Section; + aPDFType = aSectString; + } + } + break; + + /* + * BLOCK-LEVEL STRUCTURE ELEMENTS + */ + + case SwFrameType::Txt : + { + SwTextFrame const& rTextFrame(*static_cast<const SwTextFrame*>(pFrame)); + const SwTextNode *const pTextNd(rTextFrame.GetTextNodeForParaProps()); + + // lazy open LBody after Lbl + if (!pTextNd->IsOutline() + && rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering)) + { + sal_Int32 const nId = BeginTagImpl(nullptr, vcl::PDFWriter::LIBody, aListBodyString); + SwNodeNum const*const pNodeNum(pTextNd->GetNum(rTextFrame.getRootFrame())); + NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap); + rNumListBodyIdMap[ pNodeNum ] = nId; + } + + const SwFormat* pTextFormat = pTextNd->GetFormatColl(); + const SwFormat* pParentTextFormat = pTextFormat ? pTextFormat->DerivedFrom() : nullptr; + + OUString sStyleName; + OUString sParentStyleName; + + if ( pTextFormat) + SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl ); + if ( pParentTextFormat) + SwStyleNameMapper::FillProgName( pParentTextFormat->GetName(), sParentStyleName, SwGetPoolIdFromName::TxtColl ); + + // This is the default. If the paragraph could not be mapped to + // any of the standard pdf tags, we write a user defined tag + // <stylename> with role = P + nPDFType = vcl::PDFWriter::Paragraph; + aPDFType = sStyleName; + + // Quotations: BlockQuote + + if (sStyleName == aQuotations) + { + nPDFType = vcl::PDFWriter::BlockQuote; + aPDFType = aBlockQuoteString; + } + + // Caption: Caption + + else if (sStyleName == aCaption) + { + nPDFType = vcl::PDFWriter::Caption; + aPDFType = aCaptionString; + } + + // Caption: Caption + + else if (sParentStyleName == aCaption) + { + nPDFType = vcl::PDFWriter::Caption; + aPDFType = sStyleName + aCaptionString; + } + + // Heading: H + + else if (sStyleName == aHeading) + { + nPDFType = vcl::PDFWriter::Heading; + aPDFType = aHString; + } + + // Heading: H1 - H6 + + if (int nRealLevel = pTextNd->GetAttrOutlineLevel() - 1; + nRealLevel >= 0 + && !pTextNd->IsInRedlines() + && sw::IsParaPropsNode(*pFrame->getRootFrame(), *pTextNd)) + { + switch(nRealLevel) + { + case 0 : + aPDFType = aH1String; + break; + case 1 : + aPDFType = aH2String; + break; + case 2 : + aPDFType = aH3String; + break; + case 3 : + aPDFType = aH4String; + break; + case 4 : + aPDFType = aH5String; + break; + case 5: + aPDFType = aH6String; + break; + case 6: + aPDFType = aH7String; + break; + case 7: + aPDFType = aH8String; + break; + case 8: + aPDFType = aH9String; + break; + case 9: + aPDFType = aH10String; + break; + default: + assert(false); + break; + } + + // PDF/UA allows unlimited headings, but PDF only up to H6 + // ... and apparently the extra H7.. must be declared in + // RoleMap, or veraPDF complains. + nRealLevel = std::min(nRealLevel, 5); + nPDFType = o3tl::narrowing<sal_uInt16>(vcl::PDFWriter::H1 + nRealLevel); + } + + // Section: TOCI + + else if ( pFrame->IsInSct() ) + { + const SwSectionFrame* pSctFrame = pFrame->FindSctFrame(); + const SwSection* pSection = pSctFrame->GetSection(); + + if ( SectionType::ToxContent == pSection->GetType() ) + { + const SwTOXBase* pTOXBase = pSection->GetTOXBase(); + if ( pTOXBase && TOX_INDEX != pTOXBase->GetType() ) + { + // Special case: Open additional TOCI tag: + BeginTagImpl(nullptr, vcl::PDFWriter::TOCI, aTOCIString); + } + } + } + } + break; + + case SwFrameType::Tab : + + // TabFrame: Table + + nPDFType = vcl::PDFWriter::Table; + aPDFType = aTableString; + + { + // set up table column data: + const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pFrame); + const SwTable* pTable = pTabFrame->GetTable(); + + TableColumnsMap& rTableColumnsMap(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap); + const TableColumnsMap::const_iterator aIter = rTableColumnsMap.find( pTable ); + + if ( aIter == rTableColumnsMap.end() ) + { + SwRectFnSet aRectFnSet(pTabFrame); + TableColumnsMapEntry& rCols = rTableColumnsMap[ pTable ]; + + const SwTabFrame* pMasterFrame = pTabFrame->IsFollow() ? pTabFrame->FindMaster( true ) : pTabFrame; + + while ( pMasterFrame ) + { + const SwRowFrame* pRowFrame = static_cast<const SwRowFrame*>(pMasterFrame->GetLower()); + + while ( pRowFrame ) + { + const SwFrame* pCellFrame = pRowFrame->GetLower(); + + const tools::Long nLeft = aRectFnSet.GetLeft(pCellFrame->getFrameArea()); + rCols.insert( nLeft ); + + while ( pCellFrame ) + { + const tools::Long nRight = aRectFnSet.GetRight(pCellFrame->getFrameArea()); + rCols.insert( nRight ); + pCellFrame = pCellFrame->GetNext(); + } + pRowFrame = static_cast<const SwRowFrame*>(pRowFrame->GetNext()); + } + pMasterFrame = pMasterFrame->GetFollow(); + } + } + } + + break; + + /* + * TABLE ELEMENTS + */ + + case SwFrameType::Row : + + // RowFrame: TR + + if ( !static_cast<const SwRowFrame*>(pFrame)->IsRepeatedHeadline() ) + { + nPDFType = vcl::PDFWriter::TableRow; + aPDFType = aTRString; + } + else + { + nPDFType = vcl::PDFWriter::NonStructElement; + } + break; + + case SwFrameType::Cell : + + // CellFrame: TH, TD + + { + const SwTabFrame* pTable = static_cast<const SwCellFrame*>(pFrame)->FindTabFrame(); + if ( pTable->IsInHeadline( *pFrame ) || lcl_IsHeadlineCell( *static_cast<const SwCellFrame*>(pFrame) ) ) + { + nPDFType = vcl::PDFWriter::TableHeader; + aPDFType = aTHString; + } + else + { + nPDFType = vcl::PDFWriter::TableData; + aPDFType = aTDString; + } + } + break; + + /* + * ILLUSTRATION + */ + + case SwFrameType::Fly : + + // FlyFrame: Figure, Formula, Control + // fly in content or fly at page + if (mpFrameInfo->m_isLink) + { // tdf#154939 additional inner link element for flys + nPDFType = vcl::PDFWriter::Link; + aPDFType = aLinkString; + } + else + { + const SwFlyFrame* pFly = static_cast<const SwFlyFrame*>(pFrame); + if (pFly->GetAnchorFrame()->FindFooterOrHeader() != nullptr + || pFly->GetFrameFormat().GetAttrSet().Get(RES_DECORATIVE).GetValue()) + { + nPDFType = vcl::PDFWriter::NonStructElement; + } + else if (pFly->Lower() && pFly->Lower()->IsNoTextFrame()) + { + bool bFormula = false; + + const SwNoTextFrame* pNoTextFrame = static_cast<const SwNoTextFrame*>(pFly->Lower()); + SwOLENode* pOLENd = const_cast<SwOLENode*>(pNoTextFrame->GetNode()->GetOLENode()); + if ( pOLENd ) + { + SwOLEObj& aOLEObj = pOLENd->GetOLEObj(); + uno::Reference< embed::XEmbeddedObject > aRef = aOLEObj.GetOleRef(); + if ( aRef.is() ) + { + bFormula = 0 != SotExchange::IsMath( SvGlobalName( aRef->getClassID() ) ); + } + } + if ( bFormula ) + { + nPDFType = vcl::PDFWriter::Formula; + aPDFType = aFormulaString; + } + else + { + nPDFType = vcl::PDFWriter::Figure; + aPDFType = aFigureString; + } + } + else + { + nPDFType = vcl::PDFWriter::Division; + aPDFType = aDivString; + } + } + break; + + default: break; + } + + if ( USHRT_MAX != nPDFType ) + { + BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType ); + } +} + +void SwTaggedPDFHelper::EndStructureElements() +{ + if (mpFrameInfo != nullptr) + { + if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan) + { // close span at end of paragraph + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset(); + ++m_nEndStructureElement; + } + if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) + { // close link at end of paragraph + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset(); + ++m_nEndStructureElement; + } + } + + while ( m_nEndStructureElement > 0 ) + { + EndTag(); + --m_nEndStructureElement; + } + + CheckRestoreTag(); +} + +void SwTaggedPDFHelper::EndCurrentLink(OutputDevice const& rOut) +{ + vcl::PDFExtOutDevData *const pPDFExtOutDevData( + dynamic_cast<vcl::PDFExtOutDevData *>(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) + { + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset(); + } + if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) + { + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset(); + } +} + +void SwTaggedPDFHelper::EndCurrentSpan() +{ + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset(); + EndTag(); // close span +} + +void SwTaggedPDFHelper::CreateCurrentSpan( + SwTextPaintInfo const& rInf, OUString const& rStyleName) +{ + assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan); + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.emplace( + SwEnhancedPDFState::Span{ + rInf.GetFont()->GetUnderline(), + rInf.GetFont()->GetOverline(), + rInf.GetFont()->GetStrikeout(), + rInf.GetFont()->GetEmphasisMark(), + rInf.GetFont()->GetEscapement(), + rInf.GetFont()->GetActual(), + rInf.GetFont()->GetLanguage(), + rStyleName}); + // leave it open to let next portion decide to merge or close + --m_nEndStructureElement; +} + +bool SwTaggedPDFHelper::CheckContinueSpan( + SwTextPaintInfo const& rInf, std::u16string_view const rStyleName, + SwTextAttr const*const pInetFormatAttr) +{ + // for now, don't create span inside of link - this should be very rare + // situation and it looks complicated to implement. + assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan + || !mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink); + if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) + { + if (pInetFormatAttr && pInetFormatAttr == *mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink) + { + return true; + } + else + { + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset(); + EndTag(); + return false; + } + } + if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan && pInetFormatAttr) + { + EndCurrentSpan(); + return false; + } + + if (!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan) + return false; + + SwEnhancedPDFState::Span const& rCurrent(*mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan); + + bool const ret(rCurrent.eUnderline == rInf.GetFont()->GetUnderline() + && rCurrent.eOverline == rInf.GetFont()->GetOverline() + && rCurrent.eStrikeout == rInf.GetFont()->GetStrikeout() + && rCurrent.eFontEmphasis == rInf.GetFont()->GetEmphasisMark() + && rCurrent.nEscapement == rInf.GetFont()->GetEscapement() + && rCurrent.nScript == rInf.GetFont()->GetActual() + && rCurrent.nLang == rInf.GetFont()->GetLanguage() + && rCurrent.StyleName == rStyleName); + if (!ret) + { + EndCurrentSpan(); + } + return ret; +} + +void SwTaggedPDFHelper::BeginInlineStructureElements() +{ + const SwLinePortion* pPor = &mpPorInfo->mrPor; + const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo(); + const SwTextFrame* pFrame = rInf.GetTextFrame(); + + // Lowers of NonStructureElements should not be considered: + + if ( lcl_IsInNonStructEnv( *pFrame ) ) + return; + + std::pair<SwTextNode const*, sal_Int32> const pos( + pFrame->MapViewToModel(rInf.GetIdx())); + SwTextAttr const*const pInetFormatAttr = + pos.first->GetTextAttrAt(pos.second, RES_TXTATR_INETFMT); + + OUString sStyleName; + if (!pInetFormatAttr) + { + std::vector<SwTextAttr *> const charAttrs( + pos.first->GetTextAttrsAt(pos.second, RES_TXTATR_CHARFMT)); + // TODO: handle more than 1 char style? + const SwCharFormat* pCharFormat = (charAttrs.size()) + ? (*charAttrs.begin())->GetCharFormat().GetCharFormat() : nullptr; + if (pCharFormat) + SwStyleNameMapper::FillProgName( pCharFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl ); + } + + // note: ILSE may be nested, so only end the span if needed to start new one + bool const isContinueSpan(CheckContinueSpan(rInf, sStyleName, pInetFormatAttr)); + + sal_uInt16 nPDFType = USHRT_MAX; + OUString aPDFType; + + switch ( pPor->GetWhichPor() ) + { + case PortionType::Hyphen : + case PortionType::SoftHyphen : + // Check for alternative spelling: + case PortionType::HyphenStr : + case PortionType::SoftHyphenStr : + nPDFType = vcl::PDFWriter::Span; + 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 : + { + // Check for Link: + if( pInetFormatAttr ) + { + if (!isContinueSpan) + { + nPDFType = vcl::PDFWriter::Link; + aPDFType = aLinkString; + assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink); + mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace(pInetFormatAttr); + // leave it open to let next portion decide to merge or close + --m_nEndStructureElement; + } + } + // Check for Quote/Code character style: + else if (sStyleName == aQuotation) + { + if (!isContinueSpan) + { + nPDFType = vcl::PDFWriter::Quote; + aPDFType = aQuoteString; + CreateCurrentSpan(rInf, sStyleName); + } + } + else if (sStyleName == aSourceText) + { + if (!isContinueSpan) + { + nPDFType = vcl::PDFWriter::Code; + aPDFType = aCodeString; + CreateCurrentSpan(rInf, sStyleName); + } + } + else if (!isContinueSpan) + { + const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage(); + const SwFontScript nFont = rInf.GetFont()->GetActual(); + const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault); + + if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() || + LINESTYLE_NONE != rInf.GetFont()->GetOverline() || + STRIKEOUT_NONE != rInf.GetFont()->GetStrikeout() || + FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() || + 0 != rInf.GetFont()->GetEscapement() || + SwFontScript::Latin != nFont || + nCurrentLanguage != nDefaultLang || + !sStyleName.isEmpty()) + { + nPDFType = vcl::PDFWriter::Span; + if (!sStyleName.isEmpty()) + aPDFType = sStyleName; + else + aPDFType = aSpanString; + CreateCurrentSpan(rInf, sStyleName); + } + } + } + break; + + case PortionType::Footnote : + nPDFType = vcl::PDFWriter::Link; + aPDFType = aLinkString; + break; + + case PortionType::Field : + { + // check field type: + TextFrameIndex const nIdx = static_cast<const SwFieldPortion*>(pPor)->IsFollow() + ? rInf.GetIdx() - TextFrameIndex(1) + : rInf.GetIdx(); + const SwTextAttr* pHint = mpPorInfo->mrTextPainter.GetAttr( nIdx ); + if ( pHint && RES_TXTATR_FIELD == pHint->Which() ) + { + const SwField* pField = pHint->GetFormatField().GetField(); + if ( SwFieldIds::GetRef == pField->Which() ) + { + nPDFType = vcl::PDFWriter::Link; + aPDFType = aLinkString; + } + else if ( SwFieldIds::TableOfAuthorities == pField->Which() ) + { + nPDFType = vcl::PDFWriter::BibEntry; + aPDFType = aBibEntryString; + } + } + } + break; + + case PortionType::Multi: + { + SwMultiPortion const*const pMulti(static_cast<SwMultiPortion const*>(pPor)); + if (pMulti->IsRuby()) + { + EndCurrentAll(); + switch (mpPorInfo->m_Mode) + { + case 0: + nPDFType = vcl::PDFWriter::Ruby; + aPDFType = "Ruby"; + break; + case 1: + nPDFType = vcl::PDFWriter::RT; + aPDFType = "RT"; + break; + case 2: + nPDFType = vcl::PDFWriter::RB; + aPDFType = "RB"; + break; + } + } + else if (pMulti->IsDouble()) + { + EndCurrentAll(); + switch (mpPorInfo->m_Mode) + { + case 0: + nPDFType = vcl::PDFWriter::Warichu; + aPDFType = "Warichu"; + break; + case 1: + nPDFType = vcl::PDFWriter::WP; + aPDFType = "WP"; + break; + case 2: + nPDFType = vcl::PDFWriter::WT; + aPDFType = "WT"; + break; + } + } + } + break; + + + // for FootnoteNum, is called twice: outer generates Lbl, inner Link + case PortionType::FootnoteNum: + assert(!isContinueSpan); // is at start + if (mpPorInfo->m_Mode == 0) + { // tdf#152218 link both directions + nPDFType = vcl::PDFWriter::Link; + aPDFType = aLinkString; + break; + } + [[fallthrough]]; + case PortionType::Number: + case PortionType::Bullet: + case PortionType::GrfNum: + assert(!isContinueSpan); // is at start + if (mpPorInfo->m_Mode == 1) + { // only works for multiple lines via wrapper from PaintSwFrame + nPDFType = vcl::PDFWriter::LILabel; + aPDFType = aListLabelString; + } + break; + + case PortionType::Tab : + case PortionType::TabRight : + case PortionType::TabCenter : + case PortionType::TabDecimal : + nPDFType = vcl::PDFWriter::NonStructElement; + break; + default: break; + } + + if ( USHRT_MAX != nPDFType ) + { + BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType ); + } +} + +bool SwTaggedPDFHelper::IsExportTaggedPDF( const OutputDevice& rOut ) +{ + vcl::PDFExtOutDevData* pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() ); + return pPDFExtOutDevData && pPDFExtOutDevData->GetIsExportTaggedPDF(); +} + +SwEnhancedPDFExportHelper::SwEnhancedPDFExportHelper( SwEditShell& rSh, + OutputDevice& rOut, + const OUString& rPageRange, + bool bSkipEmptyPages, + bool bEditEngineOnly, + const SwPrintData& rPrintData ) + : mrSh( rSh ), + mrOut( rOut ), + mbSkipEmptyPages( bSkipEmptyPages ), + mbEditEngineOnly( bEditEngineOnly ), + mrPrintData( rPrintData ) +{ + if ( !rPageRange.isEmpty() ) + mpRangeEnum.reset( new StringRangeEnumerator( rPageRange, 0, mrSh.GetPageCount()-1 ) ); + + if ( mbSkipEmptyPages ) + { + maPageNumberMap.resize( mrSh.GetPageCount() ); + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + sal_Int32 nPageNumber = 0; + for ( size_t i = 0, n = maPageNumberMap.size(); i < n && pCurrPage; ++i ) + { + if ( pCurrPage->IsEmptyPage() ) + maPageNumberMap[i] = -1; + else + maPageNumberMap[i] = nPageNumber++; + + pCurrPage = static_cast<const SwPageFrame*>( pCurrPage->GetNext() ); + } + } + +#if OSL_DEBUG_LEVEL > 1 + aStructStack.clear(); +#endif + + const sal_Int16 nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() ); + TypedWhichId<SvxLanguageItem> nLangRes = RES_CHRATR_LANGUAGE; + + if ( i18n::ScriptType::ASIAN == nScript ) + nLangRes = RES_CHRATR_CJK_LANGUAGE; + else if ( i18n::ScriptType::COMPLEX == nScript ) + nLangRes = RES_CHRATR_CTL_LANGUAGE; + + const SvxLanguageItem& rLangItem = mrSh.GetDoc()->GetDefault( nLangRes ); + auto const eLanguageDefault = rLangItem.GetLanguage(); + + EnhancedPDFExport(eLanguageDefault); +} + +SwEnhancedPDFExportHelper::~SwEnhancedPDFExportHelper() +{ +} + +tools::Rectangle SwEnhancedPDFExportHelper::SwRectToPDFRect(const SwPageFrame* pCurrPage, + const tools::Rectangle& rRectangle) const +{ + if (!::sw::IsShrinkPageForPostIts(mrSh, mrPrintData)) // tdf#148729 + { + return rRectangle; + } + //the page has been scaled by 75% and vertically centered, so adjust these + //rectangles equivalently + tools::Rectangle aRect(rRectangle); + Size aRectSize(aRect.GetSize()); + double fScale = 0.75; + aRectSize.setWidth( aRectSize.Width() * fScale ); + aRectSize.setHeight( aRectSize.Height() * fScale ); + tools::Long nOrigHeight = pCurrPage->getFrameArea().Height(); + tools::Long nNewHeight = nOrigHeight*fScale; + tools::Long nShiftY = (nOrigHeight-nNewHeight)/2; + aRect.SetLeft( aRect.Left() * fScale ); + aRect.SetTop( aRect.Top() * fScale ); + aRect.Move(0, nShiftY); + aRect.SetSize(aRectSize); + return aRect; +} + +void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDefault) +{ + vcl::PDFExtOutDevData* pPDFExtOutDevData = + dynamic_cast< vcl::PDFExtOutDevData*>( mrOut.GetExtOutDevData() ); + + if ( !pPDFExtOutDevData ) + return; + + // set the document locale + + lang::Locale const aDocLocale( LanguageTag(eLanguageDefault).getLocale() ); + pPDFExtOutDevData->SetDocumentLocale( aDocLocale ); + + // Prepare the output device: + + mrOut.Push( vcl::PushFlags::MAPMODE ); + MapMode aMapMode( mrOut.GetMapMode() ); + aMapMode.SetMapUnit( MapUnit::MapTwip ); + mrOut.SetMapMode( aMapMode ); + + // Create new cursor and lock the view: + + SwDoc* pDoc = mrSh.GetDoc(); + mrSh.SwCursorShell::Push(); + mrSh.SwCursorShell::ClearMark(); + const bool bOldLockView = mrSh.IsViewLocked(); + mrSh.LockView( true ); + + if ( !mbEditEngineOnly ) + { + assert(pPDFExtOutDevData->GetSwPDFState() == nullptr); + pPDFExtOutDevData->SetSwPDFState(new SwEnhancedPDFState(eLanguageDefault)); + + // POSTITS + + if ( pPDFExtOutDevData->GetIsExportNotes() ) + { + std::vector<SwFormatField*> vpFields; + mrSh.GetFieldType(SwFieldIds::Postit, OUString())->GatherFields(vpFields); + for(auto pFormatField : vpFields) + { + const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode(); + OSL_ENSURE(nullptr != pTNd, "Enhanced pdf export - text node is missing"); + if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField)) + continue; + // Link Rectangle + const SwRect& rNoteRect = mrSh.GetCharRect(); + const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower()); + + // Link PageNums + std::vector<sal_Int32> aNotePageNums = CalcOutputPageNums(rNoteRect); + for (sal_Int32 aNotePageNum : aNotePageNums) + { + + // Use the NumberFormatter to get the date string: + const SwPostItField* pField = static_cast<SwPostItField*>(pFormatField->GetField()); + SvNumberFormatter* pNumFormatter = pDoc->GetNumberFormatter(); + const Date aDateDiff(pField->GetDate() - pNumFormatter->GetNullDate()); + const sal_uLong nFormat = pNumFormatter->GetStandardFormat(SvNumFormatType::DATE, pField->GetLanguage()); + OUString sDate; + const Color* pColor; + pNumFormatter->GetOutputString(aDateDiff.GetDate(), nFormat, sDate, &pColor); + + vcl::PDFNote aNote; + // The title should consist of the author and the date: + aNote.Title = pField->GetPar1() + ", " + sDate + ", " + (pField->GetResolved() ? SwResId(STR_RESOLVED) : ""); + // Guess what the contents contains... + aNote.Contents = pField->GetText(); + + // Link Export + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rNoteRect.SVRect())); + pPDFExtOutDevData->CreateNote(aRect, aNote, aNotePageNum); + } + mrSh.SwCursorShell::ClearMark(); + } + } + + // HYPERLINKS + + SwGetINetAttrs aArr; + mrSh.GetINetAttrs( aArr ); + for( auto &rAttr : aArr ) + { + SwGetINetAttr* p = &rAttr; + OSL_ENSURE( nullptr != p, "Enhanced pdf export - SwGetINetAttr is missing" ); + + const SwTextNode* pTNd = p->rINetAttr.GetpTextNode(); + OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" ); + + // 1. Check if the whole paragraph is hidden + // 2. Move to the hyperlink + // 3. Check for hidden text attribute + if ( !pTNd->IsHidden() && + mrSh.GotoINetAttr( p->rINetAttr ) && + !mrSh.IsInHiddenRange(/*bSelect=*/false) ) + { + // Select the hyperlink: + mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars ); + if ( mrSh.SwCursorShell::SelectTextAttr( RES_TXTATR_INETFMT, true ) ) + { + // First, we create the destination, because there may be more + // than one link to this destination: + OUString aURL( INetURLObject::decode( + p->rINetAttr.GetINetFormat().GetValue(), + INetURLObject::DecodeMechanism::Unambiguous ) ); + + // We have to distinguish between internal and real URLs + const bool bInternal = '#' == aURL[0]; + + // GetCursor_() is a SwShellCursor, which is derived from + // SwSelPaintRects, therefore the rectangles of the current + // selection can be easily obtained: + // Note: We make a copy of the rectangles, because they may + // be deleted again in JumpToSwMark. + SwRects const aTmp(GetCursorRectsContainingText(mrSh)); + OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" ); + OUString const altText(mrSh.GetSelText()); + + const SwPageFrame* pSelectionPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Create the destination for internal links: + sal_Int32 nDestId = -1; + if ( bInternal ) + { + aURL = aURL.copy( 1 ); + mrSh.SwCursorShell::ClearMark(); + if (! JumpToSwMark( &mrSh, aURL )) + { + continue; // target deleted + } + + // Destination Rectangle + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + // Destination Export + if ( -1 != nDestPageNum ) + { + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + } + } + + if ( !bInternal || -1 != nDestId ) + { + // #i44368# Links in Header/Footer + const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd ); + + // Create links for all selected rectangles: + const size_t nNumOfRects = aTmp.size(); + for ( size_t i = 0; i < nNumOfRects; ++i ) + { + // Link Rectangle + const SwRect& rLinkRect( aTmp[ i ] ); + + // Link PageNums + std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect ); + + for (sal_Int32 aLinkPageNum : aLinkPageNums) + { + // Link Export + tools::Rectangle aRect(SwRectToPDFRect(pSelectionPage, rLinkRect.SVRect())); + const sal_Int32 nLinkId = + pPDFExtOutDevData->CreateLink(aRect, altText, aLinkPageNum); + + // Store link info for tagged pdf output: + const IdMapEntry aLinkEntry( rLinkRect, nLinkId ); + pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry); + + // Connect Link and Destination: + if ( bInternal ) + pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); + else + pPDFExtOutDevData->SetLinkURL( nLinkId, aURL ); + + // #i44368# Links in Header/Footer + if ( bHeaderFooter ) + MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, aURL, bInternal, altText); + } + } + } + } + } + mrSh.SwCursorShell::ClearMark(); + } + + // HYPERLINKS (Graphics, Frames, OLEs ) + + for(sw::SpzFrameFormat* pFrameFormat: *pDoc->GetSpzFrameFormats()) + { + const SwFormatURL* pItem; + if ( RES_DRAWFRMFMT != pFrameFormat->Which() && + GetFrameOfModify(mrSh.GetLayout(), *pFrameFormat, SwFrameType::Fly) && + (pItem = pFrameFormat->GetAttrSet().GetItemIfSet( RES_URL )) ) + { + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + OUString aURL( pItem->GetURL() ); + if (aURL.isEmpty()) + continue; + const bool bInternal = '#' == aURL[0]; + + // Create the destination for internal links: + sal_Int32 nDestId = -1; + if ( bInternal ) + { + aURL = aURL.copy( 1 ); + mrSh.SwCursorShell::ClearMark(); + if (! JumpToSwMark( &mrSh, aURL )) + { + continue; // target deleted + } + + // Destination Rectangle + const SwRect& rDestRect = mrSh.GetCharRect(); + + pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + // Destination Export + if ( -1 != nDestPageNum ) + { + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + } + } + + if ( !bInternal || -1 != nDestId ) + { + Point aNullPt; + const SwRect aLinkRect = pFrameFormat->FindLayoutRect( false, &aNullPt ); + OUString const formatName(pFrameFormat->GetName()); + // Link PageNums + std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( aLinkRect ); + + // Link Export + for (sal_Int32 aLinkPageNum : aLinkPageNums) + { + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect())); + const sal_Int32 nLinkId = + pPDFExtOutDevData->CreateLink(aRect, formatName, aLinkPageNum); + + // Store link info for tagged pdf output: + const IdMapEntry aLinkEntry(aLinkRect, nLinkId); + pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry); + + // Connect Link and Destination: + if ( bInternal ) + pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); + else + pPDFExtOutDevData->SetLinkURL( nLinkId, aURL ); + + // #i44368# Links in Header/Footer + const SwFormatAnchor &rAnch = pFrameFormat->GetAnchor(); + if (RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId()) + { + const SwNode* pAnchorNode = rAnch.GetAnchorNode(); + if ( pAnchorNode && pDoc->IsInHeaderFooter( *pAnchorNode ) ) + { + const SwTextNode* pTNd = pAnchorNode->GetTextNode(); + if ( pTNd ) + MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, aLinkRect, nDestId, aURL, bInternal, formatName); + } + } + } + } + } + else if (pFrameFormat->Which() == RES_DRAWFRMFMT) + { + // Turn media shapes into Screen annotations. + if (SdrObject* pObject = pFrameFormat->FindRealSdrObject()) + { + SwRect aSnapRect(pObject->GetSnapRect()); + std::vector<sal_Int32> aScreenPageNums = CalcOutputPageNums(aSnapRect); + if (aScreenPageNums.empty()) + continue; + + uno::Reference<drawing::XShape> xShape(pObject->getUnoShape(), uno::UNO_QUERY); + if (xShape->getShapeType() == "com.sun.star.drawing.MediaShape") + { + uno::Reference<beans::XPropertySet> xShapePropSet(xShape, uno::UNO_QUERY); + OUString title; + xShapePropSet->getPropertyValue("Title") >>= title; + OUString description; + xShapePropSet->getPropertyValue("Description") >>= description; + OUString const altText(title.isEmpty() + ? description + : description.isEmpty() + ? title + : OUString::Concat(title) + OUString::Concat("\n") + OUString::Concat(description)); + + OUString aMediaURL; + xShapePropSet->getPropertyValue("MediaURL") >>= aMediaURL; + if (!aMediaURL.isEmpty()) + { + OUString const mimeType(xShapePropSet->getPropertyValue("MediaMimeType").get<OUString>()); + const SwPageFrame* pCurrPage = mrSh.GetLayout()->GetPageAtPos(aSnapRect.Center()); + tools::Rectangle aPDFRect(SwRectToPDFRect(pCurrPage, aSnapRect.SVRect())); + for (sal_Int32 nScreenPageNum : aScreenPageNums) + { + sal_Int32 nScreenId = pPDFExtOutDevData->CreateScreen(aPDFRect, altText, mimeType, nScreenPageNum, pObject); + if (aMediaURL.startsWith("vnd.sun.star.Package:")) + { + // Embedded media. + OUString aTempFileURL; + xShapePropSet->getPropertyValue("PrivateTempFileURL") >>= aTempFileURL; + pPDFExtOutDevData->SetScreenStream(nScreenId, aTempFileURL); + } + else + // Linked media. + pPDFExtOutDevData->SetScreenURL(nScreenId, aMediaURL); + } + } + } + } + } + mrSh.SwCursorShell::ClearMark(); + } + + // REFERENCES + + std::vector<SwFormatField*> vpFields; + mrSh.GetFieldType( SwFieldIds::GetRef, OUString() )->GatherFields(vpFields); + for(auto pFormatField : vpFields ) + { + if( pFormatField->GetTextField() && pFormatField->IsFieldInDoc() ) + { + const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode(); + OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" ); + if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField)) + continue; + // Select the field: + mrSh.SwCursorShell::SetMark(); + mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars ); + + // Link Rectangles + SwRects const aTmp(GetCursorRectsContainingText(mrSh)); + OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" ); + + mrSh.SwCursorShell::ClearMark(); + + // Destination Rectangle + const SwGetRefField* pField = static_cast<SwGetRefField*>(pFormatField->GetField()); + const OUString& rRefName = pField->GetSetRefName(); + mrSh.GotoRefMark( rRefName, pField->GetSubType(), pField->GetSeqNo(), pField->GetFlags() ); + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + if ( -1 != nDestPageNum ) + { + // Destination Export + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + + // #i44368# Links in Header/Footer + const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd ); + OUString const content(pField->ExpandField(true, mrSh.GetLayout())); + + // Create links for all selected rectangles: + const size_t nNumOfRects = aTmp.size(); + for ( size_t i = 0; i < nNumOfRects; ++i ) + { + // Link rectangle + const SwRect& rLinkRect( aTmp[ i ] ); + + // Link PageNums + std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect ); + + for (sal_Int32 aLinkPageNum : aLinkPageNums) + { + // Link Export + aRect = SwRectToPDFRect(pCurrPage, rLinkRect.SVRect()); + const sal_Int32 nLinkId = + pPDFExtOutDevData->CreateLink(aRect, content, aLinkPageNum); + + // Store link info for tagged pdf output: + const IdMapEntry aLinkEntry( rLinkRect, nLinkId ); + pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry); + + // Connect Link and Destination: + pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); + + // #i44368# Links in Header/Footer + if ( bHeaderFooter ) + { + MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, "", true, content); + } + } + } + } + } + mrSh.SwCursorShell::ClearMark(); + } + + ExportAuthorityEntryLinks(); + + // FOOTNOTES + + const size_t nFootnoteCount = pDoc->GetFootnoteIdxs().size(); + for ( size_t nIdx = 0; nIdx < nFootnoteCount; ++nIdx ) + { + // Set cursor to text node that contains the footnote: + const SwTextFootnote* pTextFootnote = pDoc->GetFootnoteIdxs()[ nIdx ]; + SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode()); + + mrSh.GetCursor_()->GetPoint()->Assign(rTNd, pTextFootnote->GetStart()); + + // 1. Check if the whole paragraph is hidden + // 2. Check for hidden text attribute + if (rTNd.GetTextNode()->IsHidden() || mrSh.IsInHiddenRange(/*bSelect=*/false) + || (mrSh.GetLayout()->IsHideRedlines() + && sw::IsFootnoteDeleted(pDoc->getIDocumentRedlineAccess(), *pTextFootnote))) + { + continue; + } + + SwCursorSaveState aSaveState( *mrSh.GetCursor_() ); + + // Select the footnote: + mrSh.SwCursorShell::SetMark(); + mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars ); + + // Link Rectangle + SwRects aTmp; + aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() ); + OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" ); + + mrSh.GetCursor_()->RestoreSavePos(); + mrSh.SwCursorShell::ClearMark(); + + if (aTmp.empty()) + continue; + + const SwRect aLinkRect( aTmp[ 0 ] ); + + // Goto footnote text: + if ( mrSh.GotoFootnoteText() ) + { + // Destination Rectangle + const SwRect& rDestRect = mrSh.GetCharRect(); + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + if ( -1 != nDestPageNum ) + { + const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + // Destination PageNum + tools::Rectangle aRect = SwRectToPDFRect(pCurrPage, rDestRect.SVRect()); + // Back link rectangle calculation + const SwPageFrame* fnBodyPage = pCurrPage->getRootFrame()->GetPageByPageNum(nDestPageNum+1); + SwRect fnSymbolRect; + if (fnBodyPage->IsVertical()){ + tools::Long fnSymbolTop = fnBodyPage->GetTopMargin() + fnBodyPage->getFrameArea().Top(); + tools::Long symbolHeight = rDestRect.Top() - fnSymbolTop; + fnSymbolRect = SwRect(rDestRect.Pos().X(),fnSymbolTop,rDestRect.Width(),symbolHeight); + } else { + if (fnBodyPage->IsRightToLeft()){ + tools::Long fnSymbolRight = fnBodyPage->getFrameArea().Right() - fnBodyPage->GetRightMargin(); + tools::Long symbolWidth = fnSymbolRight - rDestRect.Right(); + fnSymbolRect = SwRect(rDestRect.Pos().X(),rDestRect.Pos().Y(),symbolWidth,rDestRect.Height()); + } else { + tools::Long fnSymbolLeft = fnBodyPage->GetLeftMargin() + fnBodyPage->getFrameArea().Left(); + tools::Long symbolWidth = rDestRect.Left() - fnSymbolLeft; + fnSymbolRect = SwRect(fnSymbolLeft,rDestRect.Pos().Y(),symbolWidth,rDestRect.Height()); + } + } + tools::Rectangle aFootnoteSymbolRect = SwRectToPDFRect(pCurrPage, fnSymbolRect.SVRect()); + + OUString const numStrSymbol(pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, mrSh.GetLayout(), true)); + OUString const numStrRef(pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, mrSh.GetLayout(), false)); + + // Export back link + const sal_Int32 nBackLinkId = pPDFExtOutDevData->CreateLink(aFootnoteSymbolRect, numStrSymbol, nDestPageNum); + // Destination Export + const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + mrSh.GotoFootnoteAnchor(); + // Link PageNums + sal_Int32 aLinkPageNum = CalcOutputPageNum( aLinkRect ); + pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + // Link Export + aRect = SwRectToPDFRect(pCurrPage, aLinkRect.SVRect()); + const sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, numStrRef, aLinkPageNum); + // Back link destination Export + const sal_Int32 nBackDestId = pPDFExtOutDevData->CreateDest(aRect, aLinkPageNum); + // Store link info for tagged pdf output: + const IdMapEntry aLinkEntry( aLinkRect, nLinkId ); + pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry); + + // Store backlink info for tagged pdf output: + const IdMapEntry aBackLinkEntry( aFootnoteSymbolRect, nBackLinkId ); + pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aBackLinkEntry); + // Connect Links and Destinations: + pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); + pPDFExtOutDevData->SetLinkDest( nBackLinkId, nBackDestId ); + } + } + } + + // OUTLINE + + if( pPDFExtOutDevData->GetIsExportBookmarks() ) + { + typedef std::pair< sal_Int8, sal_Int32 > StackEntry; + std::stack< StackEntry > aOutlineStack; + aOutlineStack.push( StackEntry( -1, -1 ) ); // push default value + + const SwOutlineNodes::size_type nOutlineCount = + mrSh.getIDocumentOutlineNodesAccess()->getOutlineNodesCount(); + for ( SwOutlineNodes::size_type i = 0; i < nOutlineCount; ++i ) + { + // Check if outline is hidden + const SwTextNode* pTNd = mrSh.GetNodes().GetOutLineNds()[ i ]->GetTextNode(); + OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" ); + + if ( pTNd->IsHidden() || + !sw::IsParaPropsNode(*mrSh.GetLayout(), *pTNd) || + // #i40292# Skip empty outlines: + pTNd->GetText().isEmpty()) + continue; + + // Get parent id from stack: + const sal_Int8 nLevel = static_cast<sal_Int8>(mrSh.getIDocumentOutlineNodesAccess()->getOutlineLevel( i )); + sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first; + while ( nLevelOnTopOfStack >= nLevel && + nLevelOnTopOfStack != -1 ) + { + aOutlineStack.pop(); + nLevelOnTopOfStack = aOutlineStack.top().first; + } + const sal_Int32 nParent = aOutlineStack.top().second; + + // Destination rectangle + mrSh.GotoOutline(i); + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + if ( -1 != nDestPageNum ) + { + // Destination Export + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + const sal_Int32 nDestId = + pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + + // Outline entry text + const OUString& rEntry = mrSh.getIDocumentOutlineNodesAccess()->getOutlineText( + i, mrSh.GetLayout(), true, false, false ); + + // Create a new outline item: + const sal_Int32 nOutlineId = + pPDFExtOutDevData->CreateOutlineItem( nParent, rEntry, nDestId ); + + // Push current level and nOutlineId on stack: + aOutlineStack.push( StackEntry( nLevel, nOutlineId ) ); + } + } + } + + if( pPDFExtOutDevData->GetIsExportNamedDestinations() ) + { + // #i56629# the iteration to convert the OOo bookmark (#bookmark) + // into PDF named destination, see section 8.2.1 in PDF 1.4 spec + // We need: + // 1. a name for the destination, formed from the standard OOo bookmark name + // 2. the destination, obtained from where the bookmark destination lies + IDocumentMarkAccess* const pMarkAccess = mrSh.GetDoc()->getIDocumentMarkAccess(); + for(IDocumentMarkAccess::const_iterator_t ppMark = pMarkAccess->getBookmarksBegin(); + ppMark != pMarkAccess->getBookmarksEnd(); + ++ppMark) + { + //get the name + const ::sw::mark::IMark* pBkmk = *ppMark; + mrSh.SwCursorShell::ClearMark(); + const OUString& sBkName = pBkmk->GetName(); + + //jump to it + if (! JumpToSwMark( &mrSh, sBkName )) + { + continue; + } + + // Destination Rectangle + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + // Destination Export + if ( -1 != nDestPageNum ) + { + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + pPDFExtOutDevData->CreateNamedDest(sBkName, aRect, nDestPageNum); + } + } + mrSh.SwCursorShell::ClearMark(); + //<--- i56629 + } + } + else + { + + // LINKS FROM EDITENGINE + + std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks(); + for ( const auto& rBookmark : rBookmarks ) + { + OUString aBookmarkName( rBookmark.aBookmark ); + const bool bInternal = '#' == aBookmarkName[0]; + if ( bInternal ) + { + aBookmarkName = aBookmarkName.copy( 1 ); + JumpToSwMark( &mrSh, aBookmarkName ); + + // Destination Rectangle + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + if ( -1 != nDestPageNum ) + { + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + if ( rBookmark.nLinkId != -1 ) + { + // Destination Export + const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + + // Connect Link and Destination: + pPDFExtOutDevData->SetLinkDest( rBookmark.nLinkId, nDestId ); + } + else + { + pPDFExtOutDevData->DescribeRegisteredDest(rBookmark.nDestId, aRect, nDestPageNum); + } + } + } + else + pPDFExtOutDevData->SetLinkURL( rBookmark.nLinkId, aBookmarkName ); + } + rBookmarks.clear(); + assert(pPDFExtOutDevData->GetSwPDFState()); + delete pPDFExtOutDevData->GetSwPDFState(); + pPDFExtOutDevData->SetSwPDFState(nullptr); + } + + // Restore view, cursor, and outdev: + mrSh.LockView( bOldLockView ); + mrSh.SwCursorShell::Pop(SwCursorShell::PopMode::DeleteCurrent); + mrOut.Pop(); +} + +void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks() +{ + auto pPDFExtOutDevData = dynamic_cast<vcl::PDFExtOutDevData*>(mrOut.GetExtOutDevData()); + if (!pPDFExtOutDevData) + { + return; + } + + // Create PDF destinations for bibliography table entries + std::vector<std::tuple<const SwTOXBase*, const OUString*, sal_Int32>> vDestinations; + // string is the row node text, sal_Int32 is number of the destination + // Note: This way of iterating doesn't seem to take into account TOXes + // that are in a frame, probably in some other cases too + { + mrSh.GotoPage(1); + while (mrSh.GotoNextTOXBase()) + { + const SwTOXBase* pIteratedTOX = nullptr; + while ((pIteratedTOX = mrSh.GetCurTOX()) != nullptr + && pIteratedTOX->GetType() == TOX_AUTHORITIES) + { + if (const SwNode& rCurrentNode = mrSh.GetCursor()->GetPoint()->GetNode(); + rCurrentNode.GetNodeType() == SwNodeType::Text) + { + if (mrSh.GetCursor()->GetPoint()->GetNode().FindSectionNode()->GetSection().GetType() + == SectionType::ToxContent) // this checks it's not a heading + { + // Destination Rectangle + const SwRect& rDestRect = mrSh.GetCharRect(); + + const SwPageFrame* pCurrPage = + static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() ); + + // Destination PageNum + const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect ); + + // Destination Export + if ( -1 != nDestPageNum ) + { + tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect())); + const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum); + const OUString* vNodeText = &static_cast<const SwTextNode*>(&rCurrentNode)->GetText(); + vDestinations.emplace_back(pIteratedTOX, vNodeText, nDestId); + } + } + } + if (!mrSh.MovePara(GoNextPara, fnParaStart)) + { // Cursor is stuck in the TOX due to document ending immediately afterwards + break; + } + } + } + } + + // Generate links to matching entries in the bibliography tables + std::vector<SwFormatField*> aFields; + SwFieldType* pType = mrSh.GetFieldType(SwFieldIds::TableOfAuthorities, OUString()); + if (!pType) + { + return; + } + + pType->GatherFields(aFields); + const auto pPageFrame = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower()); + for (const auto pFormatField : aFields) + { + if (!pFormatField->GetTextField() || !pFormatField->IsFieldInDoc()) + { + continue; + } + + const auto& rAuthorityField + = *static_cast<const SwAuthorityField*>(pFormatField->GetField()); + + if (auto targetType = rAuthorityField.GetTargetType(); + targetType == SwAuthorityField::TargetType::UseDisplayURL + || targetType == SwAuthorityField::TargetType::UseTargetURL) + { + // Since the target type specifies to use an URL, link to it + const OUString& rURL = rAuthorityField.GetAbsoluteURL(); + if (rURL.getLength() == 0) + { + continue; + } + + const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode(); + if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField)) + { + continue; + } + + OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout())); + + // Select the field. + mrSh.SwCursorShell::SetMark(); + mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars); + + // Create the links. + SwRects const rects(GetCursorRectsContainingText(mrSh)); + for (const auto& rLinkRect : rects) + { + for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect)) + { + tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect())); + sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum); + IdMapEntry aLinkEntry(rLinkRect, nLinkId); + pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry); + pPDFExtOutDevData->SetLinkURL(nLinkId, rURL); + } + } + mrSh.SwCursorShell::ClearMark(); + } + else if (targetType == SwAuthorityField::TargetType::BibliographyTableRow) + { + // As the target type specifies, try linking to a bibliography table row + sal_Int32 nDestId = -1; + + std::unordered_map<const SwTOXBase*, OUString> vFormattedFieldStrings; + for (const auto& rDestinationTuple : vDestinations) + { + if (vFormattedFieldStrings.find(std::get<0>(rDestinationTuple)) + == vFormattedFieldStrings.end()) + vFormattedFieldStrings.emplace(std::get<0>(rDestinationTuple), + rAuthorityField.GetAuthority(mrSh.GetLayout(), + &std::get<0>(rDestinationTuple)->GetTOXForm())); + + if (vFormattedFieldStrings.at(std::get<0>(rDestinationTuple)) == *std::get<1>(rDestinationTuple)) + { + nDestId = std::get<2>(rDestinationTuple); + break; + } + } + + if (nDestId == -1) + continue; + + const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode(); + if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField)) + { + continue; + } + + OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout())); + + // Select the field. + mrSh.SwCursorShell::SetMark(); + mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars); + + // Create the links. + SwRects const rects(GetCursorRectsContainingText(mrSh)); + for (const auto& rLinkRect : rects) + { + for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect)) + { + tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect())); + sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum); + IdMapEntry aLinkEntry(rLinkRect, nLinkId); + pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry); + pPDFExtOutDevData->SetLinkDest(nLinkId, nDestId); + } + } + mrSh.SwCursorShell::ClearMark(); + } + } +} + +// Returns the page number in the output pdf on which the given rect is located. +// If this page is duplicated, method will return first occurrence of it. +sal_Int32 SwEnhancedPDFExportHelper::CalcOutputPageNum( const SwRect& rRect ) const +{ + std::vector< sal_Int32 > aPageNums = CalcOutputPageNums( rRect ); + if ( !aPageNums.empty() ) + return aPageNums[0]; + return -1; +} + +// Returns a vector of the page numbers in the output pdf on which the given +// rect is located. There can be many such pages since StringRangeEnumerator +// allows duplication of its entries. +std::vector< sal_Int32 > SwEnhancedPDFExportHelper::CalcOutputPageNums( + const SwRect& rRect ) const +{ + std::vector< sal_Int32 > aPageNums; + + // Document page number. + sal_Int32 nPageNumOfRect = mrSh.GetPageNumAndSetOffsetForPDF( mrOut, rRect ); + if ( nPageNumOfRect < 0 ) + return aPageNums; + + // What will be the page numbers of page nPageNumOfRect in the output pdf? + if ( mpRangeEnum ) + { + if ( mbSkipEmptyPages ) + // Map the page number to the range without empty pages. + nPageNumOfRect = maPageNumberMap[ nPageNumOfRect ]; + + if ( mpRangeEnum->hasValue( nPageNumOfRect ) ) + { + sal_Int32 nOutputPageNum = 0; + StringRangeEnumerator::Iterator aIter = mpRangeEnum->begin(); + StringRangeEnumerator::Iterator aEnd = mpRangeEnum->end(); + for ( ; aIter != aEnd; ++aIter ) + { + if ( *aIter == nPageNumOfRect ) + aPageNums.push_back( nOutputPageNum ); + ++nOutputPageNum; + } + } + } + else + { + if ( mbSkipEmptyPages ) + { + sal_Int32 nOutputPageNum = 0; + for ( size_t i = 0; i < maPageNumberMap.size(); ++i ) + { + if ( maPageNumberMap[i] >= 0 ) // is not empty? + { + if ( i == static_cast<size_t>( nPageNumOfRect ) ) + { + aPageNums.push_back( nOutputPageNum ); + break; + } + ++nOutputPageNum; + } + } + } + else + aPageNums.push_back( nPageNumOfRect ); + } + + return aPageNums; +} + +void SwEnhancedPDFExportHelper::MakeHeaderFooterLinks( vcl::PDFExtOutDevData& rPDFExtOutDevData, + const SwTextNode& rTNd, + const SwRect& rLinkRect, + sal_Int32 nDestId, + const OUString& rURL, + bool bInternal, + OUString const& rContent) const +{ + // We assume, that the primary link has just been exported. Therefore + // the offset of the link rectangle calculates as follows: + const Point aOffset = rLinkRect.Pos() + mrOut.GetMapMode().GetOrigin(); + + SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rTNd); + for ( SwTextFrame* pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() ) + { + // Add offset to current page: + const SwPageFrame* pPageFrame = pTmpFrame->FindPageFrame(); + SwRect aHFLinkRect( rLinkRect ); + aHFLinkRect.Pos() = pPageFrame->getFrameArea().Pos() + aOffset; + + // #i97135# the gcc_x64 optimizer gets aHFLinkRect != rLinkRect wrong + // fool it by comparing the position only (the width and height are the + // same anyway) + if ( aHFLinkRect.Pos() != rLinkRect.Pos() ) + { + // Link PageNums + std::vector<sal_Int32> aHFLinkPageNums = CalcOutputPageNums( aHFLinkRect ); + + for (sal_Int32 aHFLinkPageNum : aHFLinkPageNums) + { + // Link Export + tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, aHFLinkRect.SVRect())); + const sal_Int32 nHFLinkId = + rPDFExtOutDevData.CreateLink(aRect, rContent, aHFLinkPageNum); + + // Connect Link and Destination: + if ( bInternal ) + rPDFExtOutDevData.SetLinkDest( nHFLinkId, nDestId ); + else + rPDFExtOutDevData.SetLinkURL( nHFLinkId, rURL ); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |