Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/LibreOffice/sw/source/core/text/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 132 kB image not shown  

SSL EnhancedPDFExportHelper.cxx

  Sprache: C
 

/* -*- 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, boolconst 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::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::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::PDFWriter::Scope, vcl::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::PDFWriter::Type, vcl::PDFWriter::Pagination);
                    mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Subtype,
                        pFrame->IsHeaderFrame()
                           ? vcl::PDFWriter::Header
                           : vcl::PDFWriter::Footer);
                }
            break;

            default :
                break;
        }

        // Set the attributes:

        if ( bPlacement )
        {
            bool bIsFigureInline = false;
            if (vcl::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::PDFWriter::Inline
                       : vcl::PDFWriter::Block;

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

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

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

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

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

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

        if ( bEndIndent )
        {
            nVal = aRectFnSet.GetRightMargin(*pFrame);
            if ( 0 != nVal )
                mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::EndIndent, nVal );
        }

        if ( bTextIndent )
        {
            OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" );
            const SvxFirstLineIndentItem& rFirstLine(
                static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent());
            nVal = rFirstLine.ResolveTextFirstLineOffset({});
            if ( 0 != nVal )
                mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::TextIndent, nVal );
        }

        if ( bTextAlign )
        {
            OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" );
            const SwAttrSet& aSet = static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet();
            const SvxAdjust nAdjust = aSet.GetAdjust().GetAdjust();
            if ( SvxAdjust::Block == nAdjust || SvxAdjust::Center == nAdjust ||
                 (  (pFrame->IsRightToLeft() && SvxAdjust::Left == nAdjust) ||
                   (!pFrame->IsRightToLeft() && SvxAdjust::Right == nAdjust) ) )
            {
                eVal = SvxAdjust::Block == nAdjust ?
                       vcl::PDFWriter::Justify :
                       SvxAdjust::Center == nAdjust ?
                       vcl::PDFWriter::Center :
                       vcl::PDFWriter::End;

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

        // ISO 14289-1:2014, Clause: 7.3
        // ISO 14289-1:2014, Clause: 7.7
        // For images (but not embedded objects), an ObjectInfoPrimitive2D is
        // created, but it's not evaluated by VclMetafileProcessor2D any more;
        // that would require producing StructureTagPrimitive2D here but that
        // looks impossible so instead duplicate the code that sets the Alt
        // text here again.
        if (bAltText)
        {
            SwFlyFrameFormat const& rFly(*static_cast<SwFlyFrame const*>(pFrame)->GetFormat());
            OUString const sep(
                (rFly.GetObjTitle().isEmpty() || rFly.GetObjDescription().isEmpty())
                ? OUString() : u" - "_ustr);
            OUString const altText(rFly.GetObjTitle() + sep + rFly.GetObjDescription());
            if (!altText.isEmpty())
            {
                mpPDFExtOutDevData->SetAlternateText(altText);
            }
        }

        if ( bWidth )
        {
            nVal = aRectFnSet.GetWidth(pFrame->getFrameArea());
            mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Width, nVal );
        }

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

        if ( bBox )
        {
            // BBox only for non-split tables:
            if (vcl::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::PDFWriter::RowSpan, nVal );

                // calculate colspan:
                const SwTabFrame* pTabFrame = pThisCell->FindTabFrame();
                const SwTable* pTable = pTabFrame->GetTable();

                SwRectFnSet fnRectX(pTabFrame);

                const TableColumnsMapEntry& rCols(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap[pTable]);

                const tools::Long nLeft  = fnRectX.GetLeft(pThisCell->getFrameArea());
                const tools::Long nRight = fnRectX.GetRight(pThisCell->getFrameArea());
                const TableColumnsMapEntry::const_iterator aLeftIter =  rCols.find( nLeft );
                const TableColumnsMapEntry::const_iterator aRightIter = rCols.find( nRight );

                OSL_ENSURE( aLeftIter != rCols.end() && aRightIter != rCols.end(), "Colspan trouble" );
                if ( aLeftIter != rCols.end() && aRightIter != rCols.end() )
                {
                    nVal = std::distance( aLeftIter, aRightIter );
                    if ( nVal > 1 )
                        mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::ColSpan, nVal );
                }
            }
        }

        if (mpFrameInfo->m_isLink)
        {
            SwRect const aRect(mpFrameInfo->mrFrame.getFrameArea());
            LinkLinkLink(*mpPDFExtOutDevData, aRect);
        }
    }

    /*
     * ATTRIBUTES FOR ILSE
     */

    else if ( mpPorInfo )
    {
        const SwLinePortion* pPor = &mpPorInfo->mrPor;
        const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo();

        bool bActualText = false;
        bool bBaselineShift = false;
        bool bTextDecorationType = false;
        bool bLinkAttribute = false;
        bool bAnnotAttribute = false;
        bool bLanguage = false;

        // Check which attributes to set:

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

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

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

        if ( bLanguage )
        {

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

            if ( nDefaultLang != nCurrentLanguage )
                mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Language, static_cast<sal_uInt16>(nCurrentLanguage) );
        }

        if ( bLinkAttribute )
        {
            SwRect aPorRect;
            rInf.CalcRect( *pPor, &aPorRect );
            LinkLinkLink(*mpPDFExtOutDevData, aPorRect);
        }

        if (bAnnotAttribute)
        {
            SwRect aPorRect;
            rInf.CalcRect(*pPor, &aPorRect);
            const NoteIdMap& rNoteIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap);
            const Point aCenter = aPorRect.Center();
            auto aIter = std::find_if(rNoteIdMap.begin(), rNoteIdMap.end(),
                                      [&aCenter](const IdMapEntry& rEntry)
                                      { return rEntry.first.Contains(aCenter); });
            if (aIter != rNoteIdMap.end())
            {
                sal_Int32 nNoteId = (*aIter).second;
                mpPDFExtOutDevData->SetStructureAttributeNumerical(vcl::PDFWriter::NoteAnnotation,
                                                                   nNoteId);
            }
        }
    }
    else if (mpNumInfo && eType == vcl::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::PDFWriter::UpperAlpha;
                case css::style::NumberingType::CHARS_LOWER_LETTER:
                    return vcl::PDFWriter::LowerAlpha;
                case css::style::NumberingType::ROMAN_UPPER:
                    return vcl::PDFWriter::UpperRoman;
                case css::style::NumberingType::ROMAN_LOWER:
                    return vcl::PDFWriter::LowerRoman;
                case css::style::NumberingType::ARABIC:
                    return vcl::PDFWriter::Decimal;
                case css::style::NumberingType::CHAR_SPECIAL:
                    switch (rFormat.GetBulletChar())
                    {
                        case u'\u2022'case u'\uE12C'case u'\uE01E'case u'\uE437':
                            return vcl::PDFWriter::Disc;
                        case u'\u2218'case u'\u25CB'case u'\u25E6':
                            return vcl::PDFWriter::Circle;
                        case u'\u25A0'case u'\u25AA'case u'\uE00A':
                            return vcl::PDFWriter::Square;
                        default:
                            return vcl::PDFWriter::NONE;
                    }
                default// the other 50 types
                    return vcl::PDFWriter::NONE;
            }
        };

        // Note: for every level, BeginNumberedListStructureElements() produces
        // a separate List element, so even though in PDF this is limited to
        // the whole List we can just export the current level here.
        vcl::PDFWriter::StructAttributeValue const value(
                ToPDFListNumbering(pNumRule->Get(rNode.GetActualListLevel())));
        // ISO 14289-1:2014, Clause: 7.6
        mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::ListNumbering, value);
    }
}

