/* -*- 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)->GetTextNodeFor
ParaProps();
const SwFormat* pTextFormat = pTextNode->GetFormatColl();
ProgName sStyleName;
SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
bRet = sStyleName.toString() == aTableHeadingName;
}
// tdf#153935 wild guessing for 1st row based on table autoformat
if (!bRet && !rCellFrame.GetUpper()->GetPrev())
{
SwTable const*const pTable(rCellFrame.FindTabFrame()->GetTable());
assert(pTable);
TableStyleName const& rStyleName(pTable->GetTableStyleName());
if (!rStyleName.isEmpty())
{
if (SwTableAutoFormat const*const pTableAF =
pTable->GetFrameFormat()->GetDoc().GetTableStyles().FindAutoFormat(rStyleName))
{
bRet |= pTableAF->HasHeaderRow();
}
}
}
return bRet;
}
// returns true if the frame is a caption
bool lcl_IsCaptionFrame(const SwFrame& rFrame)
{
if (!rFrame.IsTextFrame())
return false;
SwTextFrame const& rTextFrame(*static_cast<const SwTextFrame*>(&rFrame));
const SwTextNode* const pTextNd(rTextFrame.GetTextNodeForParaProps());
if (!pTextNd)
return false;
const SwFormat* pTextFormat = pTextNd->GetFormatColl();
const SwFormat* pParentTextFormat = pTextFormat ? pTextFormat->DerivedFrom() : nullptr;
ProgName sParentStyleName;
if (pParentTextFormat)
SwStyleNameMapper::FillProgName(pParentTextFormat->GetName(), sParentStyleName,
SwGetPoolIdFromName::TxtColl);
return sParentStyleName == aCaption;
}
const SwTabFrame* lcl_FindTableForCaption(const SwFrame& rFrame)
{
const SwTabFrame* pTabFrame = nullptr;
bool bPrevFrame = false;
// It is possible to add multiple captions to a table,
// both above and below, either simultaneously or separately.
// Start by checking the next frame, and if we don't find a table frame
// or if the next frame is not a caption, we return to the current caption
// and perform the same operation backwards using the previous frames.
const SwFrame* pRetFrame = rFrame.GetNext();
if (!pRetFrame)
{
bPrevFrame = true;
pRetFrame = rFrame.GetPrev();
}
while (pRetFrame)
{
if (pRetFrame->IsTabFrame())
{
pTabFrame = static_cast<const SwTabFrame*>(pRetFrame);
break;
}
// Check if the next or the previous frame is a caption frame
bool bIsCaption = lcl_IsCaptionFrame(*pRetFrame);
if (bIsCaption && pRetFrame->GetNext())
{
pRetFrame = !bPrevFrame ? pRetFrame->GetNext() : pRetFrame->GetPrev();
}
else if (!bPrevFrame && rFrame.GetPrev())
{
// If no table was found while checking the GetNext() frames,
// jump back to the current caption and
// start checking the GetPrev() frames.
bPrevFrame = true;
pRetFrame = rFrame.GetPrev();
}
else
// This part handles the case
// when the table has been deleted,
// but the caption has not.
break;
}
return pTabFrame;
}
// List all frames for which the NonStructElement tag is set:
bool lcl_IsInNonStructEnv( const SwFrame& rFrame )
{
bool bRet = false;
if ( nullptr != rFrame.FindFooterOrHeader() &&
!rFrame.IsHeaderFrame() && !rFrame.IsFooterFrame() )
{
bRet = true;
}
else if ( rFrame.IsInTab() && !rFrame.IsTabFrame() )
{
const SwTabFrame* pTabFrame = rFrame.FindTabFrame();
if ( rFrame.GetUpper() != pTabFrame &&
pTabFrame->IsFollow() && pTabFrame->IsInHeadline( rFrame ) )
bRet = true;
}
return bRet;
}
// Generate key from frame for reopening tags:
void const* lcl_GetKeyFromFrame( const SwFrame& rFrame )
{
void const* pKey = nullptr;
if ( rFrame.IsPageFrame() )
pKey = static_cast<void const *>(&(static_cast<const SwPageFrame&>(rFrame).GetFormat()->getIDocumentSettingAccess()));
else if ( rFrame.IsTextFrame() )
pKey = static_cast<void const *>(static_cast<const SwTextFrame&>(rFrame).GetTextNodeFirst());
else if ( rFrame.IsSctFrame() )
pKey = static_cast<void const *>(static_cast<const SwSectionFrame&>(rFrame).GetSection());
else if ( rFrame.IsTabFrame() )
pKey = static_cast<void const *>(static_cast<const SwTabFrame&>(rFrame).GetTable());
else if ( rFrame.IsRowFrame() )
pKey = static_cast<void const *>(static_cast<const SwRowFrame&>(rFrame).GetTabLine());
else if ( rFrame.IsCellFrame() )
{
const SwTabFrame* pTabFrame = rFrame.FindTabFrame();
const SwTable* pTable = pTabFrame->GetTable();
pKey = static_cast<void const *>(& static_cast<const SwCellFrame&>(rFrame).GetTabBox()->FindStartOfRowSpan(*pTable));
}
else if (rFrame.IsFootnoteFrame())
{
pKey = static_cast<void const*>(static_cast<SwFootnoteFrame const&>(rFrame).GetAttr());
}
return pKey;
}
bool lcl_HasPreviousParaSameNumRule(SwTextFrame const& rTextFrame, const SwTextNode& rNode)
{
bool bRet = false;
SwNodeIndex aIdx( rNode );
const SwDoc& rDoc = rNode.GetDoc();
const SwNodes& rNodes = rDoc.GetNodes();
const SwNode* pNode = &rNode;
const SwNumRule* pNumRule = rNode.GetNumRule();
while (pNode != rNodes.DocumentSectionStartNode(const_cast<SwNode*>(static_cast<SwNode const *>(&rNode))) )
{
sw::GotoPrevLayoutTextFrame(aIdx, rTextFrame.getRootFrame());
if (aIdx.GetNode().IsTextNode())
{
const SwTextNode *const pPrevTextNd = sw::GetParaPropsNode(
*rTextFrame.getRootFrame(), *aIdx.GetNode().GetTextNode());
const SwNumRule * pPrevNumRule = pPrevTextNd->GetNumRule();
// We find the previous text node. Now check, if the previous text node
// has the same numrule like rNode:
if ( (pPrevNumRule == pNumRule) &&
(!pPrevTextNd->IsOutline() == !rNode.IsOutline()))
bRet = true;
break;
}
pNode = &aIdx.GetNode();
}
return bRet;
}
bool lcl_TryMoveToNonHiddenField(SwEditShell& rShell, const SwTextNode& rNd, const SwFormatField& rField)
{
// 1. Check if the whole paragraph is hidden
// 2. Move to the field
// 3. Check for hidden text attribute
if(rNd.IsHidden())
return false;
if(!rShell.GotoFormatField(rField) || rShell.IsInHiddenRange(/*bSelect=*/false))
{
rShell.SwCursorShell::ClearMark();
return false;
}
return true;
};
// tdf#157816: try to check if the rectangle contains actual text
::std::vector<SwRect> GetCursorRectsContainingText(SwCursorShell const& rShell)
{
::std::vector<SwRect> ret;
SwRects rects;
rShell.GetLayout()->CalcFrameRects(*rShell.GetCursor_(), rects, SwRootFrame::RectsMode::NoAnchoredFlys);
for (SwRect const& rRect : rects)
{
Point center(rRect.Center());
SwSpecialPos special;
SwCursorMoveState cms(CursorMoveState::NONE);
cms.m_pSpecialPos = &special;
cms.m_bFieldInfo = true;
SwPosition pos(rShell.GetDoc()->GetNodes());
auto const [pStart, pEnd] = rShell.GetCursor_()->StartEnd();
if (rShell.GetLayout()->GetModelPositionForViewPoint(&pos, center, &cms)
&& *pStart <= pos && pos <= *pEnd)
{
SwRect charRect;
std::pair<Point, bool> const tmp(center, false);
SwContentFrame const*const pFrame(
pos.nNode.GetNode().GetTextNode()->getLayoutFrame(rShell.GetLayout(), &pos, &tmp));
if (pFrame->GetCharRect(charRect, pos, &cms, false)
&& rRect.Overlaps(charRect))
{
ret.push_back(rRect);
}
}
// reset stupid static var that may have gotten set now
SwTextCursor::SetRightMargin(false); // WTF is this crap
}
return ret;
}
} // end namespace
SwTaggedPDFHelper::SwTaggedPDFHelper( const Num_Info* pNumInfo,
const Frame_Info* pFrameInfo,
const Por_Info* pPorInfo,
OutputDevice const & rOut )
: m_nEndStructureElement( 0 ),
m_nRestoreCurrentTag( -1 ),
mpNumInfo( pNumInfo ),
mpFrameInfo( pFrameInfo ),
mpPorInfo( pPorInfo )
{
mpPDFExtOutDevData =
dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() );
if ( !(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()) )
return;
#if OSL_DEBUG_LEVEL > 1
sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
lcl_DBGCheckStack();
#endif
if ( mpNumInfo )
BeginNumberedListStructureElements();
else if ( mpFrameInfo )
BeginBlockStructureElements();
else if ( mpPorInfo )
BeginInlineStructureElements();
else
BeginTag( vcl::pdf::StructElement::NonStructElement, OUString() );
#if OSL_DEBUG_LEVEL > 1
nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
lcl_DBGCheckStack();
(void)nCurrentStruct;
#endif
}
SwTaggedPDFHelper::~SwTaggedPDFHelper()
{
if ( !(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()) )
return;
#if OSL_DEBUG_LEVEL > 1
sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
lcl_DBGCheckStack();
#endif
EndStructureElements();
#if OSL_DEBUG_LEVEL > 1
nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
lcl_DBGCheckStack();
(void)nCurrentStruct;
#endif
}
void const* SwDrawContact::GetPDFAnchorStructureElementKey(SdrObject const& rObj)
{
SwFrame const*const pAnchorFrame(GetAnchoredObj(&rObj)->GetAnchorFrame());
return pAnchorFrame ? lcl_GetKeyFromFrame(*pAnchorFrame) : nullptr;
}
bool SwTaggedPDFHelper::CheckReopenTag()
{
bool bRet = false;
void const* pReopenKey(nullptr);
bool bContinue = false; // in some cases we just have to reopen a tag without early returning
if ( mpFrameInfo )
{
const SwFrame& rFrame = mpFrameInfo->mrFrame;
const SwFrame* pKeyFrame = nullptr;
// Reopen an existing structure element if
// - rFrame is not the first page frame (reopen Document tag)
// - rFrame is a follow frame (reopen Master tag)
// - rFrame is a fly frame anchored at content (reopen Anchor paragraph tag)
// - rFrame is a fly frame anchored at page (reopen Document tag)
// - rFrame is a follow flow row (reopen TableRow tag)
// - rFrame is a cell frame in a follow flow row (reopen TableData tag)
if ( ( rFrame.IsPageFrame() && static_cast<const SwPageFrame&>(rFrame).GetPrev() ) ||
( rFrame.IsFlowFrame() && SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() ) ||
(rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetMaster()) ||
( rFrame.IsRowFrame() && rFrame.IsInFollowFlowRow() ) ||
( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetPrevCellLeaf() ) )
{
pKeyFrame = &rFrame;
}
else if (rFrame.IsFlyFrame() && !mpFrameInfo->m_isLink)
{
const SwFormatAnchor& rAnchor =
static_cast<const SwFlyFrame*>(&rFrame)->GetFormat()->GetAnchor();
if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) ||
(RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) ||
(RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()))
{
pKeyFrame = static_cast<const SwFlyFrame&>(rFrame).GetAnchorFrame();
bContinue = true;
}
}
if ( pKeyFrame )
{
void const*const pKey = lcl_GetKeyFromFrame(*pKeyFrame);
FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
if (rFrameTagSet.contains(pKey)
|| rFrame.IsFlyFrame()) // for hell layer flys
{
pReopenKey = pKey;
}
}
}
if (pReopenKey)
{
// note: it would be possible to get rid of the SetCurrentStructureElement()
// - which is quite ugly - for most cases by recreating the parents until the
// current ancestor, but there are special cases cell frame rowspan > 1 follow
// and footnote frame follow where the parent of the follow is different from
// the parent of the first one, and so in PDFExtOutDevData the wrong parent
// would be restored and used for next elements.
m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement();
sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pReopenKey);
mpPDFExtOutDevData->SetCurrentStructureElement(id);
bRet = true;
}
return bRet && !bContinue;
}
void SwTaggedPDFHelper::CheckRestoreTag() const
{
if ( m_nRestoreCurrentTag != -1 )
{
mpPDFExtOutDevData->SetCurrentStructureElement( m_nRestoreCurrentTag );
#if OSL_DEBUG_LEVEL > 1
aStructStack.pop_back();
#endif
}
}
void SwTaggedPDFHelper::OpenTagImpl(void const*const pKey)
{
sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pKey);
mpPDFExtOutDevData->BeginStructureElement(id);
++m_nEndStructureElement;
#if OSL_DEBUG_LEVEL > 1
aStructStack.push_back( 99 );
#endif
}
sal_Int32 SwTaggedPDFHelper::BeginTagImpl(void const*const pKey,
vcl::pdf::StructElement const eType, const OUString& rString)
{
// write new tag
const sal_Int32 nId = mpPDFExtOutDevData->EnsureStructureElement(pKey);
mpPDFExtOutDevData->InitStructureElement(nId, eType, rString);
mpPDFExtOutDevData->BeginStructureElement(nId);
++m_nEndStructureElement;
#if OSL_DEBUG_LEVEL > 1
aStructStack.push_back( o3tl::narrowing<sal_uInt16>(eType) );
#endif
return nId;
}
void SwTaggedPDFHelper::BeginTag(vcl::pdf::StructElement eType, const OUString& rString)
{
void const* pKey(nullptr);
if (mpFrameInfo)
{
const SwFrame& rFrame = mpFrameInfo->mrFrame;
if ( ( rFrame.IsPageFrame() && !static_cast<const SwPageFrame&>(rFrame).GetPrev() ) ||
( rFrame.IsFlowFrame() && !SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() && SwFlowFrame::CastFlowFrame(&rFrame)->HasFollow() ) ||
rFrame.IsSctFrame() || // all of them, so that opening parent sections works
( rFrame.IsTextFrame() && rFrame.GetDrawObjs() ) ||
(rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetFollow()) ||
( rFrame.IsRowFrame() && rFrame.IsInSplitTableRow() ) ||
( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetNextCellLeaf() ) ||
rFrame.IsTabFrame() )
{
pKey = lcl_GetKeyFromFrame(rFrame);
if (pKey)
{
FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
assert(!rFrameTagSet.contains(pKey));
rFrameTagSet.emplace(pKey);
}
}
}
sal_Int32 const nId = BeginTagImpl(pKey, eType, rString);
// Store the id of the current structure element if
// - it is a list structure element
// - it is a list body element with children
// - rFrame is the first page frame
// - rFrame is a master frame
// - rFrame has objects anchored to it
// - rFrame is a row frame or cell frame in a split table row
if ( mpNumInfo )
{
const SwTextFrame& rTextFrame = mpNumInfo->mrFrame;
SwTextNode const*const pTextNd = rTextFrame.GetTextNodeForParaProps();
const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame());
if (vcl::pdf::StructElement::List == eType)
{
NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
rNumListIdMap[ pNodeNum ] = nId;
}
else if (vcl::pdf::StructElement::LIBody == eType)
{
NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
rNumListBodyIdMap[ pNodeNum ] = nId;
}
}
SetAttributes( eType );
}
void SwTaggedPDFHelper::EndTag()
{
mpPDFExtOutDevData->EndStructureElement();
#if OSL_DEBUG_LEVEL > 1
aStructStack.pop_back();
#endif
}
namespace {
// link the link annotation to the link structured element
void LinkLinkLink(vcl::PDFExtOutDevData & rPDFExtOutDevData, SwRect const& rRect)
{
const LinkIdMap& rLinkIdMap(rPDFExtOutDevData.GetSwPDFState()->m_LinkIdMap);
const Point aCenter = rRect.Center();
auto aIter = std::find_if(rLinkIdMap.begin(), rLinkIdMap.end(),
[&aCenter](const IdMapEntry& rEntry) { return rEntry.first.Contains(aCenter); });
if (aIter != rLinkIdMap.end())
{
sal_Int32 nLinkId = (*aIter).second;
rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::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
--> --------------------