/* -*- 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 <names.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 SwTable*, sal_Int32 > TableCaptionsMap;
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;
    TableCaptionsMap m_TableCaptionsMap;
    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;
constexpr OUString constTitleStyleName = u"Title"_ustr;
constexpr OUString constEmphasisStyleName = u"Emphasis"_ustr;
constexpr OUString constStrongEmphasisStyleName = u"Strong Emphasis"_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();

        ProgName sStyleName;
        SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
        bRet = sStyleName.toString() == 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);
        TableStyleName const& rStyleName(pTable->GetTableStyleName());
        if (!rStyleName.isEmpty())
        {
            if (SwTableAutoFormat const*const pTableAF =
                pTable->GetFrameFormat()->GetDoc().GetTableStyles().FindAutoFormat(rStyleName))
            {
                bRet |= pTableAF->HasHeaderRow();
            }
        }
    }

    return bRet;
}

// returns true if the frame is a caption
bool lcl_IsCaptionFrame(const SwFrame& rFrame)
{
    if (!rFrame.IsTextFrame())
        return false;

    SwTextFrame const& rTextFrame(*static_cast<const SwTextFrame*>(&rFrame));
    const SwTextNode* const pTextNd(rTextFrame.GetTextNodeForParaProps());
    if (!pTextNd)
        return false;

    const SwFormat* pTextFormat = pTextNd->GetFormatColl();
    const SwFormat* pParentTextFormat = pTextFormat ? pTextFormat->DerivedFrom() : nullptr;

    ProgName sParentStyleName;
    if (pParentTextFormat)
        SwStyleNameMapper::FillProgName(pParentTextFormat->GetName(), sParentStyleName,
                                        SwGetPoolIdFromName::TxtColl);

    return sParentStyleName == aCaption;
}

const SwTabFrame* lcl_FindTableForCaption(const SwFrame& rFrame)
{
    const SwTabFrame* pTabFrame = nullptr;
    bool bPrevFrame = false;

    // It is possible to add multiple captions to a table,
    // both above and below, either simultaneously or separately.
    // Start by checking the next frame, and if we don't find a table frame
    // or if the next frame is not a caption, we return to the current caption
    // and perform the same operation backwards using the previous frames.
    const SwFrame* pRetFrame = rFrame.GetNext();
    if (!pRetFrame)
    {
        bPrevFrame = true;
        pRetFrame = rFrame.GetPrev();
    }

    while (pRetFrame)
    {
        if (pRetFrame->IsTabFrame())
        {
            pTabFrame = static_cast<const SwTabFrame*>(pRetFrame);
            break;
        }

        // Check if the next or the previous frame is a caption frame
        bool bIsCaption = lcl_IsCaptionFrame(*pRetFrame);
        if (bIsCaption && pRetFrame->GetNext())
        {
            pRetFrame = !bPrevFrame ? pRetFrame->GetNext() : pRetFrame->GetPrev();
        }
        else if (!bPrevFrame && rFrame.GetPrev())
        {
            // If no table was found while checking the GetNext() frames,
            // jump back to the current caption and
            // start checking the GetPrev() frames.
            bPrevFrame = true;
            pRetFrame = rFrame.GetPrev();
        }
        else
            // This part handles the case
            // when the table has been deleted,
            // but the caption has not.
            break;
    }

    return pTabFrame;
}

// 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::pdf::StructElement::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.contains(pKey)
                || 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::pdf::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::pdf::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() ) ||
               rFrame.IsTabFrame() )
        {
            pKey = lcl_GetKeyFromFrame(rFrame);

            if (pKey)
            {
                FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
                assert(!rFrameTagSet.contains(pKey));
                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::pdf::StructElement::List == eType)
        {
            NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
            rNumListIdMap[ pNodeNum ] = nId;
        }
        else if (vcl::pdf::StructElement::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::pdf::PDFWriter::LinkAnnotation, nLinkId);
        }
    }
}

// Sets the attributes according to the structure type.
void SwTaggedPDFHelper::SetAttributes(vcl::pdf::StructElement eType)
{
    sal_Int32 nVal;

    /*
     * ATTRIBUTES FOR BLSE
     */
    if ( mpFrameInfo )
    {
        vcl::pdf::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::pdf::StructElement::Document:
                bWritingMode = true;
                break;

            case vcl::pdf::StructElement::Note:
                bPlacement = true;
                break;

            case vcl::pdf::StructElement::Table:
                bPlacement =
                bWritingMode =
                bSpaceBefore =
                bSpaceAfter =
                bStartIndent =
                bEndIndent =
                bWidth =
                bHeight =
                bBox = true;
                break;

            case vcl::pdf::StructElement::TableRow:
                bPlacement =
                bWritingMode = true;
                break;

            case vcl::pdf::StructElement::TableHeader:
                mpPDFExtOutDevData->SetStructureAttribute(vcl::pdf::PDFWriter::Scope, vcl::pdf::PDFWriter::Column);
                [[fallthrough]];
            case vcl::pdf::StructElement::TableData:
                bPlacement =
                bWritingMode =
                bWidth =
                bHeight =
                bRowSpan = true;
                break;

            case vcl::pdf::StructElement::Caption:
                if (pFrame->IsSctFrame())
                {
                    break;
                }
                [[fallthrough]];
            case vcl::pdf::StructElement::H1:
            case vcl::pdf::StructElement::H2:
            case vcl::pdf::StructElement::H3:
            case vcl::pdf::StructElement::H4:
            case vcl::pdf::StructElement::H5:
            case vcl::pdf::StructElement::H6:
            case vcl::pdf::StructElement::Paragraph:
            case vcl::pdf::StructElement::Heading:
            case vcl::pdf::StructElement::BlockQuote:
            case vcl::pdf::StructElement::Title:
                bPlacement =
                bWritingMode =
                bSpaceBefore =
                bSpaceAfter =
                bStartIndent =
                bEndIndent =
                bTextIndent =
                bTextAlign = true;
                break;

            case vcl::pdf::StructElement::Formula:
            case vcl::pdf::StructElement::Figure:
                bAltText =
                bPlacement =
                bWidth =
                bHeight =
                bBox = true;
                break;

            case vcl::pdf::StructElement::Division:
                if (pFrame->IsFlyFrame()) // this can be something else too
                {
                    bAltText = true;
                    bBox = true;
                }
                break;

            case vcl::pdf::StructElement::NonStructElement:
                if (pFrame->IsHeaderFrame() || pFrame->IsFooterFrame())
                {
                    // ISO 14289-1:2014, Clause: 7.8
                    mpPDFExtOutDevData->SetStructureAttribute(vcl::pdf::PDFWriter::Type, vcl::pdf::PDFWriter::Pagination);
                    mpPDFExtOutDevData->SetStructureAttribute(vcl::pdf::PDFWriter::Subtype,
                        pFrame->IsHeaderFrame()
                           ? vcl::pdf::PDFWriter::Header
                           : vcl::pdf::PDFWriter::Footer);
                }
            break;

            default :
                break;
        }

        // Set the attributes:

        if ( bPlacement )
        {
            bool bIsFigureInline = false;
            if (vcl::pdf::StructElement::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::pdf::StructElement::TableHeader == eType
                || vcl::pdf::StructElement::TableData == eType
                || bIsFigureInline
                       ? vcl::pdf::PDFWriter::Inline
                       : vcl::pdf::PDFWriter::Block;

            mpPDFExtOutDevData->SetStructureAttribute( vcl::pdf::PDFWriter::Placement, eVal );
        }

        if ( bWritingMode )
        {
            eVal =  pFrame->IsVertical() ?
                    vcl::pdf::PDFWriter::TbRl :
                    pFrame->IsRightToLeft() ?
                    vcl::pdf::PDFWriter::RlTb :
                    vcl::pdf::PDFWriter::LrTb;

            if ( vcl::pdf::PDFWriter::LrTb != eVal )
                mpPDFExtOutDevData->SetStructureAttribute( vcl::pdf::PDFWriter::WritingMode, eVal );
        }

        if ( bSpaceBefore )
        {
            nVal = aRectFnSet.GetTopMargin(*pFrame);
            if ( 0 != nVal )
                mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::pdf::PDFWriter::SpaceBefore, nVal );
        }

        if ( bSpaceAfter )
        {
            nVal = aRectFnSet.GetBottomMargin(*pFrame);
            if ( 0 != nVal )
                mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::pdf::PDFWriter::SpaceAfter, nVal );
        }

        if ( bStartIndent )
        {
            nVal = aRectFnSet.GetLeftMargin(*pFrame);
            if ( 0 != nVal )
                mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::pdf::PDFWriter::StartIndent, nVal );
        }

        if ( bEndIndent )
        {
            nVal = aRectFnSet.GetRightMargin(*pFrame);
            if ( 0 != nVal )
                mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::pdf::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.ResolveTextFirstLineOffset({});
            if ( 0 != nVal )
                mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::pdf::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::pdf::PDFWriter::Justify :
                       SvxAdjust::Center == nAdjust ?
                       vcl::pdf::PDFWriter::Center :
                       vcl::pdf::PDFWriter::End;

                mpPDFExtOutDevData->SetStructureAttribute( vcl::pdf::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::pdf::PDFWriter::Width, nVal );
        }

        if ( bHeight )
        {
            nVal = aRectFnSet.GetHeight(pFrame->getFrameArea());
            mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::pdf::PDFWriter::Height, nVal );
        }

        if ( bBox )
        {
            // BBox only for non-split tables:
            if (vcl::pdf::StructElement::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::pdf::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::pdf::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::pdf::StructElement::Span:
            case vcl::pdf::StructElement::Quote:
            case vcl::pdf::StructElement::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::pdf::StructElement::Link:
            case vcl::pdf::StructElement::BibEntry:
                bTextDecorationType =
                bBaselineShift =
                bLinkAttribute =
                bLanguage = true;
                break;

            case vcl::pdf::StructElement::RT:
                {
                    SwRubyPortion const*const pRuby(static_cast<SwRubyPortion const*>(pPor));
                    vcl::pdf::PDFWriter::StructAttributeValue nAlign = {};
                    switch (pRuby->GetAdjustment())
                    {
                        case text::RubyAdjust_LEFT:
                            nAlign = vcl::pdf::PDFWriter::RStart;
                            break;
                        case text::RubyAdjust_CENTER:
                            nAlign = vcl::pdf::PDFWriter::RCenter;
                            break;
                        case text::RubyAdjust_RIGHT:
                            nAlign = vcl::pdf::PDFWriter::REnd;
                            break;
                        case text::RubyAdjust_BLOCK:
                            nAlign = vcl::pdf::PDFWriter::RJustify;
                            break;
                        case text::RubyAdjust_INDENT_BLOCK:
                            nAlign = vcl::pdf::PDFWriter::RDistribute;
                            break;
                        default:
                            assert(false);
                            break;
                    }
                    ::std::optional<vcl::pdf::PDFWriter::StructAttributeValue> oPos;
                    switch (pRuby->GetRubyPosition())
                    {
                        case RubyPosition::ABOVE:
                            oPos = vcl::pdf::PDFWriter::RBefore;
                            break;
                        case RubyPosition::BELOW:
                            oPos = vcl::pdf::PDFWriter::RAfter;
                            break;
                        case RubyPosition::RIGHT:
                            break; // no such thing???
                    }
                    mpPDFExtOutDevData->SetStructureAttribute(vcl::pdf::PDFWriter::RubyAlign, nAlign);
                    if (oPos)
                    {
                        mpPDFExtOutDevData->SetStructureAttribute(vcl::pdf::PDFWriter::RubyPosition, *oPos);
                    }
                }
                break;

            case vcl::pdf::StructElement::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::pdf::PDFWriter::BaselineShift, nVal );
            }
        }

        if ( bTextDecorationType )
        {
            if ( LINESTYLE_NONE    != rInf.GetFont()->GetUnderline() )
                mpPDFExtOutDevData->SetStructureAttribute( vcl::pdf::PDFWriter::TextDecorationType, vcl::pdf::PDFWriter::Underline );
            if ( LINESTYLE_NONE    != rInf.GetFont()->GetOverline() )
                mpPDFExtOutDevData->SetStructureAttribute( vcl::pdf::PDFWriter::TextDecorationType, vcl::pdf::PDFWriter::Overline );
            if ( STRIKEOUT_NONE    != rInf.GetFont()->GetStrikeout() )
                mpPDFExtOutDevData->SetStructureAttribute( vcl::pdf::PDFWriter::TextDecorationType, vcl::pdf::PDFWriter::LineThrough );
            if ( FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() )
                mpPDFExtOutDevData->SetStructureAttribute( vcl::pdf::PDFWriter::TextDecorationType, vcl::pdf::PDFWriter::Overline );
        }

        if ( bLanguage )
        {

            const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage();
            const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault);

            if ( nDefaultLang != nCurrentLanguage )
                mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::pdf::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::pdf::PDFWriter::NoteAnnotation,
                                                                   nNoteId);
            }
        }
    }
    else if (mpNumInfo && eType == vcl::pdf::StructElement::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::pdf::PDFWriter::UpperAlpha;
                case css::style::NumberingType::CHARS_LOWER_LETTER:
                    return vcl::pdf::PDFWriter::LowerAlpha;
                case css::style::NumberingType::ROMAN_UPPER:
                    return vcl::pdf::PDFWriter::UpperRoman;
                case css::style::NumberingType::ROMAN_LOWER:
                    return vcl::pdf::PDFWriter::LowerRoman;
                case css::style::NumberingType::ARABIC:
                    return vcl::pdf::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::pdf::PDFWriter::Disc;
                        case u'\u2218': case u'\u25CB': case u'\u25E6':
                            return vcl::pdf::PDFWriter::Circle;
                        case u'\u25A0': case u'\u25AA': case u'\uE00A':
                            return vcl::pdf::PDFWriter::Square;
                        default:
                            return vcl::pdf::PDFWriter::NONE;
                    }
                default: // the other 50 types
                    return vcl::pdf::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::pdf::PDFWriter::StructAttributeValue const value(
                ToPDFListNumbering(pNumRule->Get(rNode.GetActualListLevel())));
        // ISO 14289-1:2014, Clause: 7.6
        mpPDFExtOutDevData->SetStructureAttribute(vcl::pdf::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::pdf::StructElement::List, aListString);

    if ( bNewItemTag )
    {
        BeginTag(vcl::pdf::StructElement::ListItem, aListItemString);
        assert(rTextFrame.GetPara());
        // check whether to open LBody now or delay until after Lbl
        if (!rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering))
        {
            BeginTag(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::Document);
            aPDFType = aDocumentString;
            break;

        case SwFrameType::Header :
        case SwFrameType::Footer :

            // Header, Footer: NonStructElement

            nPDFType = sal_uInt16(vcl::pdf::StructElement::NonStructElement);
            break;

        case SwFrameType::FootnoteContainer:

            // Footnote container: Division

            nPDFType = sal_uInt16(vcl::pdf::StructElement::Division);
            aPDFType = aDivString;
            break;

        case SwFrameType::Footnote:

            // Footnote frame: Note

            // Note: vcl::pdf::PDFWriter::Note is actually a ILSE. Nevertheless
            // we treat it like a grouping element!
            nPDFType = sal_uInt16(vcl::pdf::StructElement::Note);
            aPDFType = aNoteString;
            break;

        case SwFrameType::Section :

            // Section: TOX, Index, or Sect

            {
                const SwSection* pSection =
                        static_cast<const SwSectionFrame*>(pFrame)->GetSection();

                if (SwSectionNode const* pFormatSectionNode = pSection->GetFormat()->GetSectionNode())
                {
                    // open all parent sections, so that the SEs of sections
                    // are nested in the same way as their SwSectionNodes
                    std::vector<SwSection const*> parents;
                    // iterate only *direct* parents - do not leave table cell!
                    for (SwSectionNode const* pSectionNode{pFormatSectionNode->StartOfSectionNode()->GetSectionNode()};
                        pSectionNode != nullptr;
                        pSectionNode = pSectionNode->StartOfSectionNode()->GetSectionNode())
                    {
                        parents.push_back(&pSectionNode->GetSection());
                    }
                    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.contains(pSection))
                {
                    // 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 = sal_uInt16(vcl::pdf::StructElement::Caption);
                    aPDFType = aCaptionString;
                }
                else if (SectionType::ToxContent == pSection->GetType())
                {
                    const SwTOXBase* pTOXBase = pSection->GetTOXBase();
                    if ( pTOXBase )
                    {
                        if ( TOX_INDEX == pTOXBase->GetType() )
                        {
                            nPDFType = sal_uInt16(vcl::pdf::StructElement::Index);
                            aPDFType = aIndexString;
                        }
                        else
                        {
                            nPDFType = sal_uInt16(vcl::pdf::StructElement::TOC);
                            aPDFType = aTOCString;
                        }
                    }
                }
                else if ( SectionType::Content == pSection->GetType() )
                {
                    nPDFType = sal_uInt16(vcl::pdf::StructElement::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::pdf::StructElement::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;

                ProgName sStyleName;
                ProgName 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 = sal_uInt16(vcl::pdf::StructElement::Paragraph);
                aPDFType = sStyleName.toString();

                // Title

                if (sStyleName == constTitleStyleName)
                {
                    nPDFType = sal_uInt16(vcl::pdf::StructElement::Title);
                    aPDFType = constTitleStyleName;
                }

                // Quotations: BlockQuote

                if (sStyleName == aQuotations)
                {
                    nPDFType = sal_uInt16(vcl::pdf::StructElement::BlockQuote);
                    aPDFType = aBlockQuoteString;
                }

                // Caption: Caption

                else if (sStyleName == aCaption)
                {
                    nPDFType = sal_uInt16(vcl::pdf::StructElement::Caption);
                    aPDFType = aCaptionString;
                }

                // Caption: Caption

                else if (sParentStyleName == aCaption)
                {
                    OUString sTableCaption = sStyleName.toString() + aCaptionString;

                    if (!pFrame->IsInFly()) // Table caption
                    {
                        TableCaptionsMap& rTableCaptionsMap(
                            mpPDFExtOutDevData->GetSwPDFState()->m_TableCaptionsMap);

                        const SwTabFrame* pTabFrame = lcl_FindTableForCaption(*pFrame);
                        if (pTabFrame)
                        {
                            const SwTable* pTable = pTabFrame->GetTable();
                            if (rTableCaptionsMap.contains(pTable))
                            {
                                // Reopen Caption tag:
                                // - if the table has an above and below caption
                                // - if the table has multiple above or below captions
                                m_nRestoreCurrentTag
                                    = mpPDFExtOutDevData->GetCurrentStructureElement();

                                sal_Int32 const nCaptionId = rTableCaptionsMap[pTable];
                                mpPDFExtOutDevData->SetCurrentStructureElement(nCaptionId);
                            }
                            else
                            {
                                OpenTagImpl(pTable);

                                // Open Caption tag
                                sal_Int32 const nId = BeginTagImpl(
                                    nullptr, vcl::pdf::StructElement::Caption, sTableCaption);

                                rTableCaptionsMap[pTable] = nId;
                            }
                        }
                        aPDFType = "Standard";
                    }
                    else // Figure caption
                    {
                        nPDFType = sal_uInt16(vcl::pdf::StructElement::Caption);
                        aPDFType = sTableCaption;
                    }
                }

                // Heading: H

                else if (sStyleName == aHeading)
                {
                    nPDFType = sal_uInt16(vcl::pdf::StructElement::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>(sal_uInt16(vcl::pdf::StructElement::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::pdf::StructElement::TOCI, aTOCIString);
                        }
                    }
                }
            }
            break;

        case SwFrameType::Tab :

            // TabFrame: Table

            nPDFType = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::TableRow);
                aPDFType = aTRString;
            }
            else
            {
                nPDFType = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::TableHeader);
                    aPDFType = aTHString;
                }
                else
                {
                    nPDFType = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::Formula);
                        aPDFType = aFormulaString;
                    }
                    else
                    {
                        nPDFType = sal_uInt16(vcl::pdf::StructElement::Figure);
                        aPDFType = aFigureString;
                    }
                }
                else
                {
                    nPDFType = sal_uInt16(vcl::pdf::StructElement::Division);
                    aPDFType = aDivString;
                }
            }
            break;

        default: break;
    }

    if ( USHRT_MAX != nPDFType )
    {
        BeginTag(vcl::pdf::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);

    ProgName 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.toString(), pInetFormatAttr));

    sal_uInt16 nPDFType = USHRT_MAX;
    OUString aPDFType;

    switch ( pPor->GetWhichPor() )
    {
        case PortionType::PostIts:
            if (!mpPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap.empty())
            {
                nPDFType = sal_uInt16(vcl::pdf::StructElement::Annot);
                aPDFType = aAnnotString;
            }
            break;

        case PortionType::Hyphen :
        case PortionType::SoftHyphen :
        // Check for alternative spelling:
        case PortionType::HyphenStr :
        case PortionType::SoftHyphenStr :
            nPDFType = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::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;
                    }
                }
                // Emphasis
                else if (sStyleName == constEmphasisStyleName)
                {
                    if (!isContinueSpan)
                    {
                        nPDFType = sal_uInt16(vcl::pdf::StructElement::Emphasis);
                        aPDFType = constEmphasisStyleName;
                        CreateCurrentSpan(rInf, sStyleName.toString());
                    }
                }
                // Strong
                else if (sStyleName == constStrongEmphasisStyleName)
                {
                    if (!isContinueSpan)
                    {
                        nPDFType = sal_uInt16(vcl::pdf::StructElement::Strong);
                        aPDFType = constStrongEmphasisStyleName;
                        CreateCurrentSpan(rInf, sStyleName.toString());
                    }
                }
                // Check for Quote/Code character style:
                else if (sStyleName == aQuotation)
                {
                    if (!isContinueSpan)
                    {
                        nPDFType = sal_uInt16(vcl::pdf::StructElement::Quote);
                        aPDFType = aQuoteString;
                        CreateCurrentSpan(rInf, sStyleName.toString());
                    }
                }
                else if (sStyleName == aSourceText)
                {
                    if (!isContinueSpan)
                    {
                        nPDFType = sal_uInt16(vcl::pdf::StructElement::Code);
                        aPDFType = aCodeString;
                        CreateCurrentSpan(rInf, sStyleName.toString());
                    }
                }
                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 = sal_uInt16(vcl::pdf::StructElement::Span);
                        if (!sStyleName.isEmpty())
                            aPDFType = sStyleName.toString();
                        else
                            aPDFType = aSpanString;
                        CreateCurrentSpan(rInf, sStyleName.toString());
                    }
                }
            }
            break;

        case PortionType::Footnote :
            nPDFType = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::Link);
                        aPDFType = aLinkString;
                    }
                    else if ( SwFieldIds::TableOfAuthorities == pField->Which() )
                    {
                        nPDFType = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::Ruby);
                            aPDFType = "Ruby";
                        break;
                        case 1:
                            nPDFType = sal_uInt16(vcl::pdf::StructElement::RT);
                            aPDFType = "RT";
                        break;
                        case 2:
                            nPDFType = sal_uInt16(vcl::pdf::StructElement::RB);
                            aPDFType = "RB";
                        break;
                    }
                }
                else if (pMulti->IsDouble())
                {
                    EndCurrentAll();
                    switch (mpPorInfo->m_Mode)
                    {
                        case 0:
                            nPDFType = sal_uInt16(vcl::pdf::StructElement::Warichu);
                            aPDFType = "Warichu";
                        break;
                        case 1:
                            nPDFType = sal_uInt16(vcl::pdf::StructElement::WP);
                            aPDFType = "WP";
                        break;
                        case 2:
                            nPDFType = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::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 = sal_uInt16(vcl::pdf::StructElement::LILabel);
                aPDFType = aListLabelString;
            }
            break;

        case PortionType::Tab :
        case PortionType::TabRight :
        case PortionType::TabCenter :
        case PortionType::TabDecimal :
            nPDFType = sal_uInt16(vcl::pdf::StructElement::NonStructElement);
            break;
        default: break;
    }

    if ( USHRT_MAX != nPDFType )
    {
        BeginTag( static_cast<vcl::pdf::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:

    auto popIt = mrOut.ScopedPush(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());

                    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, SwMarkName(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, SwMarkName(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 );
                    UIName const linkName(pItem->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, linkName.toString(), 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, linkName.toString());
                            }
                        }
                    }
                }
            }
            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 SwMarkName& 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 );

                    // 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, rRefName.toString(), 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, rRefName.toString());
                            }
                        }
                    }
                }
            }
            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;
            }

            // 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" );

            // restore cursor position
            mrSh.GetCursor_()->GetPoint()->Assign(rTNd, pTextFootnote->GetStart());
            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, numStrRef, 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, numStrSymbol, 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 SwMarkName& 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.toString(), 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, SwMarkName(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);
}

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.GetAuthority(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.GetAuthority(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: */