void SwTaggedPDFHelper::BeginNumberedListStructureElements()
{
    OSL_ENSURE( mpNumInfo, "List without mpNumInfo?" );
    if ( !mpNumInfo )
        return;

    const SwFrame& rFrame = mpNumInfo->mrFrame;
    assert(rFrame.IsTextFrame());
    const SwTextFrame& rTextFrame = static_cast<const SwTextFrame&>(rFrame);

    // Lowers of NonStructureElements should not be considered:
    if (lcl_IsInNonStructEnv(rTextFrame))
        return;

    // do it for the first one in the follow chain that has content
    for (SwFlowFrame const* pPrecede = rTextFrame.GetPrecede(); pPrecede; pPrecede = pPrecede->GetPrecede())
    {
        SwTextFrame const*const pText(static_cast<SwTextFrame const*>(pPrecede));
        if (!pText->HasPara() || pText->GetPara()->HasContentPortions())
        {
            return;
        }
    }

    const SwTextNode *const pTextNd = rTextFrame.GetTextNodeForParaProps();
    const SwNumRule* pNumRule = pTextNd->GetNumRule();
    const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame());

    const bool bNumbered = !pTextNd->IsOutline() && pNodeNum && pNodeNum->GetParent() && pNumRule;

    // Check, if we have to reopen a list or a list body:
    // First condition:
    // Paragraph is numbered/bulleted
    if ( !bNumbered )
        return;

    const SwNumberTreeNode* pParent = pNodeNum->GetParent();
    const bool bSameNumbering = lcl_HasPreviousParaSameNumRule(rTextFrame, *pTextNd);

    // Second condition: current numbering is not 'interrupted'
    if ( bSameNumbering )
    {
        sal_Int32 nReopenTag = -1;

        // Two cases:
        // 1. We have to reopen an existing list body tag:
        // - If the current node is either the first child of its parent
        //   and its level > 1 or
        // - Numbering should restart at the current node and its level > 1
        // - The current item has no label
        const bool bNewSubListStart = pParent->GetParent() && (pParent->IsFirst( pNodeNum ) || pTextNd->IsListRestart() );
        const bool bNoLabel = !pTextNd->IsCountedInList() && !pTextNd->IsListRestart();
        if ( bNewSubListStart || bNoLabel )
        {
            // Fine, we try to reopen the appropriate list body
            NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);

            if ( bNewSubListStart )
            {
                // The list body tag associated with the parent has to be reopened
                // to start a new list inside the list body
                NumListBodyIdMap::const_iterator aIter;

                do
                    aIter = rNumListBodyIdMap.find( pParent );
                while ( aIter == rNumListBodyIdMap.end() && nullptr != ( pParent = pParent->GetParent() ) );

                if ( aIter != rNumListBodyIdMap.end() )
                    nReopenTag = (*aIter).second;
            }
            else // if(bNoLabel)
            {
                // The list body tag of a 'counted' predecessor has to be reopened
                const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true);
                while ( pPrevious )
                {
                    if ( pPrevious->IsCounted())
                    {
                        // get id of list body tag
                        const NumListBodyIdMap::const_iterator aIter =  rNumListBodyIdMap.find( pPrevious );
                        if ( aIter != rNumListBodyIdMap.end() )
                        {
                            nReopenTag = (*aIter).second;
                            break;
                        }
                    }
                    pPrevious = pPrevious->GetPred(true);
                }
            }
        }
        // 2. We have to reopen an existing list tag:
        else if ( !pParent->IsFirst( pNodeNum ) && !pTextNd->IsListRestart() )
        {
            // any other than the first node in a list level has to reopen the current
            // list. The current list is associated in a map with the first child of the list:
            NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);

            // Search backwards and check if any of the previous nodes has a list associated with it:
            const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true);
            while ( pPrevious )
            {
                // get id of list tag
                const NumListIdMap::const_iterator aIter =  rNumListIdMap.find( pPrevious );
                if ( aIter != rNumListIdMap.end() )
                {
                    nReopenTag = (*aIter).second;
                    break;
                }

                pPrevious = pPrevious->GetPred(true);
            }
        }

        if ( -1 != nReopenTag )
        {
            m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement();
            mpPDFExtOutDevData->SetCurrentStructureElement( nReopenTag );

#if OSL_DEBUG_LEVEL > 1
            aStructStack.push_back( 99 );
#endif
        }
    }
    else
    {
        // clear list maps in case a list has been interrupted
        NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
        rNumListIdMap.clear();
        NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
        rNumListBodyIdMap.clear();
    }

    // New tags:
    const bool bNewListTag = (pNodeNum->GetParent()->IsFirst( pNodeNum ) || pTextNd->IsListRestart() || !bSameNumbering);
    const bool bNewItemTag = bNewListTag || pTextNd->IsCountedInList(); // If the text node is not counted, we do not start a new list item:

    if ( bNewListTag )
        BeginTag(vcl::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::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;

        defaultbreak;
    }

    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;
        defaultbreak;
    }

    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:

    mrOut.Push( vcl::PushFlags::MAPMODE );
    MapMode aMapMode( mrOut.GetMapMode() );
    aMapMode.SetMapUnit( MapUnit::MapTwip );
    mrOut.SetMapMode( aMapMode );

    // Create new cursor and lock the view:

    SwDoc* pDoc = mrSh.GetDoc();
    mrSh.SwCursorShell::Push();
    mrSh.SwCursorShell::ClearMark();
    const bool bOldLockView = mrSh.IsViewLocked();
    mrSh.LockView( true );

    if ( !mbEditEngineOnly )
    {
        assert(pPDFExtOutDevData->GetSwPDFState() == nullptr);
        pPDFExtOutDevData->SetSwPDFState(new SwEnhancedPDFState(eLanguageDefault));

        // POSTITS

        if ( pPDFExtOutDevData->GetIsExportNotes() )
        {
            std::vector<SwFormatField*> vpFields;
            mrSh.GetFieldType(SwFieldIds::Postit, OUString())->GatherFields(vpFields);
            for(auto pFormatField : vpFields)
            {
                const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode();
                OSL_ENSURE(nullptr != pTNd, "Enhanced pdf export - text node is missing");
                if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField))
                    continue;
                // Link Rectangle
                const SwRect& rNoteRect = mrSh.GetCharRect();
                const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower());

                // Link PageNums
                std::vector<sal_Int32> aNotePageNums = CalcOutputPageNums(rNoteRect);
                for (sal_Int32 aNotePageNum : aNotePageNums)
                {

                    // Use the NumberFormatter to get the date string:
                    const SwPostItField* pField = static_cast<SwPostItField*>(pFormatField->GetField());
                    SvNumberFormatter* pNumFormatter = pDoc->GetNumberFormatter();
                    const Date aDateDiff(pField->GetDate() - pNumFormatter->GetNullDate());
                    const sal_uLong nFormat = pNumFormatter->GetStandardFormat(SvNumFormatType::DATE, pField->GetLanguage());
                    OUString sDate;
                    const Color* pColor;
                    pNumFormatter->GetOutputString(aDateDiff.GetDate(), nFormat, sDate, &pColor);

                    vcl::pdf::PDFNote aNote;
                    // The title should consist of the author and the date:
                    aNote.maTitle = pField->GetPar1() + ", " + sDate + ", " + (pField->GetResolved() ? SwResId(STR_RESOLVED) : u""_ustr);
                    // Guess what the contents contains...
                    aNote.maContents = pField->GetText();

                    tools::Rectangle aPopupRect(0, 0);
                    SwPostItMgr* pPostItMgr = pDoc->GetEditShell()->GetPostItMgr();
                    for (auto it = pPostItMgr->begin(); it != pPostItMgr->end(); ++it)
                    {
                        sw::annotation::SwAnnotationWin* pWin = it->get()->mpPostIt;
                        if (pWin)
                        {
                            const SwRect& aAnnotRect = pWin->GetAnchorRect();
                            if (aAnnotRect.Contains(rNoteRect))
                            {
                                Point aPt(pDoc->GetEditShell()->GetWin()->PixelToLogic(pWin->GetPosPixel()));
                                Size aSize(pDoc->GetEditShell()->GetWin()->PixelToLogic(pWin->GetSizePixel()));
                                aPopupRect = tools::Rectangle(aPt, aSize);
                            }
                        }
                    }

                    // Link Export
                    tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rNoteRect.SVRect()));
                    sal_Int32 nNoteId = pPDFExtOutDevData->CreateNote(aRect, aNote, aPopupRect, aNotePageNum);

                    if (mrPrintData.GetPrintPostIts() != SwPostItMode::InMargins)
                    {
                        const IdMapEntry aNoteEntry(aRect, nNoteId);
                        pPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap.push_back(aNoteEntry);
                    }
                }
                mrSh.SwCursorShell::ClearMark();
            }
        }

        // HYPERLINKS

        SwGetINetAttrs aArr;
        mrSh.GetINetAttrs( aArr );
        forauto &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;
            }

            SwCursorSaveState aSaveState( *mrSh.GetCursor_() );

            // Select the footnote:
            mrSh.SwCursorShell::SetMark();
            mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );

            // Link Rectangle
            SwRects aTmp;
            aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() );
            OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );

            mrSh.GetCursor_()->RestoreSavePos();
            mrSh.SwCursorShell::ClearMark();

            if (aTmp.empty())
                continue;

            const SwRect aLinkRect( aTmp[ 0 ] );

            // Goto footnote text:
            if ( mrSh.GotoFootnoteText() )
            {
                // Destination Rectangle
                const SwRect& rDestRect = mrSh.GetCharRect();
                const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
                if ( -1 != nDestPageNum )
                {
                    const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
                    // Destination PageNum
                    tools::Rectangle aRect = SwRectToPDFRect(pCurrPage, rDestRect.SVRect());
                    // Back link rectangle calculation
                    const SwPageFrame* fnBodyPage = pCurrPage->getRootFrame()->GetPageByPageNum(nDestPageNum+1);
                    SwRect fnSymbolRect;
                    if (fnBodyPage->IsVertical()){
                        tools::Long fnSymbolTop = fnBodyPage->GetTopMargin() + fnBodyPage->getFrameArea().Top();
                        tools::Long symbolHeight = rDestRect.Top() - fnSymbolTop;
                        fnSymbolRect = SwRect(rDestRect.Pos().X(),fnSymbolTop,rDestRect.Width(),symbolHeight);
                    } else {
                       if (fnBodyPage->IsRightToLeft()){
                           tools::Long fnSymbolRight = fnBodyPage->getFrameArea().Right() - fnBodyPage->GetRightMargin();
                           tools::Long symbolWidth = fnSymbolRight - rDestRect.Right();
                           fnSymbolRect = SwRect(rDestRect.Pos().X(),rDestRect.Pos().Y(),symbolWidth,rDestRect.Height());
                       } else {
                           tools::Long fnSymbolLeft = fnBodyPage->GetLeftMargin() + fnBodyPage->getFrameArea().Left();
                           tools::Long symbolWidth = rDestRect.Left() - fnSymbolLeft;
                           fnSymbolRect = SwRect(fnSymbolLeft,rDestRect.Pos().Y(),symbolWidth,rDestRect.Height());
                       }
                    }
                    tools::Rectangle aFootnoteSymbolRect = SwRectToPDFRect(pCurrPage, fnSymbolRect.SVRect());

                    OUString const numStrSymbol(pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, mrSh.GetLayout(), true));
                    OUString const numStrRef(pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, mrSh.GetLayout(), false));

                    // Export back link
                    const sal_Int32 nBackLinkId = pPDFExtOutDevData->CreateLink(aFootnoteSymbolRect, 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(), truefalsefalse );

                    // 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);
    mrOut.Pop();
}

