/* -*- 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 <vcl/pdf/PDFNote.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 <PostItMgr.hxx>
#include <AnnotationWin.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::vector< IdMapEntry > NoteIdMap;
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;
    NoteIdMap m_NoteIdMap;
    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:
constexpr OUString aTableHeadingName  = u"Table Heading"_ustr;
constexpr OUString aQuotations        = u"Quotations"_ustr;
constexpr OUString aCaption           = u"Caption"_ustr;
constexpr OUString aHeading           = u"Heading"_ustr;
constexpr OUString aQuotation         = u"Quotation"_ustr;
constexpr OUString aSourceText        = u"Source Text"_ustr;
 
// 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";
constexpr OUStringLiteral aAnnotString = u"Annot";
 
// 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;
            std::pair<Point, bool> const tmp(center, false);
            SwContentFrame const*const pFrame(
                pos.nNode.GetNode().GetTextNode()->getLayoutFrame(rShell.GetLayout(), &pos, &tmp));
            if (pFrame->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 )
        {
            bool bIsFigureInline = false;
            if (vcl::PDFWriter::Figure == eType)
            {
                const SwFrame* pKeyFrame = static_cast<const SwFlyFrame&>(*pFrame).GetAnchorFrame();
                if (const SwLayoutFrame* pUpperFrame = pKeyFrame->GetUpper())
                    if (pUpperFrame->GetType() == SwFrameType::Body)
                        bIsFigureInline = true;
            }
 
            eVal = vcl::PDFWriter::TableHeader == eType || vcl::PDFWriter::TableData == eType
                           || bIsFigureInline
                       ? 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() : u" - "_ustr);
            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 bAnnotAttribute = 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 :
            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;
 
            case vcl::PDFWriter::Annot:
                bAnnotAttribute =
                bLanguage = true;
                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);
        }
 
        if (bAnnotAttribute)
        {
            SwRect aPorRect;
            rInf.CalcRect(*pPor, &aPorRect);
            const NoteIdMap& rNoteIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap);
            const Point aCenter = aPorRect.Center();
            auto aIter = std::find_if(rNoteIdMap.begin(), rNoteIdMap.end(),
                                      [&aCenter](const IdMapEntry& rEntry)
                                      { return rEntry.first.Contains(aCenter); });
            if (aIter != rNoteIdMap.end())
            {
                sal_Int32 nNoteId = (*aIter).second;
                mpPDFExtOutDevData->SetStructureAttributeNumerical(vcl::PDFWriter::NoteAnnotation,
                                                                   nNoteId);
            }
        }
    }
    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::PostIts:
            if (!mpPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap.empty())
            {
                nPDFType = vcl::PDFWriter::Annot;
                aPDFType = aAnnotString;
            }
            break;
 
        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;
    }
    return MapSwRectToPDFRect(pCurrPage, rRectangle);
}
 
double SwEnhancedPDFExportHelper::GetSwRectToPDFRectScale()
{
    return 0.75;
}
 
tools::Rectangle SwEnhancedPDFExportHelper::MapSwRectToPDFRect(const SwPageFrame* pCurrPage,
                                                               const tools::Rectangle& 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 = GetSwRectToPDFRectScale();
    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::pdf::PDFNote aNote;
                    // The title should consist of the author and the date:
                    aNote.maTitle = pField->GetPar1() + ", " + sDate + ", " + (pField->GetResolved() ? SwResId(STR_RESOLVED) : u""_ustr);
                    // Guess what the contents contains...
                    aNote.maContents = pField->GetText();
 
                    tools::Rectangle aPopupRect(0, 0);
                    SwPostItMgr* pPostItMgr = pDoc->GetEditShell()->GetPostItMgr();
                    for (auto it = pPostItMgr->begin(); it != pPostItMgr->end(); ++it)
                    {
                        sw::annotation::SwAnnotationWin* pWin = it->get()->mpPostIt;
                        if (pWin)
                        {
                            const SwRect& aAnnotRect = pWin->GetAnchorRect();
                            if (aAnnotRect.Contains(rNoteRect))
                            {
                                Point aPt(pDoc->GetEditShell()->GetWin()->PixelToLogic(pWin->GetPosPixel()));
                                Size aSize(pDoc->GetEditShell()->GetWin()->PixelToLogic(pWin->GetSizePixel()));
                                aPopupRect = tools::Rectangle(aPt, aSize);
                            }
                        }
                    }
 
                    // Link Export
                    tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rNoteRect.SVRect()));
                    sal_Int32 nNoteId = pPDFExtOutDevData->CreateNote(aRect, aNote, aPopupRect, aNotePageNum);
 
                    if (mrPrintData.GetPrintPostIts() != SwPostItMode::InMargins)
                    {
                        const IdMapEntry aNoteEntry(aRect, nNoteId);
                        pPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap.push_back(aNoteEntry);
                    }
                }
                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 altText(p->rINetAttr.GetINetFormat().GetName());
                    if (altText.isEmpty())
                        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(u"Title"_ustr) >>= title;
                        OUString description;
                        xShapePropSet->getPropertyValue(u"Description"_ustr) >>= description;
                        OUString const altText(title.isEmpty()
                            ? description
                            : description.isEmpty()
                                ? title
                                : OUString::Concat(title) + OUString::Concat("\n") + OUString::Concat(description));
 
                        OUString aMediaURL;
                        xShapePropSet->getPropertyValue(u"MediaURL"_ustr) >>= aMediaURL;
                        if (!aMediaURL.isEmpty())
                        {
                            OUString const mimeType(xShapePropSet->getPropertyValue(u"MediaMimeType"_ustr).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(u"PrivateTempFileURL"_ustr) >>= 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, u""_ustr, 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
 
            // outlines inside flys (text frames) collected before the normal
            // outlines by GetOutLineNds(), so store them with page/position data
            // to insert later on the right page and position:
            // tuple< nDestPageNum, rDestRect, nLevel, rEntry, nDestId >
            typedef std::tuple< sal_Int32, SwRect, sal_Int32, const OUString, sal_Int32 > FlyEntry;
            std::vector< FlyEntry > aFlyVector;
            sal_Int32 nStartFly = 0; // first not processed item in aFlyVector
 
            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();
                assert(pTNd && "Enhanced pdf export - text node is missing");
 
                if ( pTNd->IsHidden() ||
                     !sw::IsParaPropsNode(*mrSh.GetLayout(), *pTNd) ||
                     // #i40292# Skip empty outlines:
                     pTNd->GetText().isEmpty())
                    continue;
 
                // Check if outline is inside a text frame
                bool bFlyOutline = pTNd->GetFlyFormat();
 
                // save outline stack to use for postponed fly outlines
                std::stack< StackEntry > aSavedOutlineStack;
                if ( !aFlyVector.empty() && !bFlyOutline )
                    aSavedOutlineStack = aOutlineStack;
 
                // 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 aEntry = mrSh.getIDocumentOutlineNodesAccess()->getOutlineText(
                        i, mrSh.GetLayout(), true, false, false );
 
                    // postpone fly outlines
                    if ( bFlyOutline )
                    {
                        aFlyVector.push_back(
                                    FlyEntry(nDestPageNum, rDestRect, nLevel, aEntry, nDestId) );
                        continue;
                    }
 
                    // create new outline items from postponed fly outlines, if they are before
                    // the recent not fly outline (and after the already created fly outlines)
                    for (size_t j = nStartFly; j < aFlyVector.size(); ++j)
                    {
                        if ( std::get<0>(aFlyVector[j]) < nDestPageNum ||
                             ( std::get<0>(aFlyVector[j]) == nDestPageNum &&
                               std::get<1>(aFlyVector[j]).Pos().Y() < rDestRect.Pos().Y() ) )
                        {
                            sal_Int32 nFlyLevel = std::get<2>(aFlyVector[j]);
                            sal_Int8 nLevelOnTopOfSavedStack = aSavedOutlineStack.top().first;
                            while ( nLevelOnTopOfSavedStack >= nFlyLevel &&
                                    nLevelOnTopOfSavedStack != -1 )
                            {
                                aSavedOutlineStack.pop();
                                nLevelOnTopOfSavedStack = aSavedOutlineStack.top().first;
                            }
                            const sal_Int32 nFlyParent = aSavedOutlineStack.top().second;
                            const sal_Int32 nId = pPDFExtOutDevData->CreateOutlineItem( nFlyParent,
                                                      std::get<3>(aFlyVector[j]),
                                                      std::get<4>(aFlyVector[j]) );
                            // Push current level and outline id on saved stack:
                            aSavedOutlineStack.push( StackEntry( nFlyLevel, nId ) );
                            ++nStartFly;
                        }
                        else
                            break;
                    }
 
                    // Create a new outline item:
                    const sal_Int32 nOutlineId =
                        pPDFExtOutDevData->CreateOutlineItem( nParent, aEntry, nDestId );
 
                    // Push current level and nOutlineId on stack:
                    aOutlineStack.push( StackEntry( nLevel, nOutlineId ) );
                }
            }
 
            // create remaining fly outlines
            for (size_t j = nStartFly; j < aFlyVector.size(); ++j)
            {
                sal_Int32 nLevel = std::get<2>(aFlyVector[j]);
                sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first;
                while ( nLevelOnTopOfStack >= nLevel &&
                        nLevelOnTopOfStack != -1 )
                {
                    aOutlineStack.pop();
                    nLevelOnTopOfStack = aOutlineStack.top().first;
                }
                const sal_Int32 nParent = aOutlineStack.top().second;
 
                const sal_Int32 nOutlineId = pPDFExtOutDevData->CreateOutlineItem( nParent,
                                          std::get<3>(aFlyVector[j]),
                                          std::get<4>(aFlyVector[j]) );
                aOutlineStack.push( StackEntry( std::get<2>(aFlyVector[j]), 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(auto ppMark = pMarkAccess->getBookmarksBegin();
                ppMark != pMarkAccess->getBookmarksEnd();
                ++ppMark)
            {
                //get the name
                const ::sw::mark::MarkBase* 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 aURL = rAuthorityField.GetAbsoluteURL();
            if (aURL.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, aURL);
                }
            }
            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: */

V547 Expression 'nullptr != p' is always true.