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  

Quelle  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());
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=92 H=96 G=93

¤ Dauer der Verarbeitung: 0.55 Sekunden  ¤

*© 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.