void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks()
{
    auto pPDFExtOutDevData = dynamic_cast<vcl::PDFExtOutDevData*>(mrOut.GetExtOutDevData());
    if (!pPDFExtOutDevData)
    {
        return;
    }

    // Create PDF destinations for bibliography table entries
    std::vector<std::tuple<const SwTOXBase*, const OUString*, sal_Int32>> vDestinations;
    //  string is the row node text, sal_Int32 is number of the destination
    // Note: This way of iterating doesn't seem to take into account TOXes
    //          that are in a frame, probably in some other cases too
    {
        mrSh.GotoPage(1);
        while (mrSh.GotoNextTOXBase())
        {
            const SwTOXBase* pIteratedTOX = nullptr;
            while ((pIteratedTOX = mrSh.GetCurTOX()) != nullptr
                   && pIteratedTOX->GetType() == TOX_AUTHORITIES)
            {
                if (const SwNode& rCurrentNode = mrSh.GetCursor()->GetPoint()->GetNode();
                    rCurrentNode.GetNodeType() == SwNodeType::Text)
                {
                    if (mrSh.GetCursor()->GetPoint()->GetNode().FindSectionNode()->GetSection().GetType()
                        == SectionType::ToxContent) // this checks it's not a heading
                    {
                        // Destination Rectangle
                        const SwRect& rDestRect = mrSh.GetCharRect();

                        const SwPageFrame* pCurrPage =
                            static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );

                        // Destination PageNum
                        const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );

                        // Destination Export
                        if ( -1 != nDestPageNum )
                        {
                            tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
                            const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
                            const OUString* vNodeText = &static_cast<const SwTextNode*>(&rCurrentNode)->GetText();
                            vDestinations.emplace_back(pIteratedTOX, vNodeText, nDestId);
                        }
                    }
                }
                if (!mrSh.MovePara(GoNextPara, fnParaStart))
                { // Cursor is stuck in the TOX due to document ending immediately afterwards
                    break;
                }
            }
        }
    }

    // Generate links to matching entries in the bibliography tables
    std::vector<SwFormatField*> aFields;
    SwFieldType* pType = mrSh.GetFieldType(SwFieldIds::TableOfAuthorities, OUString());
    if (!pType)
    {
        return;
    }

    pType->GatherFields(aFields);
    const auto pPageFrame = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower());
    for (const auto pFormatField : aFields)
    {
        if (!pFormatField->GetTextField() || !pFormatField->IsFieldInDoc())
        {
            continue;
        }

        const auto& rAuthorityField
            = *static_cast<const SwAuthorityField*>(pFormatField->GetField());

        if (auto targetType = rAuthorityField.GetTargetType();
            targetType == SwAuthorityField::TargetType::UseDisplayURL
            || targetType == SwAuthorityField::TargetType::UseTargetURL)
        {
            // Since the target type specifies to use an URL, link to it
            const OUString aURL = rAuthorityField.GetAbsoluteURL();
            if (aURL.getLength() == 0)
            {
                continue;
            }

            const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode();
            if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField))
            {
                continue;
            }

            OUString const content(rAuthorityField.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: */

Messung V0.5 in Prozent
C=92 H=95 G=93

¤ Dauer der Verarbeitung: 0.70 Sekunden  (vorverarbeitet am  2026-05-08) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.