office-gobmx/sw/source/core/text/EnhancedPDFExportHelper.cxx
Tibor Nagy 1e905680c8 tdf#162359 sw: fix the export of comments as PDF annotations
Change-Id: If4a8a1a73c382f496b2c6dd4d52271dc6bc87dda
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/172877
Tested-by: Jenkins
Reviewed-by: Nagy Tibor <tibor.nagy.extern@allotropia.de>
2024-09-05 22:58:30 +02:00

3146 lines
122 KiB
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 <tools/globname.hxx>
#include <svx/svdobj.hxx>
#include <stack>
#include <map>
#include <set>
#include <optional>
using namespace ::com::sun::star;
#if OSL_DEBUG_LEVEL > 1
static std::vector< sal_uInt16 > aStructStack;
void lcl_DBGCheckStack()
{
/* NonStructElement = 0 Document = 1 Part = 2
* Article = 3 Section = 4 Division = 5
* BlockQuote = 6 Caption = 7 TOC = 8
* TOCI = 9 Index = 10 Paragraph = 11
* Heading = 12 H1-6 = 13 - 18 List = 19
* ListItem = 20 LILabel = 21 LIBody = 22
* Table = 23 TableRow = 24 TableHeader = 25
* TableData = 26 Span = 27 Quote = 28
* Note = 29 Reference = 30 BibEntry = 31
* Code = 32 Link = 33 Figure = 34
* Formula = 35 Form = 36 Continued frame = 99
*/
sal_uInt16 nElement;
for ( const auto& rItem : aStructStack )
{
nElement = rItem;
}
(void)nElement;
};
#endif
typedef std::set< tools::Long, lt_TableColumn > TableColumnsMapEntry;
typedef std::pair< SwRect, sal_Int32 > IdMapEntry;
typedef std::vector< IdMapEntry > LinkIdMap;
typedef std::vector< IdMapEntry > NoteIdMap;
typedef std::map< const SwTable*, TableColumnsMapEntry > TableColumnsMap;
typedef std::map< const SwNumberTreeNode*, sal_Int32 > NumListIdMap;
typedef std::map< const SwNumberTreeNode*, sal_Int32 > NumListBodyIdMap;
typedef std::set<const void*> FrameTagSet;
struct SwEnhancedPDFState
{
TableColumnsMap m_TableColumnsMap;
LinkIdMap m_LinkIdMap;
NoteIdMap m_NoteIdMap;
NumListIdMap m_NumListIdMap;
NumListBodyIdMap m_NumListBodyIdMap;
FrameTagSet m_FrameTagSet;
LanguageType m_eLanguageDefault;
struct Span
{
FontLineStyle eUnderline;
FontLineStyle eOverline;
FontStrikeout eStrikeout;
FontEmphasisMark eFontEmphasis;
short nEscapement;
SwFontScript nScript;
LanguageType nLang;
OUString StyleName;
};
::std::optional<Span> m_oCurrentSpan;
::std::optional<SwTextAttr const*> m_oCurrentLink;
SwEnhancedPDFState(LanguageType const eLanguageDefault)
: m_eLanguageDefault(eLanguageDefault)
{
}
};
namespace
{
// ODF Style Names:
constexpr OUString aTableHeadingName = u"Table Heading"_ustr;
constexpr OUString aQuotations = u"Quotations"_ustr;
constexpr OUString aCaption = u"Caption"_ustr;
constexpr OUString aHeading = u"Heading"_ustr;
constexpr OUString aQuotation = u"Quotation"_ustr;
constexpr OUString aSourceText = u"Source Text"_ustr;
// PDF Tag Names:
constexpr OUStringLiteral aDocumentString = u"Document";
constexpr OUString aDivString = u"Div"_ustr;
constexpr OUStringLiteral aSectString = u"Sect";
constexpr OUStringLiteral aHString = u"H";
constexpr OUStringLiteral aH1String = u"H1";
constexpr OUStringLiteral aH2String = u"H2";
constexpr OUStringLiteral aH3String = u"H3";
constexpr OUStringLiteral aH4String = u"H4";
constexpr OUStringLiteral aH5String = u"H5";
constexpr OUStringLiteral aH6String = u"H6";
constexpr OUStringLiteral aH7String = u"H7";
constexpr OUStringLiteral aH8String = u"H8";
constexpr OUStringLiteral aH9String = u"H9";
constexpr OUStringLiteral aH10String = u"H10";
constexpr OUStringLiteral aListString = u"L";
constexpr OUStringLiteral aListItemString = u"LI";
constexpr OUStringLiteral aListLabelString = u"Lbl";
constexpr OUString aListBodyString = u"LBody"_ustr;
constexpr OUStringLiteral aBlockQuoteString = u"BlockQuote";
constexpr OUString aCaptionString = u"Caption"_ustr;
constexpr OUStringLiteral aIndexString = u"Index";
constexpr OUStringLiteral aTOCString = u"TOC";
constexpr OUStringLiteral aTOCIString = u"TOCI";
constexpr OUStringLiteral aTableString = u"Table";
constexpr OUStringLiteral aTRString = u"TR";
constexpr OUStringLiteral aTDString = u"TD";
constexpr OUStringLiteral aTHString = u"TH";
constexpr OUStringLiteral aBibEntryString = u"BibEntry";
constexpr OUStringLiteral aQuoteString = u"Quote";
constexpr OUString aSpanString = u"Span"_ustr;
constexpr OUStringLiteral aCodeString = u"Code";
constexpr OUStringLiteral aFigureString = u"Figure";
constexpr OUStringLiteral aFormulaString = u"Formula";
constexpr OUString aLinkString = u"Link"_ustr;
constexpr OUStringLiteral aNoteString = u"Note";
constexpr OUStringLiteral aAnnotString = u"Annot";
// returns true if first paragraph in cell frame has 'table heading' style
bool lcl_IsHeadlineCell( const SwCellFrame& rCellFrame )
{
bool bRet = false;
const SwContentFrame *pCnt = rCellFrame.ContainsContent();
if ( pCnt && pCnt->IsTextFrame() )
{
SwTextNode const*const pTextNode = static_cast<const SwTextFrame*>(pCnt)->GetTextNodeForParaProps();
const SwFormat* pTextFormat = pTextNode->GetFormatColl();
OUString sStyleName;
SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
bRet = sStyleName == aTableHeadingName;
}
// tdf#153935 wild guessing for 1st row based on table autoformat
if (!bRet && !rCellFrame.GetUpper()->GetPrev())
{
SwTable const*const pTable(rCellFrame.FindTabFrame()->GetTable());
assert(pTable);
OUString const& rStyleName(pTable->GetTableStyleName());
if (!rStyleName.isEmpty())
{
if (SwTableAutoFormat const*const pTableAF =
pTable->GetFrameFormat()->GetDoc()->GetTableStyles().FindAutoFormat(rStyleName))
{
bRet |= pTableAF->HasHeaderRow();
}
}
}
return bRet;
}
// List all frames for which the NonStructElement tag is set:
bool lcl_IsInNonStructEnv( const SwFrame& rFrame )
{
bool bRet = false;
if ( nullptr != rFrame.FindFooterOrHeader() &&
!rFrame.IsHeaderFrame() && !rFrame.IsFooterFrame() )
{
bRet = true;
}
else if ( rFrame.IsInTab() && !rFrame.IsTabFrame() )
{
const SwTabFrame* pTabFrame = rFrame.FindTabFrame();
if ( rFrame.GetUpper() != pTabFrame &&
pTabFrame->IsFollow() && pTabFrame->IsInHeadline( rFrame ) )
bRet = true;
}
return bRet;
}
// Generate key from frame for reopening tags:
void const* lcl_GetKeyFromFrame( const SwFrame& rFrame )
{
void const* pKey = nullptr;
if ( rFrame.IsPageFrame() )
pKey = static_cast<void const *>(&(static_cast<const SwPageFrame&>(rFrame).GetFormat()->getIDocumentSettingAccess()));
else if ( rFrame.IsTextFrame() )
pKey = static_cast<void const *>(static_cast<const SwTextFrame&>(rFrame).GetTextNodeFirst());
else if ( rFrame.IsSctFrame() )
pKey = static_cast<void const *>(static_cast<const SwSectionFrame&>(rFrame).GetSection());
else if ( rFrame.IsTabFrame() )
pKey = static_cast<void const *>(static_cast<const SwTabFrame&>(rFrame).GetTable());
else if ( rFrame.IsRowFrame() )
pKey = static_cast<void const *>(static_cast<const SwRowFrame&>(rFrame).GetTabLine());
else if ( rFrame.IsCellFrame() )
{
const SwTabFrame* pTabFrame = rFrame.FindTabFrame();
const SwTable* pTable = pTabFrame->GetTable();
pKey = static_cast<void const *>(& static_cast<const SwCellFrame&>(rFrame).GetTabBox()->FindStartOfRowSpan(*pTable));
}
else if (rFrame.IsFootnoteFrame())
{
pKey = static_cast<void const*>(static_cast<SwFootnoteFrame const&>(rFrame).GetAttr());
}
return pKey;
}
bool lcl_HasPreviousParaSameNumRule(SwTextFrame const& rTextFrame, const SwTextNode& rNode)
{
bool bRet = false;
SwNodeIndex aIdx( rNode );
const SwDoc& rDoc = rNode.GetDoc();
const SwNodes& rNodes = rDoc.GetNodes();
const SwNode* pNode = &rNode;
const SwNumRule* pNumRule = rNode.GetNumRule();
while (pNode != rNodes.DocumentSectionStartNode(const_cast<SwNode*>(static_cast<SwNode const *>(&rNode))) )
{
sw::GotoPrevLayoutTextFrame(aIdx, rTextFrame.getRootFrame());
if (aIdx.GetNode().IsTextNode())
{
const SwTextNode *const pPrevTextNd = sw::GetParaPropsNode(
*rTextFrame.getRootFrame(), *aIdx.GetNode().GetTextNode());
const SwNumRule * pPrevNumRule = pPrevTextNd->GetNumRule();
// We find the previous text node. Now check, if the previous text node
// has the same numrule like rNode:
if ( (pPrevNumRule == pNumRule) &&
(!pPrevTextNd->IsOutline() == !rNode.IsOutline()))
bRet = true;
break;
}
pNode = &aIdx.GetNode();
}
return bRet;
}
bool lcl_TryMoveToNonHiddenField(SwEditShell& rShell, const SwTextNode& rNd, const SwFormatField& rField)
{
// 1. Check if the whole paragraph is hidden
// 2. Move to the field
// 3. Check for hidden text attribute
if(rNd.IsHidden())
return false;
if(!rShell.GotoFormatField(rField) || rShell.IsInHiddenRange(/*bSelect=*/false))
{
rShell.SwCursorShell::ClearMark();
return false;
}
return true;
};
// tdf#157816: try to check if the rectangle contains actual text
::std::vector<SwRect> GetCursorRectsContainingText(SwCursorShell const& rShell)
{
::std::vector<SwRect> ret;
SwRects rects;
rShell.GetLayout()->CalcFrameRects(*rShell.GetCursor_(), rects, SwRootFrame::RectsMode::NoAnchoredFlys);
for (SwRect const& rRect : rects)
{
Point center(rRect.Center());
SwSpecialPos special;
SwCursorMoveState cms(CursorMoveState::NONE);
cms.m_pSpecialPos = &special;
cms.m_bFieldInfo = true;
SwPosition pos(rShell.GetDoc()->GetNodes());
auto const [pStart, pEnd] = rShell.GetCursor_()->StartEnd();
if (rShell.GetLayout()->GetModelPositionForViewPoint(&pos, center, &cms)
&& *pStart <= pos && pos <= *pEnd)
{
SwRect charRect;
std::pair<Point, bool> const tmp(center, false);
SwContentFrame const*const pFrame(
pos.nNode.GetNode().GetTextNode()->getLayoutFrame(rShell.GetLayout(), &pos, &tmp));
if (pFrame->GetCharRect(charRect, pos, &cms, false)
&& rRect.Overlaps(charRect))
{
ret.push_back(rRect);
}
}
// reset stupid static var that may have gotten set now
SwTextCursor::SetRightMargin(false); // WTF is this crap
}
return ret;
}
} // end namespace
SwTaggedPDFHelper::SwTaggedPDFHelper( const Num_Info* pNumInfo,
const Frame_Info* pFrameInfo,
const Por_Info* pPorInfo,
OutputDevice const & rOut )
: m_nEndStructureElement( 0 ),
m_nRestoreCurrentTag( -1 ),
mpNumInfo( pNumInfo ),
mpFrameInfo( pFrameInfo ),
mpPorInfo( pPorInfo )
{
mpPDFExtOutDevData =
dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() );
if ( !(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()) )
return;
#if OSL_DEBUG_LEVEL > 1
sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
lcl_DBGCheckStack();
#endif
if ( mpNumInfo )
BeginNumberedListStructureElements();
else if ( mpFrameInfo )
BeginBlockStructureElements();
else if ( mpPorInfo )
BeginInlineStructureElements();
else
BeginTag( vcl::PDFWriter::NonStructElement, OUString() );
#if OSL_DEBUG_LEVEL > 1
nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
lcl_DBGCheckStack();
(void)nCurrentStruct;
#endif
}
SwTaggedPDFHelper::~SwTaggedPDFHelper()
{
if ( !(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()) )
return;
#if OSL_DEBUG_LEVEL > 1
sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
lcl_DBGCheckStack();
#endif
EndStructureElements();
#if OSL_DEBUG_LEVEL > 1
nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
lcl_DBGCheckStack();
(void)nCurrentStruct;
#endif
}
void const* SwDrawContact::GetPDFAnchorStructureElementKey(SdrObject const& rObj)
{
SwFrame const*const pAnchorFrame(GetAnchoredObj(&rObj)->GetAnchorFrame());
return pAnchorFrame ? lcl_GetKeyFromFrame(*pAnchorFrame) : nullptr;
}
bool SwTaggedPDFHelper::CheckReopenTag()
{
bool bRet = false;
void const* pReopenKey(nullptr);
bool bContinue = false; // in some cases we just have to reopen a tag without early returning
if ( mpFrameInfo )
{
const SwFrame& rFrame = mpFrameInfo->mrFrame;
const SwFrame* pKeyFrame = nullptr;
// Reopen an existing structure element if
// - rFrame is not the first page frame (reopen Document tag)
// - rFrame is a follow frame (reopen Master tag)
// - rFrame is a fly frame anchored at content (reopen Anchor paragraph tag)
// - rFrame is a fly frame anchored at page (reopen Document tag)
// - rFrame is a follow flow row (reopen TableRow tag)
// - rFrame is a cell frame in a follow flow row (reopen TableData tag)
if ( ( rFrame.IsPageFrame() && static_cast<const SwPageFrame&>(rFrame).GetPrev() ) ||
( rFrame.IsFlowFrame() && SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() ) ||
(rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetMaster()) ||
( rFrame.IsRowFrame() && rFrame.IsInFollowFlowRow() ) ||
( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetPrevCellLeaf() ) )
{
pKeyFrame = &rFrame;
}
else if (rFrame.IsFlyFrame() && !mpFrameInfo->m_isLink)
{
const SwFormatAnchor& rAnchor =
static_cast<const SwFlyFrame*>(&rFrame)->GetFormat()->GetAnchor();
if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) ||
(RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) ||
(RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()))
{
pKeyFrame = static_cast<const SwFlyFrame&>(rFrame).GetAnchorFrame();
bContinue = true;
}
}
if ( pKeyFrame )
{
void const*const pKey = lcl_GetKeyFromFrame(*pKeyFrame);
FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
if (rFrameTagSet.find(pKey) != rFrameTagSet.end()
|| rFrame.IsFlyFrame()) // for hell layer flys
{
pReopenKey = pKey;
}
}
}
if (pReopenKey)
{
// note: it would be possible to get rid of the SetCurrentStructureElement()
// - which is quite ugly - for most cases by recreating the parents until the
// current ancestor, but there are special cases cell frame rowspan > 1 follow
// and footnote frame follow where the parent of the follow is different from
// the parent of the first one, and so in PDFExtOutDevData the wrong parent
// would be restored and used for next elements.
m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement();
sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pReopenKey);
mpPDFExtOutDevData->SetCurrentStructureElement(id);
bRet = true;
}
return bRet && !bContinue;
}
void SwTaggedPDFHelper::CheckRestoreTag() const
{
if ( m_nRestoreCurrentTag != -1 )
{
mpPDFExtOutDevData->SetCurrentStructureElement( m_nRestoreCurrentTag );
#if OSL_DEBUG_LEVEL > 1
aStructStack.pop_back();
#endif
}
}
void SwTaggedPDFHelper::OpenTagImpl(void const*const pKey)
{
sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pKey);
mpPDFExtOutDevData->BeginStructureElement(id);
++m_nEndStructureElement;
#if OSL_DEBUG_LEVEL > 1
aStructStack.push_back( 99 );
#endif
}
sal_Int32 SwTaggedPDFHelper::BeginTagImpl(void const*const pKey,
vcl::PDFWriter::StructElement const eType, const OUString& rString)
{
// write new tag
const sal_Int32 nId = mpPDFExtOutDevData->EnsureStructureElement(pKey);
mpPDFExtOutDevData->InitStructureElement(nId, eType, rString);
mpPDFExtOutDevData->BeginStructureElement(nId);
++m_nEndStructureElement;
#if OSL_DEBUG_LEVEL > 1
aStructStack.push_back( o3tl::narrowing<sal_uInt16>(eType) );
#endif
return nId;
}
void SwTaggedPDFHelper::BeginTag( vcl::PDFWriter::StructElement eType, const OUString& rString )
{
void const* pKey(nullptr);
if (mpFrameInfo)
{
const SwFrame& rFrame = mpFrameInfo->mrFrame;
if ( ( rFrame.IsPageFrame() && !static_cast<const SwPageFrame&>(rFrame).GetPrev() ) ||
( rFrame.IsFlowFrame() && !SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() && SwFlowFrame::CastFlowFrame(&rFrame)->HasFollow() ) ||
rFrame.IsSctFrame() || // all of them, so that opening parent sections works
( rFrame.IsTextFrame() && rFrame.GetDrawObjs() ) ||
(rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetFollow()) ||
( rFrame.IsRowFrame() && rFrame.IsInSplitTableRow() ) ||
( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetNextCellLeaf() ) )
{
pKey = lcl_GetKeyFromFrame(rFrame);
if (pKey)
{
FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
assert(rFrameTagSet.find(pKey) == rFrameTagSet.end());
rFrameTagSet.emplace(pKey);
}
}
}
sal_Int32 const nId = BeginTagImpl(pKey, eType, rString);
// Store the id of the current structure element if
// - it is a list structure element
// - it is a list body element with children
// - rFrame is the first page frame
// - rFrame is a master frame
// - rFrame has objects anchored to it
// - rFrame is a row frame or cell frame in a split table row
if ( mpNumInfo )
{
const SwTextFrame& rTextFrame = mpNumInfo->mrFrame;
SwTextNode const*const pTextNd = rTextFrame.GetTextNodeForParaProps();
const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame());
if ( vcl::PDFWriter::List == eType )
{
NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
rNumListIdMap[ pNodeNum ] = nId;
}
else if ( vcl::PDFWriter::LIBody == eType )
{
NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
rNumListBodyIdMap[ pNodeNum ] = nId;
}
}
SetAttributes( eType );
}
void SwTaggedPDFHelper::EndTag()
{
mpPDFExtOutDevData->EndStructureElement();
#if OSL_DEBUG_LEVEL > 1
aStructStack.pop_back();
#endif
}
namespace {
// link the link annotation to the link structured element
void LinkLinkLink(vcl::PDFExtOutDevData & rPDFExtOutDevData, SwRect const& rRect)
{
const LinkIdMap& rLinkIdMap(rPDFExtOutDevData.GetSwPDFState()->m_LinkIdMap);
const Point aCenter = rRect.Center();
auto aIter = std::find_if(rLinkIdMap.begin(), rLinkIdMap.end(),
[&aCenter](const IdMapEntry& rEntry) { return rEntry.first.Contains(aCenter); });
if (aIter != rLinkIdMap.end())
{
sal_Int32 nLinkId = (*aIter).second;
rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::PDFWriter::LinkAnnotation, nLinkId);
}
}
}
// Sets the attributes according to the structure type.
void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType )
{
sal_Int32 nVal;
/*
* ATTRIBUTES FOR BLSE
*/
if ( mpFrameInfo )
{
vcl::PDFWriter::StructAttributeValue eVal;
const SwFrame* pFrame = &mpFrameInfo->mrFrame;
SwRectFnSet aRectFnSet(pFrame);
bool bPlacement = false;
bool bWritingMode = false;
bool bSpaceBefore = false;
bool bSpaceAfter = false;
bool bStartIndent = false;
bool bEndIndent = false;
bool bTextIndent = false;
bool bTextAlign = false;
bool bWidth = false;
bool bHeight = false;
bool bBox = false;
bool bRowSpan = false;
bool bAltText = false;
// Check which attributes to set:
switch ( eType )
{
case vcl::PDFWriter::Document :
bWritingMode = true;
break;
case vcl::PDFWriter::Note:
bPlacement = true;
break;
case vcl::PDFWriter::Table :
bPlacement =
bWritingMode =
bSpaceBefore =
bSpaceAfter =
bStartIndent =
bEndIndent =
bWidth =
bHeight =
bBox = true;
break;
case vcl::PDFWriter::TableRow :
bPlacement =
bWritingMode = true;
break;
case vcl::PDFWriter::TableHeader :
mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope, vcl::PDFWriter::Column);
[[fallthrough]];
case vcl::PDFWriter::TableData :
bPlacement =
bWritingMode =
bWidth =
bHeight =
bRowSpan = true;
break;
case vcl::PDFWriter::Caption:
if (pFrame->IsSctFrame())
{
break;
}
[[fallthrough]];
case vcl::PDFWriter::H1 :
case vcl::PDFWriter::H2 :
case vcl::PDFWriter::H3 :
case vcl::PDFWriter::H4 :
case vcl::PDFWriter::H5 :
case vcl::PDFWriter::H6 :
case vcl::PDFWriter::Paragraph :
case vcl::PDFWriter::Heading :
case vcl::PDFWriter::BlockQuote :
bPlacement =
bWritingMode =
bSpaceBefore =
bSpaceAfter =
bStartIndent =
bEndIndent =
bTextIndent =
bTextAlign = true;
break;
case vcl::PDFWriter::Formula :
case vcl::PDFWriter::Figure :
bAltText =
bPlacement =
bWidth =
bHeight =
bBox = true;
break;
case vcl::PDFWriter::Division:
if (pFrame->IsFlyFrame()) // this can be something else too
{
bAltText = true;
bBox = true;
}
break;
case vcl::PDFWriter::NonStructElement:
if (pFrame->IsHeaderFrame() || pFrame->IsFooterFrame())
{
// ISO 14289-1:2014, Clause: 7.8
mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Type, vcl::PDFWriter::Pagination);
mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Subtype,
pFrame->IsHeaderFrame()
? vcl::PDFWriter::Header
: vcl::PDFWriter::Footer);
}
break;
default :
break;
}
// Set the attributes:
if ( bPlacement )
{
bool bIsFigureInline = false;
if (vcl::PDFWriter::Figure == eType)
{
const SwFrame* pKeyFrame = static_cast<const SwFlyFrame&>(*pFrame).GetAnchorFrame();
if (const SwLayoutFrame* pUpperFrame = pKeyFrame->GetUpper())
if (pUpperFrame->GetType() == SwFrameType::Body)
bIsFigureInline = true;
}
eVal = vcl::PDFWriter::TableHeader == eType || vcl::PDFWriter::TableData == eType
|| bIsFigureInline
? vcl::PDFWriter::Inline
: vcl::PDFWriter::Block;
mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::Placement, eVal );
}
if ( bWritingMode )
{
eVal = pFrame->IsVertical() ?
vcl::PDFWriter::TbRl :
pFrame->IsRightToLeft() ?
vcl::PDFWriter::RlTb :
vcl::PDFWriter::LrTb;
if ( vcl::PDFWriter::LrTb != eVal )
mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::WritingMode, eVal );
}
if ( bSpaceBefore )
{
nVal = aRectFnSet.GetTopMargin(*pFrame);
if ( 0 != nVal )
mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::SpaceBefore, nVal );
}
if ( bSpaceAfter )
{
nVal = aRectFnSet.GetBottomMargin(*pFrame);
if ( 0 != nVal )
mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::SpaceAfter, nVal );
}
if ( bStartIndent )
{
nVal = aRectFnSet.GetLeftMargin(*pFrame);
if ( 0 != nVal )
mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::StartIndent, nVal );
}
if ( bEndIndent )
{
nVal = aRectFnSet.GetRightMargin(*pFrame);
if ( 0 != nVal )
mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::EndIndent, nVal );
}
if ( bTextIndent )
{
OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" );
const SvxFirstLineIndentItem& rFirstLine(
static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent());
nVal = rFirstLine.GetTextFirstLineOffset();
if ( 0 != nVal )
mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::TextIndent, nVal );
}
if ( bTextAlign )
{
OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" );
const SwAttrSet& aSet = static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet();
const SvxAdjust nAdjust = aSet.GetAdjust().GetAdjust();
if ( SvxAdjust::Block == nAdjust || SvxAdjust::Center == nAdjust ||
( (pFrame->IsRightToLeft() && SvxAdjust::Left == nAdjust) ||
(!pFrame->IsRightToLeft() && SvxAdjust::Right == nAdjust) ) )
{
eVal = SvxAdjust::Block == nAdjust ?
vcl::PDFWriter::Justify :
SvxAdjust::Center == nAdjust ?
vcl::PDFWriter::Center :
vcl::PDFWriter::End;
mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextAlign, eVal );
}
}
// ISO 14289-1:2014, Clause: 7.3
// ISO 14289-1:2014, Clause: 7.7
// For images (but not embedded objects), an ObjectInfoPrimitive2D is
// created, but it's not evaluated by VclMetafileProcessor2D any more;
// that would require producing StructureTagPrimitive2D here but that
// looks impossible so instead duplicate the code that sets the Alt
// text here again.
if (bAltText)
{
SwFlyFrameFormat const& rFly(*static_cast<SwFlyFrame const*>(pFrame)->GetFormat());
OUString const sep(
(rFly.GetObjTitle().isEmpty() || rFly.GetObjDescription().isEmpty())
? OUString() : u" - "_ustr);
OUString const altText(rFly.GetObjTitle() + sep + rFly.GetObjDescription());
if (!altText.isEmpty())
{
mpPDFExtOutDevData->SetAlternateText(altText);
}
}
if ( bWidth )
{
nVal = aRectFnSet.GetWidth(pFrame->getFrameArea());
mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Width, nVal );
}
if ( bHeight )
{
nVal = aRectFnSet.GetHeight(pFrame->getFrameArea());
mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Height, nVal );
}
if ( bBox )
{
// BBox only for non-split tables:
if ( vcl::PDFWriter::Table != eType ||
( pFrame->IsTabFrame() &&
!static_cast<const SwTabFrame*>(pFrame)->IsFollow() &&
!static_cast<const SwTabFrame*>(pFrame)->HasFollow() ) )
{
mpPDFExtOutDevData->SetStructureBoundingBox(pFrame->getFrameArea().SVRect());
}
}
if ( bRowSpan )
{
if ( pFrame->IsCellFrame() )
{
const SwCellFrame* pThisCell = static_cast<const SwCellFrame*>(pFrame);
nVal = pThisCell->GetTabBox()->getRowSpan();
if ( nVal > 1 )
mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::RowSpan, nVal );
// calculate colspan:
const SwTabFrame* pTabFrame = pThisCell->FindTabFrame();
const SwTable* pTable = pTabFrame->GetTable();
SwRectFnSet fnRectX(pTabFrame);
const TableColumnsMapEntry& rCols(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap[pTable]);
const tools::Long nLeft = fnRectX.GetLeft(pThisCell->getFrameArea());
const tools::Long nRight = fnRectX.GetRight(pThisCell->getFrameArea());
const TableColumnsMapEntry::const_iterator aLeftIter = rCols.find( nLeft );
const TableColumnsMapEntry::const_iterator aRightIter = rCols.find( nRight );
OSL_ENSURE( aLeftIter != rCols.end() && aRightIter != rCols.end(), "Colspan trouble" );
if ( aLeftIter != rCols.end() && aRightIter != rCols.end() )
{
nVal = std::distance( aLeftIter, aRightIter );
if ( nVal > 1 )
mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::ColSpan, nVal );
}
}
}
if (mpFrameInfo->m_isLink)
{
SwRect const aRect(mpFrameInfo->mrFrame.getFrameArea());
LinkLinkLink(*mpPDFExtOutDevData, aRect);
}
}
/*
* ATTRIBUTES FOR ILSE
*/
else if ( mpPorInfo )
{
const SwLinePortion* pPor = &mpPorInfo->mrPor;
const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo();
bool bActualText = false;
bool bBaselineShift = false;
bool bTextDecorationType = false;
bool bLinkAttribute = false;
bool bAnnotAttribute = false;
bool bLanguage = false;
// Check which attributes to set:
switch ( eType )
{
case vcl::PDFWriter::Span :
case vcl::PDFWriter::Quote :
case vcl::PDFWriter::Code :
if( PortionType::HyphenStr == pPor->GetWhichPor() || PortionType::SoftHyphenStr == pPor->GetWhichPor() ||
PortionType::Hyphen == pPor->GetWhichPor() || PortionType::SoftHyphen == pPor->GetWhichPor() )
bActualText = true;
else
{
bBaselineShift =
bTextDecorationType =
bLanguage = true;
}
break;
case vcl::PDFWriter::Link :
bTextDecorationType =
bBaselineShift =
bLinkAttribute =
bLanguage = true;
break;
case vcl::PDFWriter::BibEntry :
bTextDecorationType =
bBaselineShift =
bLinkAttribute =
bLanguage = true;
break;
case vcl::PDFWriter::RT:
{
SwRubyPortion const*const pRuby(static_cast<SwRubyPortion const*>(pPor));
vcl::PDFWriter::StructAttributeValue nAlign = {};
switch (pRuby->GetAdjustment())
{
case text::RubyAdjust_LEFT:
nAlign = vcl::PDFWriter::RStart;
break;
case text::RubyAdjust_CENTER:
nAlign = vcl::PDFWriter::RCenter;
break;
case text::RubyAdjust_RIGHT:
nAlign = vcl::PDFWriter::REnd;
break;
case text::RubyAdjust_BLOCK:
nAlign = vcl::PDFWriter::RJustify;
break;
case text::RubyAdjust_INDENT_BLOCK:
nAlign = vcl::PDFWriter::RDistribute;
break;
default:
assert(false);
break;
}
::std::optional<vcl::PDFWriter::StructAttributeValue> oPos;
switch (pRuby->GetRubyPosition())
{
case RubyPosition::ABOVE:
oPos = vcl::PDFWriter::RBefore;
break;
case RubyPosition::BELOW:
oPos = vcl::PDFWriter::RAfter;
break;
case RubyPosition::RIGHT:
break; // no such thing???
}
mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::RubyAlign, nAlign);
if (oPos)
{
mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::RubyPosition, *oPos);
}
}
break;
case vcl::PDFWriter::Annot:
bAnnotAttribute =
bLanguage = true;
break;
default:
break;
}
if ( bActualText )
{
OUString aActualText;
if (pPor->GetWhichPor() == PortionType::SoftHyphen || pPor->GetWhichPor() == PortionType::Hyphen)
aActualText = OUString(u'\x00ad'); // soft hyphen
else
aActualText = rInf.GetText().copy(sal_Int32(rInf.GetIdx()), sal_Int32(pPor->GetLen()));
mpPDFExtOutDevData->SetActualText( aActualText );
}
if ( bBaselineShift )
{
// TODO: Calculate correct values!
nVal = rInf.GetFont()->GetEscapement();
if ( nVal > 0 ) nVal = 33;
else if ( nVal < 0 ) nVal = -33;
if ( 0 != nVal )
{
nVal = nVal * pPor->Height() / 100;
mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::BaselineShift, nVal );
}
}
if ( bTextDecorationType )
{
if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() )
mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Underline );
if ( LINESTYLE_NONE != rInf.GetFont()->GetOverline() )
mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Overline );
if ( STRIKEOUT_NONE != rInf.GetFont()->GetStrikeout() )
mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::LineThrough );
if ( FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() )
mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Overline );
}
if ( bLanguage )
{
const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage();
const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault);
if ( nDefaultLang != nCurrentLanguage )
mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Language, static_cast<sal_uInt16>(nCurrentLanguage) );
}
if ( bLinkAttribute )
{
SwRect aPorRect;
rInf.CalcRect( *pPor, &aPorRect );
LinkLinkLink(*mpPDFExtOutDevData, aPorRect);
}
if (bAnnotAttribute)
{
SwRect aPorRect;
rInf.CalcRect(*pPor, &aPorRect);
const NoteIdMap& rNoteIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap);
const Point aCenter = aPorRect.Center();
auto aIter = std::find_if(rNoteIdMap.begin(), rNoteIdMap.end(),
[&aCenter](const IdMapEntry& rEntry)
{ return rEntry.first.Contains(aCenter); });
if (aIter != rNoteIdMap.end())
{
sal_Int32 nNoteId = (*aIter).second;
mpPDFExtOutDevData->SetStructureAttributeNumerical(vcl::PDFWriter::NoteAnnotation,
nNoteId);
}
}
}
else if (mpNumInfo && eType == vcl::PDFWriter::List)
{
SwTextFrame const& rFrame(mpNumInfo->mrFrame);
SwTextNode const& rNode(*rFrame.GetTextNodeForParaProps());
SwNumRule const*const pNumRule = rNode.GetNumRule();
assert(pNumRule); // was required for List
auto ToPDFListNumbering = [](SvxNumberFormat const& rFormat) {
switch (rFormat.GetNumberingType())
{
case css::style::NumberingType::CHARS_UPPER_LETTER:
return vcl::PDFWriter::UpperAlpha;
case css::style::NumberingType::CHARS_LOWER_LETTER:
return vcl::PDFWriter::LowerAlpha;
case css::style::NumberingType::ROMAN_UPPER:
return vcl::PDFWriter::UpperRoman;
case css::style::NumberingType::ROMAN_LOWER:
return vcl::PDFWriter::LowerRoman;
case css::style::NumberingType::ARABIC:
return vcl::PDFWriter::Decimal;
case css::style::NumberingType::CHAR_SPECIAL:
switch (rFormat.GetBulletChar())
{
case u'\u2022': case u'\uE12C': case u'\uE01E': case u'\uE437':
return vcl::PDFWriter::Disc;
case u'\u2218': case u'\u25CB': case u'\u25E6':
return vcl::PDFWriter::Circle;
case u'\u25A0': case u'\u25AA': case u'\uE00A':
return vcl::PDFWriter::Square;
default:
return vcl::PDFWriter::NONE;
}
default: // the other 50 types
return vcl::PDFWriter::NONE;
}
};
// Note: for every level, BeginNumberedListStructureElements() produces
// a separate List element, so even though in PDF this is limited to
// the whole List we can just export the current level here.
vcl::PDFWriter::StructAttributeValue const value(
ToPDFListNumbering(pNumRule->Get(rNode.GetActualListLevel())));
// ISO 14289-1:2014, Clause: 7.6
mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::ListNumbering, value);
}
}
void SwTaggedPDFHelper::BeginNumberedListStructureElements()
{
OSL_ENSURE( mpNumInfo, "List without mpNumInfo?" );
if ( !mpNumInfo )
return;
const SwFrame& rFrame = mpNumInfo->mrFrame;
assert(rFrame.IsTextFrame());
const SwTextFrame& rTextFrame = static_cast<const SwTextFrame&>(rFrame);
// Lowers of NonStructureElements should not be considered:
if (lcl_IsInNonStructEnv(rTextFrame))
return;
// do it for the first one in the follow chain that has content
for (SwFlowFrame const* pPrecede = rTextFrame.GetPrecede(); pPrecede; pPrecede = pPrecede->GetPrecede())
{
SwTextFrame const*const pText(static_cast<SwTextFrame const*>(pPrecede));
if (!pText->HasPara() || pText->GetPara()->HasContentPortions())
{
return;
}
}
const SwTextNode *const pTextNd = rTextFrame.GetTextNodeForParaProps();
const SwNumRule* pNumRule = pTextNd->GetNumRule();
const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame());
const bool bNumbered = !pTextNd->IsOutline() && pNodeNum && pNodeNum->GetParent() && pNumRule;
// Check, if we have to reopen a list or a list body:
// First condition:
// Paragraph is numbered/bulleted
if ( !bNumbered )
return;
const SwNumberTreeNode* pParent = pNodeNum->GetParent();
const bool bSameNumbering = lcl_HasPreviousParaSameNumRule(rTextFrame, *pTextNd);
// Second condition: current numbering is not 'interrupted'
if ( bSameNumbering )
{
sal_Int32 nReopenTag = -1;
// Two cases:
// 1. We have to reopen an existing list body tag:
// - If the current node is either the first child of its parent
// and its level > 1 or
// - Numbering should restart at the current node and its level > 1
// - The current item has no label
const bool bNewSubListStart = pParent->GetParent() && (pParent->IsFirst( pNodeNum ) || pTextNd->IsListRestart() );
const bool bNoLabel = !pTextNd->IsCountedInList() && !pTextNd->IsListRestart();
if ( bNewSubListStart || bNoLabel )
{
// Fine, we try to reopen the appropriate list body
NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
if ( bNewSubListStart )
{
// The list body tag associated with the parent has to be reopened
// to start a new list inside the list body
NumListBodyIdMap::const_iterator aIter;
do
aIter = rNumListBodyIdMap.find( pParent );
while ( aIter == rNumListBodyIdMap.end() && nullptr != ( pParent = pParent->GetParent() ) );
if ( aIter != rNumListBodyIdMap.end() )
nReopenTag = (*aIter).second;
}
else // if(bNoLabel)
{
// The list body tag of a 'counted' predecessor has to be reopened
const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true);
while ( pPrevious )
{
if ( pPrevious->IsCounted())
{
// get id of list body tag
const NumListBodyIdMap::const_iterator aIter = rNumListBodyIdMap.find( pPrevious );
if ( aIter != rNumListBodyIdMap.end() )
{
nReopenTag = (*aIter).second;
break;
}
}
pPrevious = pPrevious->GetPred(true);
}
}
}
// 2. We have to reopen an existing list tag:
else if ( !pParent->IsFirst( pNodeNum ) && !pTextNd->IsListRestart() )
{
// any other than the first node in a list level has to reopen the current
// list. The current list is associated in a map with the first child of the list:
NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
// Search backwards and check if any of the previous nodes has a list associated with it:
const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true);
while ( pPrevious )
{
// get id of list tag
const NumListIdMap::const_iterator aIter = rNumListIdMap.find( pPrevious );
if ( aIter != rNumListIdMap.end() )
{
nReopenTag = (*aIter).second;
break;
}
pPrevious = pPrevious->GetPred(true);
}
}
if ( -1 != nReopenTag )
{
m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement();
mpPDFExtOutDevData->SetCurrentStructureElement( nReopenTag );
#if OSL_DEBUG_LEVEL > 1
aStructStack.push_back( 99 );
#endif
}
}
else
{
// clear list maps in case a list has been interrupted
NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
rNumListIdMap.clear();
NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
rNumListBodyIdMap.clear();
}
// New tags:
const bool bNewListTag = (pNodeNum->GetParent()->IsFirst( pNodeNum ) || pTextNd->IsListRestart() || !bSameNumbering);
const bool bNewItemTag = bNewListTag || pTextNd->IsCountedInList(); // If the text node is not counted, we do not start a new list item:
if ( bNewListTag )
BeginTag( vcl::PDFWriter::List, aListString );
if ( bNewItemTag )
{
BeginTag( vcl::PDFWriter::ListItem, aListItemString );
assert(rTextFrame.GetPara());
// check whether to open LBody now or delay until after Lbl
if (!rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering))
{
BeginTag(vcl::PDFWriter::LIBody, aListBodyString);
}
}
}
void SwTaggedPDFHelper::BeginBlockStructureElements()
{
const SwFrame* pFrame = &mpFrameInfo->mrFrame;
// Lowers of NonStructureElements should not be considered:
if (lcl_IsInNonStructEnv(*pFrame) && !pFrame->IsFlyFrame())
return;
// Check if we have to reopen an existing structure element.
// This has to be done e.g., if pFrame is a follow frame.
if ( CheckReopenTag() )
return;
sal_uInt16 nPDFType = USHRT_MAX;
OUString aPDFType;
switch ( pFrame->GetType() )
{
/*
* GROUPING ELEMENTS
*/
case SwFrameType::Page :
// Document: Document
nPDFType = vcl::PDFWriter::Document;
aPDFType = aDocumentString;
break;
case SwFrameType::Header :
case SwFrameType::Footer :
// Header, Footer: NonStructElement
nPDFType = vcl::PDFWriter::NonStructElement;
break;
case SwFrameType::FtnCont :
// Footnote container: Division
nPDFType = vcl::PDFWriter::Division;
aPDFType = aDivString;
break;
case SwFrameType::Ftn :
// Footnote frame: Note
// Note: vcl::PDFWriter::Note is actually a ILSE. Nevertheless
// we treat it like a grouping element!
nPDFType = vcl::PDFWriter::Note;
aPDFType = aNoteString;
break;
case SwFrameType::Section :
// Section: TOX, Index, or Sect
{
const SwSection* pSection =
static_cast<const SwSectionFrame*>(pFrame)->GetSection();
// open all parent sections, so that the SEs of sections
// are nested in the same way as their SwSectionNodes
std::vector<SwSection const*> parents;
for (SwSection const* pParent = pSection->GetParent();
pParent != nullptr; pParent = pParent->GetParent())
{
parents.push_back(pParent);
}
for (auto it = parents.rbegin(); it != parents.rend(); ++it)
{
// key is the SwSection - see lcl_GetKeyFromFrame()
OpenTagImpl(*it);
}
FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
if (rFrameTagSet.find(pSection) != rFrameTagSet.end())
{
// special case: section may have *multiple* master frames,
// when it is interrupted by nested section - reopen!
OpenTagImpl(pSection);
break;
}
else if (SectionType::ToxHeader == pSection->GetType())
{
nPDFType = vcl::PDFWriter::Caption;
aPDFType = aCaptionString;
}
else if (SectionType::ToxContent == pSection->GetType())
{
const SwTOXBase* pTOXBase = pSection->GetTOXBase();
if ( pTOXBase )
{
if ( TOX_INDEX == pTOXBase->GetType() )
{
nPDFType = vcl::PDFWriter::Index;
aPDFType = aIndexString;
}
else
{
nPDFType = vcl::PDFWriter::TOC;
aPDFType = aTOCString;
}
}
}
else if ( SectionType::Content == pSection->GetType() )
{
nPDFType = vcl::PDFWriter::Section;
aPDFType = aSectString;
}
}
break;
/*
* BLOCK-LEVEL STRUCTURE ELEMENTS
*/
case SwFrameType::Txt :
{
SwTextFrame const& rTextFrame(*static_cast<const SwTextFrame*>(pFrame));
const SwTextNode *const pTextNd(rTextFrame.GetTextNodeForParaProps());
// lazy open LBody after Lbl
if (!pTextNd->IsOutline()
&& rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering))
{
sal_Int32 const nId = BeginTagImpl(nullptr, vcl::PDFWriter::LIBody, aListBodyString);
SwNodeNum const*const pNodeNum(pTextNd->GetNum(rTextFrame.getRootFrame()));
NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
rNumListBodyIdMap[ pNodeNum ] = nId;
}
const SwFormat* pTextFormat = pTextNd->GetFormatColl();
const SwFormat* pParentTextFormat = pTextFormat ? pTextFormat->DerivedFrom() : nullptr;
OUString sStyleName;
OUString sParentStyleName;
if ( pTextFormat)
SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
if ( pParentTextFormat)
SwStyleNameMapper::FillProgName( pParentTextFormat->GetName(), sParentStyleName, SwGetPoolIdFromName::TxtColl );
// This is the default. If the paragraph could not be mapped to
// any of the standard pdf tags, we write a user defined tag
// <stylename> with role = P
nPDFType = vcl::PDFWriter::Paragraph;
aPDFType = sStyleName;
// Quotations: BlockQuote
if (sStyleName == aQuotations)
{
nPDFType = vcl::PDFWriter::BlockQuote;
aPDFType = aBlockQuoteString;
}
// Caption: Caption
else if (sStyleName == aCaption)
{
nPDFType = vcl::PDFWriter::Caption;
aPDFType = aCaptionString;
}
// Caption: Caption
else if (sParentStyleName == aCaption)
{
nPDFType = vcl::PDFWriter::Caption;
aPDFType = sStyleName + aCaptionString;
}
// Heading: H
else if (sStyleName == aHeading)
{
nPDFType = vcl::PDFWriter::Heading;
aPDFType = aHString;
}
// Heading: H1 - H6
if (int nRealLevel = pTextNd->GetAttrOutlineLevel() - 1;
nRealLevel >= 0
&& !pTextNd->IsInRedlines()
&& sw::IsParaPropsNode(*pFrame->getRootFrame(), *pTextNd))
{
switch(nRealLevel)
{
case 0 :
aPDFType = aH1String;
break;
case 1 :
aPDFType = aH2String;
break;
case 2 :
aPDFType = aH3String;
break;
case 3 :
aPDFType = aH4String;
break;
case 4 :
aPDFType = aH5String;
break;
case 5:
aPDFType = aH6String;
break;
case 6:
aPDFType = aH7String;
break;
case 7:
aPDFType = aH8String;
break;
case 8:
aPDFType = aH9String;
break;
case 9:
aPDFType = aH10String;
break;
default:
assert(false);
break;
}
// PDF/UA allows unlimited headings, but PDF only up to H6
// ... and apparently the extra H7.. must be declared in
// RoleMap, or veraPDF complains.
nRealLevel = std::min(nRealLevel, 5);
nPDFType = o3tl::narrowing<sal_uInt16>(vcl::PDFWriter::H1 + nRealLevel);
}
// Section: TOCI
else if ( pFrame->IsInSct() )
{
const SwSectionFrame* pSctFrame = pFrame->FindSctFrame();
const SwSection* pSection = pSctFrame->GetSection();
if ( SectionType::ToxContent == pSection->GetType() )
{
const SwTOXBase* pTOXBase = pSection->GetTOXBase();
if ( pTOXBase && TOX_INDEX != pTOXBase->GetType() )
{
// Special case: Open additional TOCI tag:
BeginTagImpl(nullptr, vcl::PDFWriter::TOCI, aTOCIString);
}
}
}
}
break;
case SwFrameType::Tab :
// TabFrame: Table
nPDFType = vcl::PDFWriter::Table;
aPDFType = aTableString;
{
// set up table column data:
const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pFrame);
const SwTable* pTable = pTabFrame->GetTable();
TableColumnsMap& rTableColumnsMap(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap);
const TableColumnsMap::const_iterator aIter = rTableColumnsMap.find( pTable );
if ( aIter == rTableColumnsMap.end() )
{
SwRectFnSet aRectFnSet(pTabFrame);
TableColumnsMapEntry& rCols = rTableColumnsMap[ pTable ];
const SwTabFrame* pMasterFrame = pTabFrame->IsFollow() ? pTabFrame->FindMaster( true ) : pTabFrame;
while ( pMasterFrame )
{
const SwRowFrame* pRowFrame = static_cast<const SwRowFrame*>(pMasterFrame->GetLower());
while ( pRowFrame )
{
const SwFrame* pCellFrame = pRowFrame->GetLower();
const tools::Long nLeft = aRectFnSet.GetLeft(pCellFrame->getFrameArea());
rCols.insert( nLeft );
while ( pCellFrame )
{
const tools::Long nRight = aRectFnSet.GetRight(pCellFrame->getFrameArea());
rCols.insert( nRight );
pCellFrame = pCellFrame->GetNext();
}
pRowFrame = static_cast<const SwRowFrame*>(pRowFrame->GetNext());
}
pMasterFrame = pMasterFrame->GetFollow();
}
}
}
break;
/*
* TABLE ELEMENTS
*/
case SwFrameType::Row :
// RowFrame: TR
if ( !static_cast<const SwRowFrame*>(pFrame)->IsRepeatedHeadline() )
{
nPDFType = vcl::PDFWriter::TableRow;
aPDFType = aTRString;
}
else
{
nPDFType = vcl::PDFWriter::NonStructElement;
}
break;
case SwFrameType::Cell :
// CellFrame: TH, TD
{
const SwTabFrame* pTable = static_cast<const SwCellFrame*>(pFrame)->FindTabFrame();
if ( pTable->IsInHeadline( *pFrame ) || lcl_IsHeadlineCell( *static_cast<const SwCellFrame*>(pFrame) ) )
{
nPDFType = vcl::PDFWriter::TableHeader;
aPDFType = aTHString;
}
else
{
nPDFType = vcl::PDFWriter::TableData;
aPDFType = aTDString;
}
}
break;
/*
* ILLUSTRATION
*/
case SwFrameType::Fly :
// FlyFrame: Figure, Formula, Control
// fly in content or fly at page
if (mpFrameInfo->m_isLink)
{ // tdf#154939 additional inner link element for flys
nPDFType = vcl::PDFWriter::Link;
aPDFType = aLinkString;
}
else
{
const SwFlyFrame* pFly = static_cast<const SwFlyFrame*>(pFrame);
if (pFly->GetAnchorFrame()->FindFooterOrHeader() != nullptr
|| pFly->GetFrameFormat()->GetAttrSet().Get(RES_DECORATIVE).GetValue())
{
nPDFType = vcl::PDFWriter::NonStructElement;
}
else if (pFly->Lower() && pFly->Lower()->IsNoTextFrame())
{
bool bFormula = false;
const SwNoTextFrame* pNoTextFrame = static_cast<const SwNoTextFrame*>(pFly->Lower());
SwOLENode* pOLENd = const_cast<SwOLENode*>(pNoTextFrame->GetNode()->GetOLENode());
if ( pOLENd )
{
SwOLEObj& aOLEObj = pOLENd->GetOLEObj();
uno::Reference< embed::XEmbeddedObject > aRef = aOLEObj.GetOleRef();
if ( aRef.is() )
{
bFormula = 0 != SotExchange::IsMath( SvGlobalName( aRef->getClassID() ) );
}
}
if ( bFormula )
{
nPDFType = vcl::PDFWriter::Formula;
aPDFType = aFormulaString;
}
else
{
nPDFType = vcl::PDFWriter::Figure;
aPDFType = aFigureString;
}
}
else
{
nPDFType = vcl::PDFWriter::Division;
aPDFType = aDivString;
}
}
break;
default: break;
}
if ( USHRT_MAX != nPDFType )
{
BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType );
}
}
void SwTaggedPDFHelper::EndStructureElements()
{
if (mpFrameInfo != nullptr)
{
if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
{ // close span at end of paragraph
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
++m_nEndStructureElement;
}
if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
{ // close link at end of paragraph
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
++m_nEndStructureElement;
}
}
while ( m_nEndStructureElement > 0 )
{
EndTag();
--m_nEndStructureElement;
}
CheckRestoreTag();
}
void SwTaggedPDFHelper::EndCurrentLink(OutputDevice const& rOut)
{
vcl::PDFExtOutDevData *const pPDFExtOutDevData(
dynamic_cast<vcl::PDFExtOutDevData *>(rOut.GetExtOutDevData()));
if (pPDFExtOutDevData && pPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
{
pPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
pPDFExtOutDevData->EndStructureElement();
#if OSL_DEBUG_LEVEL > 1
aStructStack.pop_back();
#endif
}
}
void SwTaggedPDFHelper::EndCurrentAll()
{
if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
{
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
}
if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
{
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
}
}
void SwTaggedPDFHelper::EndCurrentSpan()
{
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
EndTag(); // close span
}
void SwTaggedPDFHelper::CreateCurrentSpan(
SwTextPaintInfo const& rInf, OUString const& rStyleName)
{
assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan);
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.emplace(
SwEnhancedPDFState::Span{
rInf.GetFont()->GetUnderline(),
rInf.GetFont()->GetOverline(),
rInf.GetFont()->GetStrikeout(),
rInf.GetFont()->GetEmphasisMark(),
rInf.GetFont()->GetEscapement(),
rInf.GetFont()->GetActual(),
rInf.GetFont()->GetLanguage(),
rStyleName});
// leave it open to let next portion decide to merge or close
--m_nEndStructureElement;
}
bool SwTaggedPDFHelper::CheckContinueSpan(
SwTextPaintInfo const& rInf, std::u16string_view const rStyleName,
SwTextAttr const*const pInetFormatAttr)
{
// for now, don't create span inside of link - this should be very rare
// situation and it looks complicated to implement.
assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan
|| !mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
{
if (pInetFormatAttr && pInetFormatAttr == *mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
{
return true;
}
else
{
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
EndTag();
return false;
}
}
if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan && pInetFormatAttr)
{
EndCurrentSpan();
return false;
}
if (!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
return false;
SwEnhancedPDFState::Span const& rCurrent(*mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan);
bool const ret(rCurrent.eUnderline == rInf.GetFont()->GetUnderline()
&& rCurrent.eOverline == rInf.GetFont()->GetOverline()
&& rCurrent.eStrikeout == rInf.GetFont()->GetStrikeout()
&& rCurrent.eFontEmphasis == rInf.GetFont()->GetEmphasisMark()
&& rCurrent.nEscapement == rInf.GetFont()->GetEscapement()
&& rCurrent.nScript == rInf.GetFont()->GetActual()
&& rCurrent.nLang == rInf.GetFont()->GetLanguage()
&& rCurrent.StyleName == rStyleName);
if (!ret)
{
EndCurrentSpan();
}
return ret;
}
void SwTaggedPDFHelper::BeginInlineStructureElements()
{
const SwLinePortion* pPor = &mpPorInfo->mrPor;
const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo();
const SwTextFrame* pFrame = rInf.GetTextFrame();
// Lowers of NonStructureElements should not be considered:
if ( lcl_IsInNonStructEnv( *pFrame ) )
return;
std::pair<SwTextNode const*, sal_Int32> const pos(
pFrame->MapViewToModel(rInf.GetIdx()));
SwTextAttr const*const pInetFormatAttr =
pos.first->GetTextAttrAt(pos.second, RES_TXTATR_INETFMT);
OUString sStyleName;
if (!pInetFormatAttr)
{
std::vector<SwTextAttr *> const charAttrs(
pos.first->GetTextAttrsAt(pos.second, RES_TXTATR_CHARFMT));
// TODO: handle more than 1 char style?
const SwCharFormat* pCharFormat = (charAttrs.size())
? (*charAttrs.begin())->GetCharFormat().GetCharFormat() : nullptr;
if (pCharFormat)
SwStyleNameMapper::FillProgName( pCharFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
}
// note: ILSE may be nested, so only end the span if needed to start new one
bool const isContinueSpan(CheckContinueSpan(rInf, sStyleName, pInetFormatAttr));
sal_uInt16 nPDFType = USHRT_MAX;
OUString aPDFType;
switch ( pPor->GetWhichPor() )
{
case PortionType::PostIts:
if (!mpPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap.empty())
{
nPDFType = vcl::PDFWriter::Annot;
aPDFType = aAnnotString;
}
break;
case PortionType::Hyphen :
case PortionType::SoftHyphen :
// Check for alternative spelling:
case PortionType::HyphenStr :
case PortionType::SoftHyphenStr :
nPDFType = vcl::PDFWriter::Span;
aPDFType = aSpanString;
break;
case PortionType::Fly:
// if a link is split by a fly overlap, then there will be multiple
// annotations for the link, and hence there must be multiple SEs,
// so every annotation has its own SE.
if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
{
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
EndTag();
}
break;
case PortionType::Lay :
case PortionType::Text :
case PortionType::Para :
{
// Check for Link:
if( pInetFormatAttr )
{
if (!isContinueSpan)
{
nPDFType = vcl::PDFWriter::Link;
aPDFType = aLinkString;
assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace(pInetFormatAttr);
// leave it open to let next portion decide to merge or close
--m_nEndStructureElement;
}
}
// Check for Quote/Code character style:
else if (sStyleName == aQuotation)
{
if (!isContinueSpan)
{
nPDFType = vcl::PDFWriter::Quote;
aPDFType = aQuoteString;
CreateCurrentSpan(rInf, sStyleName);
}
}
else if (sStyleName == aSourceText)
{
if (!isContinueSpan)
{
nPDFType = vcl::PDFWriter::Code;
aPDFType = aCodeString;
CreateCurrentSpan(rInf, sStyleName);
}
}
else if (!isContinueSpan)
{
const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage();
const SwFontScript nFont = rInf.GetFont()->GetActual();
const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault);
if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() ||
LINESTYLE_NONE != rInf.GetFont()->GetOverline() ||
STRIKEOUT_NONE != rInf.GetFont()->GetStrikeout() ||
FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() ||
0 != rInf.GetFont()->GetEscapement() ||
SwFontScript::Latin != nFont ||
nCurrentLanguage != nDefaultLang ||
!sStyleName.isEmpty())
{
nPDFType = vcl::PDFWriter::Span;
if (!sStyleName.isEmpty())
aPDFType = sStyleName;
else
aPDFType = aSpanString;
CreateCurrentSpan(rInf, sStyleName);
}
}
}
break;
case PortionType::Footnote :
nPDFType = vcl::PDFWriter::Link;
aPDFType = aLinkString;
break;
case PortionType::Field :
{
// check field type:
TextFrameIndex const nIdx = static_cast<const SwFieldPortion*>(pPor)->IsFollow()
? rInf.GetIdx() - TextFrameIndex(1)
: rInf.GetIdx();
const SwTextAttr* pHint = mpPorInfo->mrTextPainter.GetAttr( nIdx );
if ( pHint && RES_TXTATR_FIELD == pHint->Which() )
{
const SwField* pField = pHint->GetFormatField().GetField();
if ( SwFieldIds::GetRef == pField->Which() )
{
nPDFType = vcl::PDFWriter::Link;
aPDFType = aLinkString;
}
else if ( SwFieldIds::TableOfAuthorities == pField->Which() )
{
nPDFType = vcl::PDFWriter::BibEntry;
aPDFType = aBibEntryString;
}
}
}
break;
case PortionType::Multi:
{
SwMultiPortion const*const pMulti(static_cast<SwMultiPortion const*>(pPor));
if (pMulti->IsRuby())
{
EndCurrentAll();
switch (mpPorInfo->m_Mode)
{
case 0:
nPDFType = vcl::PDFWriter::Ruby;
aPDFType = "Ruby";
break;
case 1:
nPDFType = vcl::PDFWriter::RT;
aPDFType = "RT";
break;
case 2:
nPDFType = vcl::PDFWriter::RB;
aPDFType = "RB";
break;
}
}
else if (pMulti->IsDouble())
{
EndCurrentAll();
switch (mpPorInfo->m_Mode)
{
case 0:
nPDFType = vcl::PDFWriter::Warichu;
aPDFType = "Warichu";
break;
case 1:
nPDFType = vcl::PDFWriter::WP;
aPDFType = "WP";
break;
case 2:
nPDFType = vcl::PDFWriter::WT;
aPDFType = "WT";
break;
}
}
}
break;
// for FootnoteNum, is called twice: outer generates Lbl, inner Link
case PortionType::FootnoteNum:
assert(!isContinueSpan); // is at start
if (mpPorInfo->m_Mode == 0)
{ // tdf#152218 link both directions
nPDFType = vcl::PDFWriter::Link;
aPDFType = aLinkString;
break;
}
[[fallthrough]];
case PortionType::Number:
case PortionType::Bullet:
case PortionType::GrfNum:
assert(!isContinueSpan); // is at start
if (mpPorInfo->m_Mode == 1)
{ // only works for multiple lines via wrapper from PaintSwFrame
nPDFType = vcl::PDFWriter::LILabel;
aPDFType = aListLabelString;
}
break;
case PortionType::Tab :
case PortionType::TabRight :
case PortionType::TabCenter :
case PortionType::TabDecimal :
nPDFType = vcl::PDFWriter::NonStructElement;
break;
default: break;
}
if ( USHRT_MAX != nPDFType )
{
BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType );
}
}
bool SwTaggedPDFHelper::IsExportTaggedPDF( const OutputDevice& rOut )
{
vcl::PDFExtOutDevData* pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() );
return pPDFExtOutDevData && pPDFExtOutDevData->GetIsExportTaggedPDF();
}
SwEnhancedPDFExportHelper::SwEnhancedPDFExportHelper( SwEditShell& rSh,
OutputDevice& rOut,
const OUString& rPageRange,
bool bSkipEmptyPages,
bool bEditEngineOnly,
const SwPrintData& rPrintData )
: mrSh( rSh ),
mrOut( rOut ),
mbSkipEmptyPages( bSkipEmptyPages ),
mbEditEngineOnly( bEditEngineOnly ),
mrPrintData( rPrintData )
{
if ( !rPageRange.isEmpty() )
mpRangeEnum.reset( new StringRangeEnumerator( rPageRange, 0, mrSh.GetPageCount()-1 ) );
if ( mbSkipEmptyPages )
{
maPageNumberMap.resize( mrSh.GetPageCount() );
const SwPageFrame* pCurrPage =
static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
sal_Int32 nPageNumber = 0;
for ( size_t i = 0, n = maPageNumberMap.size(); i < n && pCurrPage; ++i )
{
if ( pCurrPage->IsEmptyPage() )
maPageNumberMap[i] = -1;
else
maPageNumberMap[i] = nPageNumber++;
pCurrPage = static_cast<const SwPageFrame*>( pCurrPage->GetNext() );
}
}
#if OSL_DEBUG_LEVEL > 1
aStructStack.clear();
#endif
const sal_Int16 nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() );
TypedWhichId<SvxLanguageItem> nLangRes = RES_CHRATR_LANGUAGE;
if ( i18n::ScriptType::ASIAN == nScript )
nLangRes = RES_CHRATR_CJK_LANGUAGE;
else if ( i18n::ScriptType::COMPLEX == nScript )
nLangRes = RES_CHRATR_CTL_LANGUAGE;
const SvxLanguageItem& rLangItem = mrSh.GetDoc()->GetDefault( nLangRes );
auto const eLanguageDefault = rLangItem.GetLanguage();
EnhancedPDFExport(eLanguageDefault);
}
SwEnhancedPDFExportHelper::~SwEnhancedPDFExportHelper()
{
}
tools::Rectangle SwEnhancedPDFExportHelper::SwRectToPDFRect(const SwPageFrame* pCurrPage,
const tools::Rectangle& rRectangle) const
{
if (!::sw::IsShrinkPageForPostIts(mrSh, mrPrintData)) // tdf#148729
{
return rRectangle;
}
return MapSwRectToPDFRect(pCurrPage, rRectangle);
}
double SwEnhancedPDFExportHelper::GetSwRectToPDFRectScale()
{
return 0.75;
}
tools::Rectangle SwEnhancedPDFExportHelper::MapSwRectToPDFRect(const SwPageFrame* pCurrPage,
const tools::Rectangle& rRectangle)
{
//the page has been scaled by 75% and vertically centered, so adjust these
//rectangles equivalently
tools::Rectangle aRect(rRectangle);
Size aRectSize(aRect.GetSize());
double fScale = GetSwRectToPDFRectScale();
aRectSize.setWidth( aRectSize.Width() * fScale );
aRectSize.setHeight( aRectSize.Height() * fScale );
tools::Long nOrigHeight = pCurrPage->getFrameArea().Height();
tools::Long nNewHeight = nOrigHeight*fScale;
tools::Long nShiftY = (nOrigHeight-nNewHeight)/2;
aRect.SetLeft( aRect.Left() * fScale );
aRect.SetTop( aRect.Top() * fScale );
aRect.Move(0, nShiftY);
aRect.SetSize(aRectSize);
return aRect;
}
void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDefault)
{
vcl::PDFExtOutDevData* pPDFExtOutDevData =
dynamic_cast< vcl::PDFExtOutDevData*>( mrOut.GetExtOutDevData() );
if ( !pPDFExtOutDevData )
return;
// set the document locale
lang::Locale const aDocLocale( LanguageTag(eLanguageDefault).getLocale() );
pPDFExtOutDevData->SetDocumentLocale( aDocLocale );
// Prepare the output device:
mrOut.Push( vcl::PushFlags::MAPMODE );
MapMode aMapMode( mrOut.GetMapMode() );
aMapMode.SetMapUnit( MapUnit::MapTwip );
mrOut.SetMapMode( aMapMode );
// Create new cursor and lock the view:
SwDoc* pDoc = mrSh.GetDoc();
mrSh.SwCursorShell::Push();
mrSh.SwCursorShell::ClearMark();
const bool bOldLockView = mrSh.IsViewLocked();
mrSh.LockView( true );
if ( !mbEditEngineOnly )
{
assert(pPDFExtOutDevData->GetSwPDFState() == nullptr);
pPDFExtOutDevData->SetSwPDFState(new SwEnhancedPDFState(eLanguageDefault));
// POSTITS
if ( pPDFExtOutDevData->GetIsExportNotes() )
{
std::vector<SwFormatField*> vpFields;
mrSh.GetFieldType(SwFieldIds::Postit, OUString())->GatherFields(vpFields);
for(auto pFormatField : vpFields)
{
const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode();
OSL_ENSURE(nullptr != pTNd, "Enhanced pdf export - text node is missing");
if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField))
continue;
// Link Rectangle
const SwRect& rNoteRect = mrSh.GetCharRect();
const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower());
// Link PageNums
std::vector<sal_Int32> aNotePageNums = CalcOutputPageNums(rNoteRect);
for (sal_Int32 aNotePageNum : aNotePageNums)
{
// Use the NumberFormatter to get the date string:
const SwPostItField* pField = static_cast<SwPostItField*>(pFormatField->GetField());
SvNumberFormatter* pNumFormatter = pDoc->GetNumberFormatter();
const Date aDateDiff(pField->GetDate() - pNumFormatter->GetNullDate());
const sal_uLong nFormat = pNumFormatter->GetStandardFormat(SvNumFormatType::DATE, pField->GetLanguage());
OUString sDate;
const Color* pColor;
pNumFormatter->GetOutputString(aDateDiff.GetDate(), nFormat, sDate, &pColor);
vcl::pdf::PDFNote aNote;
// The title should consist of the author and the date:
aNote.maTitle = pField->GetPar1() + ", " + sDate + ", " + (pField->GetResolved() ? SwResId(STR_RESOLVED) : u""_ustr);
// Guess what the contents contains...
aNote.maContents = pField->GetText();
tools::Rectangle aPopupRect(0, 0);
SwPostItMgr* pPostItMgr = pDoc->GetEditShell()->GetPostItMgr();
for (auto it = pPostItMgr->begin(); it != pPostItMgr->end(); ++it)
{
sw::annotation::SwAnnotationWin* pWin = it->get()->mpPostIt;
if (pWin)
{
const SwRect& aAnnotRect = pWin->GetAnchorRect();
if (aAnnotRect.Contains(rNoteRect))
{
Point aPt(pDoc->GetEditShell()->GetWin()->PixelToLogic(pWin->GetPosPixel()));
Size aSize(pDoc->GetEditShell()->GetWin()->PixelToLogic(pWin->GetSizePixel()));
aPopupRect = tools::Rectangle(aPt, aSize);
}
}
}
// Link Export
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rNoteRect.SVRect()));
sal_Int32 nNoteId = pPDFExtOutDevData->CreateNote(aRect, aNote, aPopupRect, aNotePageNum);
if (mrPrintData.GetPrintPostIts() != SwPostItMode::InMargins)
{
const IdMapEntry aNoteEntry(aRect, nNoteId);
pPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap.push_back(aNoteEntry);
}
}
mrSh.SwCursorShell::ClearMark();
}
}
// HYPERLINKS
SwGetINetAttrs aArr;
mrSh.GetINetAttrs( aArr );
for( auto &rAttr : aArr )
{
SwGetINetAttr* p = &rAttr;
OSL_ENSURE( nullptr != p, "Enhanced pdf export - SwGetINetAttr is missing" );
const SwTextNode* pTNd = p->rINetAttr.GetpTextNode();
OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" );
// 1. Check if the whole paragraph is hidden
// 2. Move to the hyperlink
// 3. Check for hidden text attribute
if ( !pTNd->IsHidden() &&
mrSh.GotoINetAttr( p->rINetAttr ) &&
!mrSh.IsInHiddenRange(/*bSelect=*/false) )
{
// Select the hyperlink:
mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );
if ( mrSh.SwCursorShell::SelectTextAttr( RES_TXTATR_INETFMT, true ) )
{
// First, we create the destination, because there may be more
// than one link to this destination:
OUString aURL( INetURLObject::decode(
p->rINetAttr.GetINetFormat().GetValue(),
INetURLObject::DecodeMechanism::Unambiguous ) );
// We have to distinguish between internal and real URLs
const bool bInternal = '#' == aURL[0];
// GetCursor_() is a SwShellCursor, which is derived from
// SwSelPaintRects, therefore the rectangles of the current
// selection can be easily obtained:
// Note: We make a copy of the rectangles, because they may
// be deleted again in JumpToSwMark.
SwRects const aTmp(GetCursorRectsContainingText(mrSh));
OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
OUString altText(p->rINetAttr.GetINetFormat().GetName());
if (altText.isEmpty())
altText = mrSh.GetSelText();
const SwPageFrame* pSelectionPage =
static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
// Create the destination for internal links:
sal_Int32 nDestId = -1;
if ( bInternal )
{
aURL = aURL.copy( 1 );
mrSh.SwCursorShell::ClearMark();
if (! JumpToSwMark( &mrSh, aURL ))
{
continue; // target deleted
}
// Destination Rectangle
const SwRect& rDestRect = mrSh.GetCharRect();
const SwPageFrame* pCurrPage =
static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
// Destination PageNum
const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
// Destination Export
if ( -1 != nDestPageNum )
{
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
}
}
if ( !bInternal || -1 != nDestId )
{
// #i44368# Links in Header/Footer
const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd );
// Create links for all selected rectangles:
const size_t nNumOfRects = aTmp.size();
for ( size_t i = 0; i < nNumOfRects; ++i )
{
// Link Rectangle
const SwRect& rLinkRect( aTmp[ i ] );
// Link PageNums
std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect );
for (sal_Int32 aLinkPageNum : aLinkPageNums)
{
// Link Export
tools::Rectangle aRect(SwRectToPDFRect(pSelectionPage, rLinkRect.SVRect()));
const sal_Int32 nLinkId =
pPDFExtOutDevData->CreateLink(aRect, altText, aLinkPageNum);
// Store link info for tagged pdf output:
const IdMapEntry aLinkEntry( rLinkRect, nLinkId );
pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
// Connect Link and Destination:
if ( bInternal )
pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
else
pPDFExtOutDevData->SetLinkURL( nLinkId, aURL );
// #i44368# Links in Header/Footer
if ( bHeaderFooter )
MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, aURL, bInternal, altText);
}
}
}
}
}
mrSh.SwCursorShell::ClearMark();
}
// HYPERLINKS (Graphics, Frames, OLEs )
for(sw::SpzFrameFormat* pFrameFormat: *pDoc->GetSpzFrameFormats())
{
const SwFormatURL* pItem;
if ( RES_DRAWFRMFMT != pFrameFormat->Which() &&
GetFrameOfModify(mrSh.GetLayout(), *pFrameFormat, SwFrameType::Fly) &&
(pItem = pFrameFormat->GetAttrSet().GetItemIfSet( RES_URL )) )
{
const SwPageFrame* pCurrPage =
static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
OUString aURL( pItem->GetURL() );
if (aURL.isEmpty())
continue;
const bool bInternal = '#' == aURL[0];
// Create the destination for internal links:
sal_Int32 nDestId = -1;
if ( bInternal )
{
aURL = aURL.copy( 1 );
mrSh.SwCursorShell::ClearMark();
if (! JumpToSwMark( &mrSh, aURL ))
{
continue; // target deleted
}
// Destination Rectangle
const SwRect& rDestRect = mrSh.GetCharRect();
pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
// Destination PageNum
const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
// Destination Export
if ( -1 != nDestPageNum )
{
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
}
}
if ( !bInternal || -1 != nDestId )
{
Point aNullPt;
const SwRect aLinkRect = pFrameFormat->FindLayoutRect( false, &aNullPt );
OUString const formatName(pFrameFormat->GetName());
// Link PageNums
std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( aLinkRect );
// Link Export
for (sal_Int32 aLinkPageNum : aLinkPageNums)
{
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect()));
const sal_Int32 nLinkId =
pPDFExtOutDevData->CreateLink(aRect, formatName, aLinkPageNum);
// Store link info for tagged pdf output:
const IdMapEntry aLinkEntry(aLinkRect, nLinkId);
pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
// Connect Link and Destination:
if ( bInternal )
pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
else
pPDFExtOutDevData->SetLinkURL( nLinkId, aURL );
// #i44368# Links in Header/Footer
const SwFormatAnchor &rAnch = pFrameFormat->GetAnchor();
if (RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId())
{
const SwNode* pAnchorNode = rAnch.GetAnchorNode();
if ( pAnchorNode && pDoc->IsInHeaderFooter( *pAnchorNode ) )
{
const SwTextNode* pTNd = pAnchorNode->GetTextNode();
if ( pTNd )
MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, aLinkRect, nDestId, aURL, bInternal, formatName);
}
}
}
}
}
else if (pFrameFormat->Which() == RES_DRAWFRMFMT)
{
// Turn media shapes into Screen annotations.
if (SdrObject* pObject = pFrameFormat->FindRealSdrObject())
{
SwRect aSnapRect(pObject->GetSnapRect());
std::vector<sal_Int32> aScreenPageNums = CalcOutputPageNums(aSnapRect);
if (aScreenPageNums.empty())
continue;
uno::Reference<drawing::XShape> xShape(pObject->getUnoShape(), uno::UNO_QUERY);
if (xShape->getShapeType() == "com.sun.star.drawing.MediaShape")
{
uno::Reference<beans::XPropertySet> xShapePropSet(xShape, uno::UNO_QUERY);
OUString title;
xShapePropSet->getPropertyValue(u"Title"_ustr) >>= title;
OUString description;
xShapePropSet->getPropertyValue(u"Description"_ustr) >>= description;
OUString const altText(title.isEmpty()
? description
: description.isEmpty()
? title
: OUString::Concat(title) + OUString::Concat("\n") + OUString::Concat(description));
OUString aMediaURL;
xShapePropSet->getPropertyValue(u"MediaURL"_ustr) >>= aMediaURL;
if (!aMediaURL.isEmpty())
{
OUString const mimeType(xShapePropSet->getPropertyValue(u"MediaMimeType"_ustr).get<OUString>());
const SwPageFrame* pCurrPage = mrSh.GetLayout()->GetPageAtPos(aSnapRect.Center());
tools::Rectangle aPDFRect(SwRectToPDFRect(pCurrPage, aSnapRect.SVRect()));
for (sal_Int32 nScreenPageNum : aScreenPageNums)
{
sal_Int32 nScreenId = pPDFExtOutDevData->CreateScreen(aPDFRect, altText, mimeType, nScreenPageNum, pObject);
if (aMediaURL.startsWith("vnd.sun.star.Package:"))
{
// Embedded media.
OUString aTempFileURL;
xShapePropSet->getPropertyValue(u"PrivateTempFileURL"_ustr) >>= aTempFileURL;
pPDFExtOutDevData->SetScreenStream(nScreenId, aTempFileURL);
}
else
// Linked media.
pPDFExtOutDevData->SetScreenURL(nScreenId, aMediaURL);
}
}
}
}
}
mrSh.SwCursorShell::ClearMark();
}
// REFERENCES
std::vector<SwFormatField*> vpFields;
mrSh.GetFieldType( SwFieldIds::GetRef, OUString() )->GatherFields(vpFields);
for(auto pFormatField : vpFields )
{
if( pFormatField->GetTextField() && pFormatField->IsFieldInDoc() )
{
const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode();
OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" );
if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField))
continue;
// Select the field:
mrSh.SwCursorShell::SetMark();
mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );
// Link Rectangles
SwRects const aTmp(GetCursorRectsContainingText(mrSh));
OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
mrSh.SwCursorShell::ClearMark();
// Destination Rectangle
const SwGetRefField* pField = static_cast<SwGetRefField*>(pFormatField->GetField());
const OUString& rRefName = pField->GetSetRefName();
mrSh.GotoRefMark( rRefName, pField->GetSubType(), pField->GetSeqNo(), pField->GetFlags() );
const SwRect& rDestRect = mrSh.GetCharRect();
const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
// Destination PageNum
const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
if ( -1 != nDestPageNum )
{
// Destination Export
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
// #i44368# Links in Header/Footer
const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd );
OUString const content(pField->ExpandField(true, mrSh.GetLayout()));
// Create links for all selected rectangles:
const size_t nNumOfRects = aTmp.size();
for ( size_t i = 0; i < nNumOfRects; ++i )
{
// Link rectangle
const SwRect& rLinkRect( aTmp[ i ] );
// Link PageNums
std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect );
for (sal_Int32 aLinkPageNum : aLinkPageNums)
{
// Link Export
aRect = SwRectToPDFRect(pCurrPage, rLinkRect.SVRect());
const sal_Int32 nLinkId =
pPDFExtOutDevData->CreateLink(aRect, content, aLinkPageNum);
// Store link info for tagged pdf output:
const IdMapEntry aLinkEntry( rLinkRect, nLinkId );
pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
// Connect Link and Destination:
pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
// #i44368# Links in Header/Footer
if ( bHeaderFooter )
{
MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, u""_ustr, true, content);
}
}
}
}
}
mrSh.SwCursorShell::ClearMark();
}
ExportAuthorityEntryLinks();
// FOOTNOTES
const size_t nFootnoteCount = pDoc->GetFootnoteIdxs().size();
for ( size_t nIdx = 0; nIdx < nFootnoteCount; ++nIdx )
{
// Set cursor to text node that contains the footnote:
const SwTextFootnote* pTextFootnote = pDoc->GetFootnoteIdxs()[ nIdx ];
SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode());
mrSh.GetCursor_()->GetPoint()->Assign(rTNd, pTextFootnote->GetStart());
// 1. Check if the whole paragraph is hidden
// 2. Check for hidden text attribute
if (rTNd.GetTextNode()->IsHidden() || mrSh.IsInHiddenRange(/*bSelect=*/false)
|| (mrSh.GetLayout()->IsHideRedlines()
&& sw::IsFootnoteDeleted(pDoc->getIDocumentRedlineAccess(), *pTextFootnote)))
{
continue;
}
SwCursorSaveState aSaveState( *mrSh.GetCursor_() );
// Select the footnote:
mrSh.SwCursorShell::SetMark();
mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );
// Link Rectangle
SwRects aTmp;
aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() );
OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
mrSh.GetCursor_()->RestoreSavePos();
mrSh.SwCursorShell::ClearMark();
if (aTmp.empty())
continue;
const SwRect aLinkRect( aTmp[ 0 ] );
// Goto footnote text:
if ( mrSh.GotoFootnoteText() )
{
// Destination Rectangle
const SwRect& rDestRect = mrSh.GetCharRect();
const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
if ( -1 != nDestPageNum )
{
const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
// Destination PageNum
tools::Rectangle aRect = SwRectToPDFRect(pCurrPage, rDestRect.SVRect());
// Back link rectangle calculation
const SwPageFrame* fnBodyPage = pCurrPage->getRootFrame()->GetPageByPageNum(nDestPageNum+1);
SwRect fnSymbolRect;
if (fnBodyPage->IsVertical()){
tools::Long fnSymbolTop = fnBodyPage->GetTopMargin() + fnBodyPage->getFrameArea().Top();
tools::Long symbolHeight = rDestRect.Top() - fnSymbolTop;
fnSymbolRect = SwRect(rDestRect.Pos().X(),fnSymbolTop,rDestRect.Width(),symbolHeight);
} else {
if (fnBodyPage->IsRightToLeft()){
tools::Long fnSymbolRight = fnBodyPage->getFrameArea().Right() - fnBodyPage->GetRightMargin();
tools::Long symbolWidth = fnSymbolRight - rDestRect.Right();
fnSymbolRect = SwRect(rDestRect.Pos().X(),rDestRect.Pos().Y(),symbolWidth,rDestRect.Height());
} else {
tools::Long fnSymbolLeft = fnBodyPage->GetLeftMargin() + fnBodyPage->getFrameArea().Left();
tools::Long symbolWidth = rDestRect.Left() - fnSymbolLeft;
fnSymbolRect = SwRect(fnSymbolLeft,rDestRect.Pos().Y(),symbolWidth,rDestRect.Height());
}
}
tools::Rectangle aFootnoteSymbolRect = SwRectToPDFRect(pCurrPage, fnSymbolRect.SVRect());
OUString const numStrSymbol(pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, mrSh.GetLayout(), true));
OUString const numStrRef(pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, mrSh.GetLayout(), false));
// Export back link
const sal_Int32 nBackLinkId = pPDFExtOutDevData->CreateLink(aFootnoteSymbolRect, numStrSymbol, nDestPageNum);
// Destination Export
const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
mrSh.GotoFootnoteAnchor();
// Link PageNums
sal_Int32 aLinkPageNum = CalcOutputPageNum( aLinkRect );
pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
// Link Export
aRect = SwRectToPDFRect(pCurrPage, aLinkRect.SVRect());
const sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, numStrRef, aLinkPageNum);
// Back link destination Export
const sal_Int32 nBackDestId = pPDFExtOutDevData->CreateDest(aRect, aLinkPageNum);
// Store link info for tagged pdf output:
const IdMapEntry aLinkEntry( aLinkRect, nLinkId );
pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
// Store backlink info for tagged pdf output:
const IdMapEntry aBackLinkEntry( aFootnoteSymbolRect, nBackLinkId );
pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aBackLinkEntry);
// Connect Links and Destinations:
pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
pPDFExtOutDevData->SetLinkDest( nBackLinkId, nBackDestId );
}
}
}
// OUTLINE
if( pPDFExtOutDevData->GetIsExportBookmarks() )
{
typedef std::pair< sal_Int8, sal_Int32 > StackEntry;
std::stack< StackEntry > aOutlineStack;
aOutlineStack.push( StackEntry( -1, -1 ) ); // push default value
const SwOutlineNodes::size_type nOutlineCount =
mrSh.getIDocumentOutlineNodesAccess()->getOutlineNodesCount();
for ( SwOutlineNodes::size_type i = 0; i < nOutlineCount; ++i )
{
// Check if outline is hidden
const SwTextNode* pTNd = mrSh.GetNodes().GetOutLineNds()[ i ]->GetTextNode();
assert(pTNd && "Enhanced pdf export - text node is missing");
if ( pTNd->IsHidden() ||
!sw::IsParaPropsNode(*mrSh.GetLayout(), *pTNd) ||
// #i40292# Skip empty outlines:
pTNd->GetText().isEmpty())
continue;
// Get parent id from stack:
const sal_Int8 nLevel = static_cast<sal_Int8>(mrSh.getIDocumentOutlineNodesAccess()->getOutlineLevel( i ));
sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first;
while ( nLevelOnTopOfStack >= nLevel &&
nLevelOnTopOfStack != -1 )
{
aOutlineStack.pop();
nLevelOnTopOfStack = aOutlineStack.top().first;
}
const sal_Int32 nParent = aOutlineStack.top().second;
// Destination rectangle
mrSh.GotoOutline(i);
const SwRect& rDestRect = mrSh.GetCharRect();
const SwPageFrame* pCurrPage =
static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
// Destination PageNum
const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
if ( -1 != nDestPageNum )
{
// Destination Export
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
const sal_Int32 nDestId =
pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
// Outline entry text
const OUString& rEntry = mrSh.getIDocumentOutlineNodesAccess()->getOutlineText(
i, mrSh.GetLayout(), true, false, false );
// Create a new outline item:
const sal_Int32 nOutlineId =
pPDFExtOutDevData->CreateOutlineItem( nParent, rEntry, nDestId );
// Push current level and nOutlineId on stack:
aOutlineStack.push( StackEntry( nLevel, nOutlineId ) );
}
}
}
if( pPDFExtOutDevData->GetIsExportNamedDestinations() )
{
// #i56629# the iteration to convert the OOo bookmark (#bookmark)
// into PDF named destination, see section 8.2.1 in PDF 1.4 spec
// We need:
// 1. a name for the destination, formed from the standard OOo bookmark name
// 2. the destination, obtained from where the bookmark destination lies
IDocumentMarkAccess* const pMarkAccess = mrSh.GetDoc()->getIDocumentMarkAccess();
for(auto ppMark = pMarkAccess->getBookmarksBegin();
ppMark != pMarkAccess->getBookmarksEnd();
++ppMark)
{
//get the name
const ::sw::mark::MarkBase* pBkmk = *ppMark;
mrSh.SwCursorShell::ClearMark();
const OUString& sBkName = pBkmk->GetName();
//jump to it
if (! JumpToSwMark( &mrSh, sBkName ))
{
continue;
}
// Destination Rectangle
const SwRect& rDestRect = mrSh.GetCharRect();
const SwPageFrame* pCurrPage =
static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
// Destination PageNum
const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
// Destination Export
if ( -1 != nDestPageNum )
{
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
pPDFExtOutDevData->CreateNamedDest(sBkName, aRect, nDestPageNum);
}
}
mrSh.SwCursorShell::ClearMark();
//<--- i56629
}
}
else
{
// LINKS FROM EDITENGINE
std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks();
for ( const auto& rBookmark : rBookmarks )
{
OUString aBookmarkName( rBookmark.aBookmark );
const bool bInternal = '#' == aBookmarkName[0];
if ( bInternal )
{
aBookmarkName = aBookmarkName.copy( 1 );
JumpToSwMark( &mrSh, aBookmarkName );
// Destination Rectangle
const SwRect& rDestRect = mrSh.GetCharRect();
const SwPageFrame* pCurrPage =
static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
// Destination PageNum
const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
if ( -1 != nDestPageNum )
{
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
if ( rBookmark.nLinkId != -1 )
{
// Destination Export
const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
// Connect Link and Destination:
pPDFExtOutDevData->SetLinkDest( rBookmark.nLinkId, nDestId );
}
else
{
pPDFExtOutDevData->DescribeRegisteredDest(rBookmark.nDestId, aRect, nDestPageNum);
}
}
}
else
pPDFExtOutDevData->SetLinkURL( rBookmark.nLinkId, aBookmarkName );
}
rBookmarks.clear();
assert(pPDFExtOutDevData->GetSwPDFState());
delete pPDFExtOutDevData->GetSwPDFState();
pPDFExtOutDevData->SetSwPDFState(nullptr);
}
// Restore view, cursor, and outdev:
mrSh.LockView( bOldLockView );
mrSh.SwCursorShell::Pop(SwCursorShell::PopMode::DeleteCurrent);
mrOut.Pop();
}
void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks()
{
auto pPDFExtOutDevData = dynamic_cast<vcl::PDFExtOutDevData*>(mrOut.GetExtOutDevData());
if (!pPDFExtOutDevData)
{
return;
}
// Create PDF destinations for bibliography table entries
std::vector<std::tuple<const SwTOXBase*, const OUString*, sal_Int32>> vDestinations;
// string is the row node text, sal_Int32 is number of the destination
// Note: This way of iterating doesn't seem to take into account TOXes
// that are in a frame, probably in some other cases too
{
mrSh.GotoPage(1);
while (mrSh.GotoNextTOXBase())
{
const SwTOXBase* pIteratedTOX = nullptr;
while ((pIteratedTOX = mrSh.GetCurTOX()) != nullptr
&& pIteratedTOX->GetType() == TOX_AUTHORITIES)
{
if (const SwNode& rCurrentNode = mrSh.GetCursor()->GetPoint()->GetNode();
rCurrentNode.GetNodeType() == SwNodeType::Text)
{
if (mrSh.GetCursor()->GetPoint()->GetNode().FindSectionNode()->GetSection().GetType()
== SectionType::ToxContent) // this checks it's not a heading
{
// Destination Rectangle
const SwRect& rDestRect = mrSh.GetCharRect();
const SwPageFrame* pCurrPage =
static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
// Destination PageNum
const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
// Destination Export
if ( -1 != nDestPageNum )
{
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
const OUString* vNodeText = &static_cast<const SwTextNode*>(&rCurrentNode)->GetText();
vDestinations.emplace_back(pIteratedTOX, vNodeText, nDestId);
}
}
}
if (!mrSh.MovePara(GoNextPara, fnParaStart))
{ // Cursor is stuck in the TOX due to document ending immediately afterwards
break;
}
}
}
}
// Generate links to matching entries in the bibliography tables
std::vector<SwFormatField*> aFields;
SwFieldType* pType = mrSh.GetFieldType(SwFieldIds::TableOfAuthorities, OUString());
if (!pType)
{
return;
}
pType->GatherFields(aFields);
const auto pPageFrame = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower());
for (const auto pFormatField : aFields)
{
if (!pFormatField->GetTextField() || !pFormatField->IsFieldInDoc())
{
continue;
}
const auto& rAuthorityField
= *static_cast<const SwAuthorityField*>(pFormatField->GetField());
if (auto targetType = rAuthorityField.GetTargetType();
targetType == SwAuthorityField::TargetType::UseDisplayURL
|| targetType == SwAuthorityField::TargetType::UseTargetURL)
{
// Since the target type specifies to use an URL, link to it
const OUString& rURL = rAuthorityField.GetAbsoluteURL();
if (rURL.getLength() == 0)
{
continue;
}
const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode();
if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField))
{
continue;
}
OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout()));
// Select the field.
mrSh.SwCursorShell::SetMark();
mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars);
// Create the links.
SwRects const rects(GetCursorRectsContainingText(mrSh));
for (const auto& rLinkRect : rects)
{
for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect))
{
tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect()));
sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum);
IdMapEntry aLinkEntry(rLinkRect, nLinkId);
pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
pPDFExtOutDevData->SetLinkURL(nLinkId, rURL);
}
}
mrSh.SwCursorShell::ClearMark();
}
else if (targetType == SwAuthorityField::TargetType::BibliographyTableRow)
{
// As the target type specifies, try linking to a bibliography table row
sal_Int32 nDestId = -1;
std::unordered_map<const SwTOXBase*, OUString> vFormattedFieldStrings;
for (const auto& rDestinationTuple : vDestinations)
{
if (vFormattedFieldStrings.find(std::get<0>(rDestinationTuple))
== vFormattedFieldStrings.end())
vFormattedFieldStrings.emplace(std::get<0>(rDestinationTuple),
rAuthorityField.GetAuthority(mrSh.GetLayout(),
&std::get<0>(rDestinationTuple)->GetTOXForm()));
if (vFormattedFieldStrings.at(std::get<0>(rDestinationTuple)) == *std::get<1>(rDestinationTuple))
{
nDestId = std::get<2>(rDestinationTuple);
break;
}
}
if (nDestId == -1)
continue;
const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode();
if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField))
{
continue;
}
OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout()));
// Select the field.
mrSh.SwCursorShell::SetMark();
mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars);
// Create the links.
SwRects const rects(GetCursorRectsContainingText(mrSh));
for (const auto& rLinkRect : rects)
{
for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect))
{
tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect()));
sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum);
IdMapEntry aLinkEntry(rLinkRect, nLinkId);
pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
pPDFExtOutDevData->SetLinkDest(nLinkId, nDestId);
}
}
mrSh.SwCursorShell::ClearMark();
}
}
}
// Returns the page number in the output pdf on which the given rect is located.
// If this page is duplicated, method will return first occurrence of it.
sal_Int32 SwEnhancedPDFExportHelper::CalcOutputPageNum( const SwRect& rRect ) const
{
std::vector< sal_Int32 > aPageNums = CalcOutputPageNums( rRect );
if ( !aPageNums.empty() )
return aPageNums[0];
return -1;
}
// Returns a vector of the page numbers in the output pdf on which the given
// rect is located. There can be many such pages since StringRangeEnumerator
// allows duplication of its entries.
std::vector< sal_Int32 > SwEnhancedPDFExportHelper::CalcOutputPageNums(
const SwRect& rRect ) const
{
std::vector< sal_Int32 > aPageNums;
// Document page number.
sal_Int32 nPageNumOfRect = mrSh.GetPageNumAndSetOffsetForPDF( mrOut, rRect );
if ( nPageNumOfRect < 0 )
return aPageNums;
// What will be the page numbers of page nPageNumOfRect in the output pdf?
if ( mpRangeEnum )
{
if ( mbSkipEmptyPages )
// Map the page number to the range without empty pages.
nPageNumOfRect = maPageNumberMap[ nPageNumOfRect ];
if ( mpRangeEnum->hasValue( nPageNumOfRect ) )
{
sal_Int32 nOutputPageNum = 0;
StringRangeEnumerator::Iterator aIter = mpRangeEnum->begin();
StringRangeEnumerator::Iterator aEnd = mpRangeEnum->end();
for ( ; aIter != aEnd; ++aIter )
{
if ( *aIter == nPageNumOfRect )
aPageNums.push_back( nOutputPageNum );
++nOutputPageNum;
}
}
}
else
{
if ( mbSkipEmptyPages )
{
sal_Int32 nOutputPageNum = 0;
for ( size_t i = 0; i < maPageNumberMap.size(); ++i )
{
if ( maPageNumberMap[i] >= 0 ) // is not empty?
{
if ( i == static_cast<size_t>( nPageNumOfRect ) )
{
aPageNums.push_back( nOutputPageNum );
break;
}
++nOutputPageNum;
}
}
}
else
aPageNums.push_back( nPageNumOfRect );
}
return aPageNums;
}
void SwEnhancedPDFExportHelper::MakeHeaderFooterLinks( vcl::PDFExtOutDevData& rPDFExtOutDevData,
const SwTextNode& rTNd,
const SwRect& rLinkRect,
sal_Int32 nDestId,
const OUString& rURL,
bool bInternal,
OUString const& rContent) const
{
// We assume, that the primary link has just been exported. Therefore
// the offset of the link rectangle calculates as follows:
const Point aOffset = rLinkRect.Pos() + mrOut.GetMapMode().GetOrigin();
SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rTNd);
for ( SwTextFrame* pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() )
{
// Add offset to current page:
const SwPageFrame* pPageFrame = pTmpFrame->FindPageFrame();
SwRect aHFLinkRect( rLinkRect );
aHFLinkRect.Pos() = pPageFrame->getFrameArea().Pos() + aOffset;
// #i97135# the gcc_x64 optimizer gets aHFLinkRect != rLinkRect wrong
// fool it by comparing the position only (the width and height are the
// same anyway)
if ( aHFLinkRect.Pos() != rLinkRect.Pos() )
{
// Link PageNums
std::vector<sal_Int32> aHFLinkPageNums = CalcOutputPageNums( aHFLinkRect );
for (sal_Int32 aHFLinkPageNum : aHFLinkPageNums)
{
// Link Export
tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, aHFLinkRect.SVRect()));
const sal_Int32 nHFLinkId =
rPDFExtOutDevData.CreateLink(aRect, rContent, aHFLinkPageNum);
// Connect Link and Destination:
if ( bInternal )
rPDFExtOutDevData.SetLinkDest( nHFLinkId, nDestId );
else
rPDFExtOutDevData.SetLinkURL( nHFLinkId, rURL );
}
}
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */