android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / core / text / EnhancedPDFExportHelper.cxx
blob201ff3c604a18857f3d3e06a1a976365e4862ad1
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <EnhancedPDFExportHelper.hxx>
22 #include <com/sun/star/embed/XEmbeddedObject.hpp>
23 #include <com/sun/star/i18n/ScriptType.hpp>
24 #include <com/sun/star/drawing/XShape.hpp>
25 #include <com/sun/star/beans/XPropertySet.hpp>
26 #include <hintids.hxx>
28 #include <sot/exchange.hxx>
29 #include <vcl/outdev.hxx>
30 #include <vcl/pdfextoutdevdata.hxx>
31 #include <tools/multisel.hxx>
32 #include <editeng/adjustitem.hxx>
33 #include <editeng/lrspitem.hxx>
34 #include <editeng/langitem.hxx>
35 #include <tools/urlobj.hxx>
36 #include <svl/languageoptions.hxx>
37 #include <svl/numformat.hxx>
38 #include <svl/zforlist.hxx>
39 #include <swatrset.hxx>
40 #include <frmatr.hxx>
41 #include <paratr.hxx>
42 #include <ndtxt.hxx>
43 #include <ndole.hxx>
44 #include <section.hxx>
45 #include <tox.hxx>
46 #include <fmtfld.hxx>
47 #include <txtinet.hxx>
48 #include <fmtinfmt.hxx>
49 #include <fchrfmt.hxx>
50 #include <charfmt.hxx>
51 #include <fmtanchr.hxx>
52 #include <fmturl.hxx>
53 #include <editsh.hxx>
54 #include <viscrs.hxx>
55 #include <txtfld.hxx>
56 #include <reffld.hxx>
57 #include <doc.hxx>
58 #include <IDocumentOutlineNodes.hxx>
59 #include <mdiexp.hxx>
60 #include <docufld.hxx>
61 #include <ftnidx.hxx>
62 #include <txtftn.hxx>
63 #include <rootfrm.hxx>
64 #include <pagefrm.hxx>
65 #include <txtfrm.hxx>
66 #include <tabfrm.hxx>
67 #include <rowfrm.hxx>
68 #include <cellfrm.hxx>
69 #include <sectfrm.hxx>
70 #include <ftnfrm.hxx>
71 #include <flyfrm.hxx>
72 #include <notxtfrm.hxx>
73 #include "porfld.hxx"
74 #include <SwStyleNameMapper.hxx>
75 #include "itrpaint.hxx"
76 #include <i18nlangtag/languagetag.hxx>
77 #include <IMark.hxx>
78 #include <printdata.hxx>
79 #include <vprint.hxx>
80 #include <SwNodeNum.hxx>
81 #include <calbck.hxx>
82 #include <frmtool.hxx>
83 #include <strings.hrc>
84 #include <frameformats.hxx>
85 #include <tblafmt.hxx>
86 #include <authfld.hxx>
87 #include <dcontact.hxx>
89 #include <tools/globname.hxx>
90 #include <svx/svdobj.hxx>
92 #include <stack>
93 #include <map>
94 #include <set>
95 #include <optional>
97 using namespace ::com::sun::star;
99 #if OSL_DEBUG_LEVEL > 1
101 static std::vector< sal_uInt16 > aStructStack;
103 void lcl_DBGCheckStack()
105 /* NonStructElement = 0 Document = 1 Part = 2
106 * Article = 3 Section = 4 Division = 5
107 * BlockQuote = 6 Caption = 7 TOC = 8
108 * TOCI = 9 Index = 10 Paragraph = 11
109 * Heading = 12 H1-6 = 13 - 18 List = 19
110 * ListItem = 20 LILabel = 21 LIBody = 22
111 * Table = 23 TableRow = 24 TableHeader = 25
112 * TableData = 26 Span = 27 Quote = 28
113 * Note = 29 Reference = 30 BibEntry = 31
114 * Code = 32 Link = 33 Figure = 34
115 * Formula = 35 Form = 36 Continued frame = 99
118 sal_uInt16 nElement;
119 for ( const auto& rItem : aStructStack )
121 nElement = rItem;
123 (void)nElement;
126 #endif
128 typedef std::set< tools::Long, lt_TableColumn > TableColumnsMapEntry;
129 typedef std::pair< SwRect, sal_Int32 > IdMapEntry;
130 typedef std::vector< IdMapEntry > LinkIdMap;
131 typedef std::map< const SwTable*, TableColumnsMapEntry > TableColumnsMap;
132 typedef std::map< const SwNumberTreeNode*, sal_Int32 > NumListIdMap;
133 typedef std::map< const SwNumberTreeNode*, sal_Int32 > NumListBodyIdMap;
134 typedef std::set<const void*> FrameTagSet;
136 struct SwEnhancedPDFState
138 TableColumnsMap m_TableColumnsMap;
139 LinkIdMap m_LinkIdMap;
140 NumListIdMap m_NumListIdMap;
141 NumListBodyIdMap m_NumListBodyIdMap;
142 FrameTagSet m_FrameTagSet;
144 LanguageType m_eLanguageDefault;
146 struct Span
148 FontLineStyle eUnderline;
149 FontLineStyle eOverline;
150 FontStrikeout eStrikeout;
151 FontEmphasisMark eFontEmphasis;
152 short nEscapement;
153 SwFontScript nScript;
154 LanguageType nLang;
155 OUString StyleName;
158 ::std::optional<Span> m_oCurrentSpan;
159 ::std::optional<SwTextAttr const*> m_oCurrentLink;
161 SwEnhancedPDFState(LanguageType const eLanguageDefault)
162 : m_eLanguageDefault(eLanguageDefault)
167 namespace
169 // ODF Style Names:
170 const char aTableHeadingName[] = "Table Heading";
171 const char aQuotations[] = "Quotations";
172 const char aCaption[] = "Caption";
173 const char aHeading[] = "Heading";
174 const char aQuotation[] = "Quotation";
175 const char aSourceText[] = "Source Text";
177 // PDF Tag Names:
178 constexpr OUStringLiteral aDocumentString = u"Document";
179 constexpr OUStringLiteral aDivString = u"Div";
180 constexpr OUStringLiteral aSectString = u"Sect";
181 constexpr OUStringLiteral aHString = u"H";
182 constexpr OUStringLiteral aH1String = u"H1";
183 constexpr OUStringLiteral aH2String = u"H2";
184 constexpr OUStringLiteral aH3String = u"H3";
185 constexpr OUStringLiteral aH4String = u"H4";
186 constexpr OUStringLiteral aH5String = u"H5";
187 constexpr OUStringLiteral aH6String = u"H6";
188 constexpr OUStringLiteral aH7String = u"H7";
189 constexpr OUStringLiteral aH8String = u"H8";
190 constexpr OUStringLiteral aH9String = u"H9";
191 constexpr OUStringLiteral aH10String = u"H10";
192 constexpr OUStringLiteral aListString = u"L";
193 constexpr OUStringLiteral aListItemString = u"LI";
194 constexpr OUStringLiteral aListLabelString = u"Lbl";
195 constexpr OUStringLiteral aListBodyString = u"LBody";
196 constexpr OUStringLiteral aBlockQuoteString = u"BlockQuote";
197 constexpr OUStringLiteral aCaptionString = u"Caption";
198 constexpr OUStringLiteral aIndexString = u"Index";
199 constexpr OUStringLiteral aTOCString = u"TOC";
200 constexpr OUStringLiteral aTOCIString = u"TOCI";
201 constexpr OUStringLiteral aTableString = u"Table";
202 constexpr OUStringLiteral aTRString = u"TR";
203 constexpr OUStringLiteral aTDString = u"TD";
204 constexpr OUStringLiteral aTHString = u"TH";
205 constexpr OUStringLiteral aBibEntryString = u"BibEntry";
206 constexpr OUStringLiteral aQuoteString = u"Quote";
207 constexpr OUStringLiteral aSpanString = u"Span";
208 constexpr OUStringLiteral aCodeString = u"Code";
209 constexpr OUStringLiteral aFigureString = u"Figure";
210 constexpr OUStringLiteral aFormulaString = u"Formula";
211 constexpr OUStringLiteral aLinkString = u"Link";
212 constexpr OUStringLiteral aNoteString = u"Note";
214 // returns true if first paragraph in cell frame has 'table heading' style
215 bool lcl_IsHeadlineCell( const SwCellFrame& rCellFrame )
217 bool bRet = false;
219 const SwContentFrame *pCnt = rCellFrame.ContainsContent();
220 if ( pCnt && pCnt->IsTextFrame() )
222 SwTextNode const*const pTextNode = static_cast<const SwTextFrame*>(pCnt)->GetTextNodeForParaProps();
223 const SwFormat* pTextFormat = pTextNode->GetFormatColl();
225 OUString sStyleName;
226 SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
227 bRet = sStyleName == aTableHeadingName;
230 // tdf#153935 wild guessing for 1st row based on table autoformat
231 if (!bRet && !rCellFrame.GetUpper()->GetPrev())
233 SwTable const*const pTable(rCellFrame.FindTabFrame()->GetTable());
234 assert(pTable);
235 OUString const& rStyleName(pTable->GetTableStyleName());
236 if (!rStyleName.isEmpty())
238 if (SwTableAutoFormat const*const pTableAF =
239 pTable->GetFrameFormat()->GetDoc()->GetTableStyles().FindAutoFormat(rStyleName))
241 bRet |= pTableAF->HasHeaderRow();
246 return bRet;
249 // List all frames for which the NonStructElement tag is set:
250 bool lcl_IsInNonStructEnv( const SwFrame& rFrame )
252 bool bRet = false;
254 if ( nullptr != rFrame.FindFooterOrHeader() &&
255 !rFrame.IsHeaderFrame() && !rFrame.IsFooterFrame() )
257 bRet = true;
259 else if ( rFrame.IsInTab() && !rFrame.IsTabFrame() )
261 const SwTabFrame* pTabFrame = rFrame.FindTabFrame();
262 if ( rFrame.GetUpper() != pTabFrame &&
263 pTabFrame->IsFollow() && pTabFrame->IsInHeadline( rFrame ) )
264 bRet = true;
267 return bRet;
270 // Generate key from frame for reopening tags:
271 void const* lcl_GetKeyFromFrame( const SwFrame& rFrame )
273 void const* pKey = nullptr;
275 if ( rFrame.IsPageFrame() )
276 pKey = static_cast<void const *>(&(static_cast<const SwPageFrame&>(rFrame).GetFormat()->getIDocumentSettingAccess()));
277 else if ( rFrame.IsTextFrame() )
278 pKey = static_cast<void const *>(static_cast<const SwTextFrame&>(rFrame).GetTextNodeFirst());
279 else if ( rFrame.IsSctFrame() )
280 pKey = static_cast<void const *>(static_cast<const SwSectionFrame&>(rFrame).GetSection());
281 else if ( rFrame.IsTabFrame() )
282 pKey = static_cast<void const *>(static_cast<const SwTabFrame&>(rFrame).GetTable());
283 else if ( rFrame.IsRowFrame() )
284 pKey = static_cast<void const *>(static_cast<const SwRowFrame&>(rFrame).GetTabLine());
285 else if ( rFrame.IsCellFrame() )
287 const SwTabFrame* pTabFrame = rFrame.FindTabFrame();
288 const SwTable* pTable = pTabFrame->GetTable();
289 pKey = static_cast<void const *>(& static_cast<const SwCellFrame&>(rFrame).GetTabBox()->FindStartOfRowSpan(*pTable));
291 else if (rFrame.IsFootnoteFrame())
293 pKey = static_cast<void const*>(static_cast<SwFootnoteFrame const&>(rFrame).GetAttr());
296 return pKey;
299 bool lcl_HasPreviousParaSameNumRule(SwTextFrame const& rTextFrame, const SwTextNode& rNode)
301 bool bRet = false;
302 SwNodeIndex aIdx( rNode );
303 const SwDoc& rDoc = rNode.GetDoc();
304 const SwNodes& rNodes = rDoc.GetNodes();
305 const SwNode* pNode = &rNode;
306 const SwNumRule* pNumRule = rNode.GetNumRule();
308 while (pNode != rNodes.DocumentSectionStartNode(const_cast<SwNode*>(static_cast<SwNode const *>(&rNode))) )
310 sw::GotoPrevLayoutTextFrame(aIdx, rTextFrame.getRootFrame());
312 if (aIdx.GetNode().IsTextNode())
314 const SwTextNode *const pPrevTextNd = sw::GetParaPropsNode(
315 *rTextFrame.getRootFrame(), *aIdx.GetNode().GetTextNode());
316 const SwNumRule * pPrevNumRule = pPrevTextNd->GetNumRule();
318 // We find the previous text node. Now check, if the previous text node
319 // has the same numrule like rNode:
320 if ( (pPrevNumRule == pNumRule) &&
321 (!pPrevTextNd->IsOutline() == !rNode.IsOutline()))
322 bRet = true;
324 break;
327 pNode = &aIdx.GetNode();
329 return bRet;
332 bool lcl_TryMoveToNonHiddenField(SwEditShell& rShell, const SwTextNode& rNd, const SwFormatField& rField)
334 // 1. Check if the whole paragraph is hidden
335 // 2. Move to the field
336 // 3. Check for hidden text attribute
337 if(rNd.IsHidden())
338 return false;
339 if(!rShell.GotoFormatField(rField) || rShell.IsInHiddenRange(/*bSelect=*/false))
341 rShell.SwCursorShell::ClearMark();
342 return false;
344 return true;
347 } // end namespace
349 SwTaggedPDFHelper::SwTaggedPDFHelper( const Num_Info* pNumInfo,
350 const Frame_Info* pFrameInfo,
351 const Por_Info* pPorInfo,
352 OutputDevice const & rOut )
353 : m_nEndStructureElement( 0 ),
354 m_nRestoreCurrentTag( -1 ),
355 mpNumInfo( pNumInfo ),
356 mpFrameInfo( pFrameInfo ),
357 mpPorInfo( pPorInfo )
359 mpPDFExtOutDevData =
360 dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() );
362 if ( !(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()) )
363 return;
365 #if OSL_DEBUG_LEVEL > 1
366 sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
367 lcl_DBGCheckStack();
368 #endif
369 if ( mpNumInfo )
370 BeginNumberedListStructureElements();
371 else if ( mpFrameInfo )
372 BeginBlockStructureElements();
373 else if ( mpPorInfo )
374 BeginInlineStructureElements();
375 else
376 BeginTag( vcl::PDFWriter::NonStructElement, OUString() );
378 #if OSL_DEBUG_LEVEL > 1
379 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
380 lcl_DBGCheckStack();
381 (void)nCurrentStruct;
382 #endif
385 SwTaggedPDFHelper::~SwTaggedPDFHelper()
387 if ( !(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()) )
388 return;
390 #if OSL_DEBUG_LEVEL > 1
391 sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
392 lcl_DBGCheckStack();
393 #endif
394 EndStructureElements();
396 #if OSL_DEBUG_LEVEL > 1
397 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
398 lcl_DBGCheckStack();
399 (void)nCurrentStruct;
400 #endif
403 void const* SwDrawContact::GetPDFAnchorStructureElementKey(SdrObject const& rObj)
405 SwFrame const*const pAnchorFrame(GetAnchoredObj(&rObj)->GetAnchorFrame());
406 return pAnchorFrame ? lcl_GetKeyFromFrame(*pAnchorFrame) : nullptr;
409 bool SwTaggedPDFHelper::CheckReopenTag()
411 bool bRet = false;
412 void const* pReopenKey(nullptr);
413 bool bContinue = false; // in some cases we just have to reopen a tag without early returning
415 if ( mpFrameInfo )
417 const SwFrame& rFrame = mpFrameInfo->mrFrame;
418 const SwFrame* pKeyFrame = nullptr;
420 // Reopen an existing structure element if
421 // - rFrame is not the first page frame (reopen Document tag)
422 // - rFrame is a follow frame (reopen Master tag)
423 // - rFrame is a fly frame anchored at content (reopen Anchor paragraph tag)
424 // - rFrame is a fly frame anchored at page (reopen Document tag)
425 // - rFrame is a follow flow row (reopen TableRow tag)
426 // - rFrame is a cell frame in a follow flow row (reopen TableData tag)
427 if ( ( rFrame.IsPageFrame() && static_cast<const SwPageFrame&>(rFrame).GetPrev() ) ||
428 ( rFrame.IsFlowFrame() && SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() ) ||
429 (rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetMaster()) ||
430 ( rFrame.IsRowFrame() && rFrame.IsInFollowFlowRow() ) ||
431 ( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetPrevCellLeaf() ) )
433 pKeyFrame = &rFrame;
435 else if (rFrame.IsFlyFrame() && !mpFrameInfo->m_isLink)
437 const SwFormatAnchor& rAnchor =
438 static_cast<const SwFlyFrame*>(&rFrame)->GetFormat()->GetAnchor();
439 if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) ||
440 (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) ||
441 (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()))
443 pKeyFrame = static_cast<const SwFlyFrame&>(rFrame).GetAnchorFrame();
444 bContinue = true;
448 if ( pKeyFrame )
450 void const*const pKey = lcl_GetKeyFromFrame(*pKeyFrame);
451 FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
452 if (rFrameTagSet.find(pKey) != rFrameTagSet.end()
453 || rFrame.IsFlyFrame()) // for hell layer flys
455 pReopenKey = pKey;
460 if (pReopenKey)
462 // note: it would be possible to get rid of the SetCurrentStructureElement()
463 // - which is quite ugly - for most cases by recreating the parents until the
464 // current ancestor, but there are special cases cell frame rowspan > 1 follow
465 // and footnote frame follow where the parent of the follow is different from
466 // the parent of the first one, and so in PDFExtOutDevData the wrong parent
467 // would be restored and used for next elements.
468 m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement();
469 sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pReopenKey);
470 mpPDFExtOutDevData->SetCurrentStructureElement(id);
472 bRet = true;
475 return bRet && !bContinue;
478 void SwTaggedPDFHelper::CheckRestoreTag() const
480 if ( m_nRestoreCurrentTag != -1 )
482 const bool bSuccess = mpPDFExtOutDevData->SetCurrentStructureElement( m_nRestoreCurrentTag );
483 OSL_ENSURE( bSuccess, "Failed to restore reopened tag" );
485 #if OSL_DEBUG_LEVEL > 1
486 aStructStack.pop_back();
487 #endif
491 void SwTaggedPDFHelper::OpenTagImpl(void const*const pKey)
493 sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pKey);
494 mpPDFExtOutDevData->BeginStructureElement(id);
495 ++m_nEndStructureElement;
497 #if OSL_DEBUG_LEVEL > 1
498 aStructStack.push_back( 99 );
499 #endif
502 sal_Int32 SwTaggedPDFHelper::BeginTagImpl(void const*const pKey,
503 vcl::PDFWriter::StructElement const eType, const OUString& rString)
505 // write new tag
506 const sal_Int32 nId = mpPDFExtOutDevData->EnsureStructureElement(pKey);
507 mpPDFExtOutDevData->InitStructureElement(nId, eType, rString);
508 mpPDFExtOutDevData->BeginStructureElement(nId);
509 ++m_nEndStructureElement;
511 #if OSL_DEBUG_LEVEL > 1
512 aStructStack.push_back( o3tl::narrowing<sal_uInt16>(eType) );
513 #endif
515 return nId;
518 void SwTaggedPDFHelper::BeginTag( vcl::PDFWriter::StructElement eType, const OUString& rString )
520 void const* pKey(nullptr);
522 if (mpFrameInfo)
524 const SwFrame& rFrame = mpFrameInfo->mrFrame;
526 if ( ( rFrame.IsPageFrame() && !static_cast<const SwPageFrame&>(rFrame).GetPrev() ) ||
527 ( rFrame.IsFlowFrame() && !SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() && SwFlowFrame::CastFlowFrame(&rFrame)->HasFollow() ) ||
528 rFrame.IsSctFrame() || // all of them, so that opening parent sections works
529 ( rFrame.IsTextFrame() && rFrame.GetDrawObjs() ) ||
530 (rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetFollow()) ||
531 ( rFrame.IsRowFrame() && rFrame.IsInSplitTableRow() ) ||
532 ( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetNextCellLeaf() ) )
534 pKey = lcl_GetKeyFromFrame(rFrame);
536 if (pKey)
538 FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
539 assert(rFrameTagSet.find(pKey) == rFrameTagSet.end());
540 rFrameTagSet.emplace(pKey);
545 sal_Int32 const nId = BeginTagImpl(pKey, eType, rString);
547 // Store the id of the current structure element if
548 // - it is a list structure element
549 // - it is a list body element with children
550 // - rFrame is the first page frame
551 // - rFrame is a master frame
552 // - rFrame has objects anchored to it
553 // - rFrame is a row frame or cell frame in a split table row
555 if ( mpNumInfo )
557 const SwTextFrame& rTextFrame = static_cast<const SwTextFrame&>(mpNumInfo->mrFrame);
558 SwTextNode const*const pTextNd = rTextFrame.GetTextNodeForParaProps();
559 const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame());
561 if ( vcl::PDFWriter::List == eType )
563 NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
564 rNumListIdMap[ pNodeNum ] = nId;
566 else if ( vcl::PDFWriter::LIBody == eType )
568 NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
569 rNumListBodyIdMap[ pNodeNum ] = nId;
573 SetAttributes( eType );
576 void SwTaggedPDFHelper::EndTag()
578 mpPDFExtOutDevData->EndStructureElement();
580 #if OSL_DEBUG_LEVEL > 1
581 aStructStack.pop_back();
582 #endif
585 namespace {
587 // link the link annotation to the link structured element
588 void LinkLinkLink(vcl::PDFExtOutDevData & rPDFExtOutDevData, SwRect const& rRect)
590 const LinkIdMap& rLinkIdMap(rPDFExtOutDevData.GetSwPDFState()->m_LinkIdMap);
591 const Point aCenter = rRect.Center();
592 auto aIter = std::find_if(rLinkIdMap.begin(), rLinkIdMap.end(),
593 [&aCenter](const IdMapEntry& rEntry) { return rEntry.first.Contains(aCenter); });
594 if (aIter != rLinkIdMap.end())
596 sal_Int32 nLinkId = (*aIter).second;
597 rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::PDFWriter::LinkAnnotation, nLinkId);
602 // Sets the attributes according to the structure type.
603 void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType )
605 sal_Int32 nVal;
608 * ATTRIBUTES FOR BLSE
610 if ( mpFrameInfo )
612 vcl::PDFWriter::StructAttributeValue eVal;
613 const SwFrame* pFrame = &mpFrameInfo->mrFrame;
614 SwRectFnSet aRectFnSet(pFrame);
616 bool bPlacement = false;
617 bool bWritingMode = false;
618 bool bSpaceBefore = false;
619 bool bSpaceAfter = false;
620 bool bStartIndent = false;
621 bool bEndIndent = false;
622 bool bTextIndent = false;
623 bool bTextAlign = false;
624 bool bWidth = false;
625 bool bHeight = false;
626 bool bBox = false;
627 bool bRowSpan = false;
628 bool bAltText = false;
630 // Check which attributes to set:
632 switch ( eType )
634 case vcl::PDFWriter::Document :
635 bWritingMode = true;
636 break;
638 case vcl::PDFWriter::Note:
639 bPlacement = true;
640 break;
642 case vcl::PDFWriter::Table :
643 bPlacement =
644 bWritingMode =
645 bSpaceBefore =
646 bSpaceAfter =
647 bStartIndent =
648 bEndIndent =
649 bWidth =
650 bHeight =
651 bBox = true;
652 break;
654 case vcl::PDFWriter::TableRow :
655 bPlacement =
656 bWritingMode = true;
657 break;
659 case vcl::PDFWriter::TableHeader :
660 mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope, vcl::PDFWriter::Column);
661 [[fallthrough]];
662 case vcl::PDFWriter::TableData :
663 bPlacement =
664 bWritingMode =
665 bWidth =
666 bHeight =
667 bRowSpan = true;
668 break;
670 case vcl::PDFWriter::Caption:
671 if (pFrame->IsSctFrame())
673 break;
675 [[fallthrough]];
676 case vcl::PDFWriter::H1 :
677 case vcl::PDFWriter::H2 :
678 case vcl::PDFWriter::H3 :
679 case vcl::PDFWriter::H4 :
680 case vcl::PDFWriter::H5 :
681 case vcl::PDFWriter::H6 :
682 case vcl::PDFWriter::Paragraph :
683 case vcl::PDFWriter::Heading :
684 case vcl::PDFWriter::BlockQuote :
686 bPlacement =
687 bWritingMode =
688 bSpaceBefore =
689 bSpaceAfter =
690 bStartIndent =
691 bEndIndent =
692 bTextIndent =
693 bTextAlign = true;
694 break;
696 case vcl::PDFWriter::Formula :
697 case vcl::PDFWriter::Figure :
698 bAltText =
699 bPlacement =
700 bWidth =
701 bHeight =
702 bBox = true;
703 break;
705 case vcl::PDFWriter::Division:
706 if (pFrame->IsFlyFrame()) // this can be something else too
708 bAltText = true;
709 bBox = true;
711 break;
713 case vcl::PDFWriter::NonStructElement:
714 if (pFrame->IsHeaderFrame() || pFrame->IsFooterFrame())
716 // ISO 14289-1:2014, Clause: 7.8
717 mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Type, vcl::PDFWriter::Pagination);
718 mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Subtype,
719 pFrame->IsHeaderFrame()
720 ? vcl::PDFWriter::Header
721 : vcl::PDFWriter::Footer);
723 break;
725 default :
726 break;
729 // Set the attributes:
731 if ( bPlacement )
733 eVal = vcl::PDFWriter::TableHeader == eType ||
734 vcl::PDFWriter::TableData == eType ?
735 vcl::PDFWriter::Inline :
736 vcl::PDFWriter::Block;
738 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::Placement, eVal );
741 if ( bWritingMode )
743 eVal = pFrame->IsVertical() ?
744 vcl::PDFWriter::TbRl :
745 pFrame->IsRightToLeft() ?
746 vcl::PDFWriter::RlTb :
747 vcl::PDFWriter::LrTb;
749 if ( vcl::PDFWriter::LrTb != eVal )
750 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::WritingMode, eVal );
753 if ( bSpaceBefore )
755 nVal = aRectFnSet.GetTopMargin(*pFrame);
756 if ( 0 != nVal )
757 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::SpaceBefore, nVal );
760 if ( bSpaceAfter )
762 nVal = aRectFnSet.GetBottomMargin(*pFrame);
763 if ( 0 != nVal )
764 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::SpaceAfter, nVal );
767 if ( bStartIndent )
769 nVal = aRectFnSet.GetLeftMargin(*pFrame);
770 if ( 0 != nVal )
771 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::StartIndent, nVal );
774 if ( bEndIndent )
776 nVal = aRectFnSet.GetRightMargin(*pFrame);
777 if ( 0 != nVal )
778 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::EndIndent, nVal );
781 if ( bTextIndent )
783 OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" );
784 const SvxFirstLineIndentItem& rFirstLine(
785 static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent());
786 nVal = rFirstLine.GetTextFirstLineOffset();
787 if ( 0 != nVal )
788 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::TextIndent, nVal );
791 if ( bTextAlign )
793 OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" );
794 const SwAttrSet& aSet = static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet();
795 const SvxAdjust nAdjust = aSet.GetAdjust().GetAdjust();
796 if ( SvxAdjust::Block == nAdjust || SvxAdjust::Center == nAdjust ||
797 ( (pFrame->IsRightToLeft() && SvxAdjust::Left == nAdjust) ||
798 (!pFrame->IsRightToLeft() && SvxAdjust::Right == nAdjust) ) )
800 eVal = SvxAdjust::Block == nAdjust ?
801 vcl::PDFWriter::Justify :
802 SvxAdjust::Center == nAdjust ?
803 vcl::PDFWriter::Center :
804 vcl::PDFWriter::End;
806 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextAlign, eVal );
810 // ISO 14289-1:2014, Clause: 7.3
811 // ISO 14289-1:2014, Clause: 7.7
812 // For images (but not embedded objects), an ObjectInfoPrimitive2D is
813 // created, but it's not evaluated by VclMetafileProcessor2D any more;
814 // that would require producing StructureTagPrimitive2D here but that
815 // looks impossible so instead duplicate the code that sets the Alt
816 // text here again.
817 if (bAltText)
819 SwFlyFrameFormat const& rFly(*static_cast<SwFlyFrame const*>(pFrame)->GetFormat());
820 OUString const sep(
821 (rFly.GetObjTitle().isEmpty() || rFly.GetObjDescription().isEmpty())
822 ? OUString() : OUString(" - "));
823 OUString const altText(rFly.GetObjTitle() + sep + rFly.GetObjDescription());
824 if (!altText.isEmpty())
826 mpPDFExtOutDevData->SetAlternateText(altText);
830 if ( bWidth )
832 nVal = aRectFnSet.GetWidth(pFrame->getFrameArea());
833 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Width, nVal );
836 if ( bHeight )
838 nVal = aRectFnSet.GetHeight(pFrame->getFrameArea());
839 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Height, nVal );
842 if ( bBox )
844 // BBox only for non-split tables:
845 if ( vcl::PDFWriter::Table != eType ||
846 ( pFrame->IsTabFrame() &&
847 !static_cast<const SwTabFrame*>(pFrame)->IsFollow() &&
848 !static_cast<const SwTabFrame*>(pFrame)->HasFollow() ) )
850 mpPDFExtOutDevData->SetStructureBoundingBox(pFrame->getFrameArea().SVRect());
854 if ( bRowSpan )
856 if ( pFrame->IsCellFrame() )
858 const SwCellFrame* pThisCell = static_cast<const SwCellFrame*>(pFrame);
859 nVal = pThisCell->GetTabBox()->getRowSpan();
860 if ( nVal > 1 )
861 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::RowSpan, nVal );
863 // calculate colspan:
864 const SwTabFrame* pTabFrame = pThisCell->FindTabFrame();
865 const SwTable* pTable = pTabFrame->GetTable();
867 SwRectFnSet fnRectX(pTabFrame);
869 const TableColumnsMapEntry& rCols(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap[pTable]);
871 const tools::Long nLeft = fnRectX.GetLeft(pThisCell->getFrameArea());
872 const tools::Long nRight = fnRectX.GetRight(pThisCell->getFrameArea());
873 const TableColumnsMapEntry::const_iterator aLeftIter = rCols.find( nLeft );
874 const TableColumnsMapEntry::const_iterator aRightIter = rCols.find( nRight );
876 OSL_ENSURE( aLeftIter != rCols.end() && aRightIter != rCols.end(), "Colspan trouble" );
877 if ( aLeftIter != rCols.end() && aRightIter != rCols.end() )
879 nVal = std::distance( aLeftIter, aRightIter );
880 if ( nVal > 1 )
881 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::ColSpan, nVal );
886 if (mpFrameInfo->m_isLink)
888 SwRect const aRect(mpFrameInfo->mrFrame.getFrameArea());
889 LinkLinkLink(*mpPDFExtOutDevData, aRect);
894 * ATTRIBUTES FOR ILSE
896 else if ( mpPorInfo )
898 const SwLinePortion* pPor = &mpPorInfo->mrPor;
899 const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo();
901 bool bActualText = false;
902 bool bBaselineShift = false;
903 bool bTextDecorationType = false;
904 bool bLinkAttribute = false;
905 bool bLanguage = false;
907 // Check which attributes to set:
909 switch ( eType )
911 case vcl::PDFWriter::Span :
912 case vcl::PDFWriter::Quote :
913 case vcl::PDFWriter::Code :
914 if( PortionType::HyphenStr == pPor->GetWhichPor() || PortionType::SoftHyphenStr == pPor->GetWhichPor() ||
915 PortionType::Hyphen == pPor->GetWhichPor() || PortionType::SoftHyphen == pPor->GetWhichPor() )
916 bActualText = true;
917 else
919 bBaselineShift =
920 bTextDecorationType =
921 bLanguage = true;
923 break;
925 case vcl::PDFWriter::Link :
926 bTextDecorationType =
927 bBaselineShift =
928 bLinkAttribute =
929 bLanguage = true;
930 break;
932 case vcl::PDFWriter::BibEntry :
933 bTextDecorationType =
934 bBaselineShift =
935 bLinkAttribute =
936 bLanguage = true;
937 break;
939 default:
940 break;
943 if ( bActualText )
945 OUString aActualText;
946 if (pPor->GetWhichPor() == PortionType::SoftHyphen || pPor->GetWhichPor() == PortionType::Hyphen)
947 aActualText = OUString(u'\x00ad'); // soft hyphen
948 else
949 aActualText = rInf.GetText().copy(sal_Int32(rInf.GetIdx()), sal_Int32(pPor->GetLen()));
950 mpPDFExtOutDevData->SetActualText( aActualText );
953 if ( bBaselineShift )
955 // TODO: Calculate correct values!
956 nVal = rInf.GetFont()->GetEscapement();
957 if ( nVal > 0 ) nVal = 33;
958 else if ( nVal < 0 ) nVal = -33;
960 if ( 0 != nVal )
962 nVal = nVal * pPor->Height() / 100;
963 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::BaselineShift, nVal );
967 if ( bTextDecorationType )
969 if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() )
970 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Underline );
971 if ( LINESTYLE_NONE != rInf.GetFont()->GetOverline() )
972 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Overline );
973 if ( STRIKEOUT_NONE != rInf.GetFont()->GetStrikeout() )
974 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::LineThrough );
975 if ( FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() )
976 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Overline );
979 if ( bLanguage )
982 const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage();
983 const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault);
985 if ( nDefaultLang != nCurrentLanguage )
986 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Language, static_cast<sal_uInt16>(nCurrentLanguage) );
989 if ( bLinkAttribute )
991 SwRect aPorRect;
992 rInf.CalcRect( *pPor, &aPorRect );
993 LinkLinkLink(*mpPDFExtOutDevData, aPorRect);
996 else if (mpNumInfo && eType == vcl::PDFWriter::List)
998 SwTextFrame const& rFrame(static_cast<SwTextFrame const&>(mpNumInfo->mrFrame));
999 SwTextNode const& rNode(*rFrame.GetTextNodeForParaProps());
1000 SwNumRule const*const pNumRule = rNode.GetNumRule();
1001 assert(pNumRule); // was required for List
1003 auto ToPDFListNumbering = [](SvxNumberFormat const& rFormat) {
1004 switch (rFormat.GetNumberingType())
1006 case css::style::NumberingType::CHARS_UPPER_LETTER:
1007 return vcl::PDFWriter::UpperAlpha;
1008 case css::style::NumberingType::CHARS_LOWER_LETTER:
1009 return vcl::PDFWriter::LowerAlpha;
1010 case css::style::NumberingType::ROMAN_UPPER:
1011 return vcl::PDFWriter::UpperRoman;
1012 case css::style::NumberingType::ROMAN_LOWER:
1013 return vcl::PDFWriter::LowerRoman;
1014 case css::style::NumberingType::ARABIC:
1015 return vcl::PDFWriter::Decimal;
1016 case css::style::NumberingType::CHAR_SPECIAL:
1017 switch (rFormat.GetBulletChar())
1019 case u'\u2022': case u'\uE12C': case u'\uE01E': case u'\uE437':
1020 return vcl::PDFWriter::Disc;
1021 case u'\u2218': case u'\u25CB': case u'\u25E6':
1022 return vcl::PDFWriter::Circle;
1023 case u'\u25A0': case u'\u25AA': case u'\uE00A':
1024 return vcl::PDFWriter::Square;
1025 default:
1026 return vcl::PDFWriter::NONE;
1028 default: // the other 50 types
1029 return vcl::PDFWriter::NONE;
1033 // Note: for every level, BeginNumberedListStructureElements() produces
1034 // a separate List element, so even though in PDF this is limited to
1035 // the whole List we can just export the current level here.
1036 vcl::PDFWriter::StructAttributeValue const value(
1037 ToPDFListNumbering(pNumRule->Get(rNode.GetActualListLevel())));
1038 // ISO 14289-1:2014, Clause: 7.6
1039 mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::ListNumbering, value);
1043 void SwTaggedPDFHelper::BeginNumberedListStructureElements()
1045 OSL_ENSURE( mpNumInfo, "List without mpNumInfo?" );
1046 if ( !mpNumInfo )
1047 return;
1049 const SwFrame& rFrame = mpNumInfo->mrFrame;
1050 assert(rFrame.IsTextFrame());
1051 const SwTextFrame& rTextFrame = static_cast<const SwTextFrame&>(rFrame);
1053 // Lowers of NonStructureElements should not be considered:
1054 if (lcl_IsInNonStructEnv(rTextFrame))
1055 return;
1057 // do it for the first one in the follow chain that has content
1058 for (SwFlowFrame const* pPrecede = rTextFrame.GetPrecede(); pPrecede; pPrecede = pPrecede->GetPrecede())
1060 SwTextFrame const*const pText(static_cast<SwTextFrame const*>(pPrecede));
1061 if (!pText->HasPara() || pText->GetPara()->HasContentPortions())
1063 return;
1067 const SwTextNode *const pTextNd = rTextFrame.GetTextNodeForParaProps();
1068 const SwNumRule* pNumRule = pTextNd->GetNumRule();
1069 const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame());
1071 const bool bNumbered = !pTextNd->IsOutline() && pNodeNum && pNodeNum->GetParent() && pNumRule;
1073 // Check, if we have to reopen a list or a list body:
1074 // First condition:
1075 // Paragraph is numbered/bulleted
1076 if ( !bNumbered )
1077 return;
1079 const SwNumberTreeNode* pParent = pNodeNum->GetParent();
1080 const bool bSameNumbering = lcl_HasPreviousParaSameNumRule(rTextFrame, *pTextNd);
1082 // Second condition: current numbering is not 'interrupted'
1083 if ( bSameNumbering )
1085 sal_Int32 nReopenTag = -1;
1087 // Two cases:
1088 // 1. We have to reopen an existing list body tag:
1089 // - If the current node is either the first child of its parent
1090 // and its level > 1 or
1091 // - Numbering should restart at the current node and its level > 1
1092 // - The current item has no label
1093 const bool bNewSubListStart = pParent->GetParent() && (pParent->IsFirst( pNodeNum ) || pTextNd->IsListRestart() );
1094 const bool bNoLabel = !pTextNd->IsCountedInList() && !pTextNd->IsListRestart();
1095 if ( bNewSubListStart || bNoLabel )
1097 // Fine, we try to reopen the appropriate list body
1098 NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
1100 if ( bNewSubListStart )
1102 // The list body tag associated with the parent has to be reopened
1103 // to start a new list inside the list body
1104 NumListBodyIdMap::const_iterator aIter;
1107 aIter = rNumListBodyIdMap.find( pParent );
1108 while ( aIter == rNumListBodyIdMap.end() && nullptr != ( pParent = pParent->GetParent() ) );
1110 if ( aIter != rNumListBodyIdMap.end() )
1111 nReopenTag = (*aIter).second;
1113 else // if(bNoLabel)
1115 // The list body tag of a 'counted' predecessor has to be reopened
1116 const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true);
1117 while ( pPrevious )
1119 if ( pPrevious->IsCounted())
1121 // get id of list body tag
1122 const NumListBodyIdMap::const_iterator aIter = rNumListBodyIdMap.find( pPrevious );
1123 if ( aIter != rNumListBodyIdMap.end() )
1125 nReopenTag = (*aIter).second;
1126 break;
1129 pPrevious = pPrevious->GetPred(true);
1133 // 2. We have to reopen an existing list tag:
1134 else if ( !pParent->IsFirst( pNodeNum ) && !pTextNd->IsListRestart() )
1136 // any other than the first node in a list level has to reopen the current
1137 // list. The current list is associated in a map with the first child of the list:
1138 NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
1140 // Search backwards and check if any of the previous nodes has a list associated with it:
1141 const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true);
1142 while ( pPrevious )
1144 // get id of list tag
1145 const NumListIdMap::const_iterator aIter = rNumListIdMap.find( pPrevious );
1146 if ( aIter != rNumListIdMap.end() )
1148 nReopenTag = (*aIter).second;
1149 break;
1152 pPrevious = pPrevious->GetPred(true);
1156 if ( -1 != nReopenTag )
1158 m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement();
1159 mpPDFExtOutDevData->SetCurrentStructureElement( nReopenTag );
1161 #if OSL_DEBUG_LEVEL > 1
1162 aStructStack.push_back( 99 );
1163 #endif
1166 else
1168 // clear list maps in case a list has been interrupted
1169 NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
1170 rNumListIdMap.clear();
1171 NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
1172 rNumListBodyIdMap.clear();
1175 // New tags:
1176 const bool bNewListTag = (pNodeNum->GetParent()->IsFirst( pNodeNum ) || pTextNd->IsListRestart() || !bSameNumbering);
1177 const bool bNewItemTag = bNewListTag || pTextNd->IsCountedInList(); // If the text node is not counted, we do not start a new list item:
1179 if ( bNewListTag )
1180 BeginTag( vcl::PDFWriter::List, aListString );
1182 if ( bNewItemTag )
1184 BeginTag( vcl::PDFWriter::ListItem, aListItemString );
1185 assert(rTextFrame.GetPara());
1186 // check whether to open LBody now or delay until after Lbl
1187 if (!rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering))
1189 BeginTag(vcl::PDFWriter::LIBody, aListBodyString);
1194 void SwTaggedPDFHelper::BeginBlockStructureElements()
1196 const SwFrame* pFrame = &mpFrameInfo->mrFrame;
1198 // Lowers of NonStructureElements should not be considered:
1200 if (lcl_IsInNonStructEnv(*pFrame) && !pFrame->IsFlyFrame())
1201 return;
1203 // Check if we have to reopen an existing structure element.
1204 // This has to be done e.g., if pFrame is a follow frame.
1205 if ( CheckReopenTag() )
1206 return;
1208 sal_uInt16 nPDFType = USHRT_MAX;
1209 OUString aPDFType;
1211 switch ( pFrame->GetType() )
1214 * GROUPING ELEMENTS
1217 case SwFrameType::Page :
1219 // Document: Document
1221 nPDFType = vcl::PDFWriter::Document;
1222 aPDFType = aDocumentString;
1223 break;
1225 case SwFrameType::Header :
1226 case SwFrameType::Footer :
1228 // Header, Footer: NonStructElement
1230 nPDFType = vcl::PDFWriter::NonStructElement;
1231 break;
1233 case SwFrameType::FtnCont :
1235 // Footnote container: Division
1237 nPDFType = vcl::PDFWriter::Division;
1238 aPDFType = aDivString;
1239 break;
1241 case SwFrameType::Ftn :
1243 // Footnote frame: Note
1245 // Note: vcl::PDFWriter::Note is actually a ILSE. Nevertheless
1246 // we treat it like a grouping element!
1247 nPDFType = vcl::PDFWriter::Note;
1248 aPDFType = aNoteString;
1249 break;
1251 case SwFrameType::Section :
1253 // Section: TOX, Index, or Sect
1256 const SwSection* pSection =
1257 static_cast<const SwSectionFrame*>(pFrame)->GetSection();
1259 // open all parent sections, so that the SEs of sections
1260 // are nested in the same way as their SwSectionNodes
1261 std::vector<SwSection const*> parents;
1262 for (SwSection const* pParent = pSection->GetParent();
1263 pParent != nullptr; pParent = pParent->GetParent())
1265 parents.push_back(pParent);
1267 for (auto it = parents.rbegin(); it != parents.rend(); ++it)
1269 // key is the SwSection - see lcl_GetKeyFromFrame()
1270 OpenTagImpl(*it);
1273 FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
1274 if (rFrameTagSet.find(pSection) != rFrameTagSet.end())
1276 // special case: section may have *multiple* master frames,
1277 // when it is interrupted by nested section - reopen!
1278 OpenTagImpl(pSection);
1279 break;
1281 else if (SectionType::ToxHeader == pSection->GetType())
1283 nPDFType = vcl::PDFWriter::Caption;
1284 aPDFType = aCaptionString;
1286 else if (SectionType::ToxContent == pSection->GetType())
1288 const SwTOXBase* pTOXBase = pSection->GetTOXBase();
1289 if ( pTOXBase )
1291 if ( TOX_INDEX == pTOXBase->GetType() )
1293 nPDFType = vcl::PDFWriter::Index;
1294 aPDFType = aIndexString;
1296 else
1298 nPDFType = vcl::PDFWriter::TOC;
1299 aPDFType = aTOCString;
1303 else if ( SectionType::Content == pSection->GetType() )
1305 nPDFType = vcl::PDFWriter::Section;
1306 aPDFType = aSectString;
1309 break;
1312 * BLOCK-LEVEL STRUCTURE ELEMENTS
1315 case SwFrameType::Txt :
1317 SwTextFrame const& rTextFrame(*static_cast<const SwTextFrame*>(pFrame));
1318 const SwTextNode *const pTextNd(rTextFrame.GetTextNodeForParaProps());
1320 // lazy open LBody after Lbl
1321 if (!pTextNd->IsOutline()
1322 && rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering))
1324 sal_Int32 const nId = BeginTagImpl(nullptr, vcl::PDFWriter::LIBody, aListBodyString);
1325 SwNodeNum const*const pNodeNum(pTextNd->GetNum(rTextFrame.getRootFrame()));
1326 NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
1327 rNumListBodyIdMap[ pNodeNum ] = nId;
1330 const SwFormat* pTextFormat = pTextNd->GetFormatColl();
1331 const SwFormat* pParentTextFormat = pTextFormat ? pTextFormat->DerivedFrom() : nullptr;
1333 OUString sStyleName;
1334 OUString sParentStyleName;
1336 if ( pTextFormat)
1337 SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
1338 if ( pParentTextFormat)
1339 SwStyleNameMapper::FillProgName( pParentTextFormat->GetName(), sParentStyleName, SwGetPoolIdFromName::TxtColl );
1341 // This is the default. If the paragraph could not be mapped to
1342 // any of the standard pdf tags, we write a user defined tag
1343 // <stylename> with role = P
1344 nPDFType = vcl::PDFWriter::Paragraph;
1345 aPDFType = sStyleName;
1347 // Quotations: BlockQuote
1349 if (sStyleName == aQuotations)
1351 nPDFType = vcl::PDFWriter::BlockQuote;
1352 aPDFType = aBlockQuoteString;
1355 // Caption: Caption
1357 else if (sStyleName == aCaption)
1359 nPDFType = vcl::PDFWriter::Caption;
1360 aPDFType = aCaptionString;
1363 // Caption: Caption
1365 else if (sParentStyleName == aCaption)
1367 nPDFType = vcl::PDFWriter::Caption;
1368 aPDFType = sStyleName + aCaptionString;
1371 // Heading: H
1373 else if (sStyleName == aHeading)
1375 nPDFType = vcl::PDFWriter::Heading;
1376 aPDFType = aHString;
1379 // Heading: H1 - H6
1381 if (int nRealLevel = pTextNd->GetAttrOutlineLevel() - 1;
1382 nRealLevel >= 0
1383 && !pTextNd->IsInRedlines()
1384 && sw::IsParaPropsNode(*pFrame->getRootFrame(), *pTextNd))
1386 switch(nRealLevel)
1388 case 0 :
1389 aPDFType = aH1String;
1390 break;
1391 case 1 :
1392 aPDFType = aH2String;
1393 break;
1394 case 2 :
1395 aPDFType = aH3String;
1396 break;
1397 case 3 :
1398 aPDFType = aH4String;
1399 break;
1400 case 4 :
1401 aPDFType = aH5String;
1402 break;
1403 case 5:
1404 aPDFType = aH6String;
1405 break;
1406 case 6:
1407 aPDFType = aH7String;
1408 break;
1409 case 7:
1410 aPDFType = aH8String;
1411 break;
1412 case 8:
1413 aPDFType = aH9String;
1414 break;
1415 case 9:
1416 aPDFType = aH10String;
1417 break;
1418 default:
1419 assert(false);
1420 break;
1423 // PDF/UA allows unlimited headings, but PDF only up to H6
1424 // ... and apparently the extra H7.. must be declared in
1425 // RoleMap, or veraPDF complains.
1426 nRealLevel = std::min(nRealLevel, 5);
1427 nPDFType = o3tl::narrowing<sal_uInt16>(vcl::PDFWriter::H1 + nRealLevel);
1430 // Section: TOCI
1432 else if ( pFrame->IsInSct() )
1434 const SwSectionFrame* pSctFrame = pFrame->FindSctFrame();
1435 const SwSection* pSection = pSctFrame->GetSection();
1437 if ( SectionType::ToxContent == pSection->GetType() )
1439 const SwTOXBase* pTOXBase = pSection->GetTOXBase();
1440 if ( pTOXBase && TOX_INDEX != pTOXBase->GetType() )
1442 // Special case: Open additional TOCI tag:
1443 BeginTagImpl(nullptr, vcl::PDFWriter::TOCI, aTOCIString);
1448 break;
1450 case SwFrameType::Tab :
1452 // TabFrame: Table
1454 nPDFType = vcl::PDFWriter::Table;
1455 aPDFType = aTableString;
1458 // set up table column data:
1459 const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pFrame);
1460 const SwTable* pTable = pTabFrame->GetTable();
1462 TableColumnsMap& rTableColumnsMap(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap);
1463 const TableColumnsMap::const_iterator aIter = rTableColumnsMap.find( pTable );
1465 if ( aIter == rTableColumnsMap.end() )
1467 SwRectFnSet aRectFnSet(pTabFrame);
1468 TableColumnsMapEntry& rCols = rTableColumnsMap[ pTable ];
1470 const SwTabFrame* pMasterFrame = pTabFrame->IsFollow() ? pTabFrame->FindMaster( true ) : pTabFrame;
1472 while ( pMasterFrame )
1474 const SwRowFrame* pRowFrame = static_cast<const SwRowFrame*>(pMasterFrame->GetLower());
1476 while ( pRowFrame )
1478 const SwFrame* pCellFrame = pRowFrame->GetLower();
1480 const tools::Long nLeft = aRectFnSet.GetLeft(pCellFrame->getFrameArea());
1481 rCols.insert( nLeft );
1483 while ( pCellFrame )
1485 const tools::Long nRight = aRectFnSet.GetRight(pCellFrame->getFrameArea());
1486 rCols.insert( nRight );
1487 pCellFrame = pCellFrame->GetNext();
1489 pRowFrame = static_cast<const SwRowFrame*>(pRowFrame->GetNext());
1491 pMasterFrame = pMasterFrame->GetFollow();
1496 break;
1499 * TABLE ELEMENTS
1502 case SwFrameType::Row :
1504 // RowFrame: TR
1506 if ( !static_cast<const SwRowFrame*>(pFrame)->IsRepeatedHeadline() )
1508 nPDFType = vcl::PDFWriter::TableRow;
1509 aPDFType = aTRString;
1511 else
1513 nPDFType = vcl::PDFWriter::NonStructElement;
1515 break;
1517 case SwFrameType::Cell :
1519 // CellFrame: TH, TD
1522 const SwTabFrame* pTable = static_cast<const SwCellFrame*>(pFrame)->FindTabFrame();
1523 if ( pTable->IsInHeadline( *pFrame ) || lcl_IsHeadlineCell( *static_cast<const SwCellFrame*>(pFrame) ) )
1525 nPDFType = vcl::PDFWriter::TableHeader;
1526 aPDFType = aTHString;
1528 else
1530 nPDFType = vcl::PDFWriter::TableData;
1531 aPDFType = aTDString;
1534 break;
1537 * ILLUSTRATION
1540 case SwFrameType::Fly :
1542 // FlyFrame: Figure, Formula, Control
1543 // fly in content or fly at page
1544 if (mpFrameInfo->m_isLink)
1545 { // tdf#154939 additional inner link element for flys
1546 nPDFType = vcl::PDFWriter::Link;
1547 aPDFType = aLinkString;
1549 else
1551 const SwFlyFrame* pFly = static_cast<const SwFlyFrame*>(pFrame);
1552 if (pFly->GetAnchorFrame()->FindFooterOrHeader() != nullptr
1553 || pFly->GetFrameFormat().GetAttrSet().Get(RES_DECORATIVE).GetValue())
1555 nPDFType = vcl::PDFWriter::NonStructElement;
1557 else if (pFly->Lower() && pFly->Lower()->IsNoTextFrame())
1559 bool bFormula = false;
1561 const SwNoTextFrame* pNoTextFrame = static_cast<const SwNoTextFrame*>(pFly->Lower());
1562 SwOLENode* pOLENd = const_cast<SwOLENode*>(pNoTextFrame->GetNode()->GetOLENode());
1563 if ( pOLENd )
1565 SwOLEObj& aOLEObj = pOLENd->GetOLEObj();
1566 uno::Reference< embed::XEmbeddedObject > aRef = aOLEObj.GetOleRef();
1567 if ( aRef.is() )
1569 bFormula = 0 != SotExchange::IsMath( SvGlobalName( aRef->getClassID() ) );
1572 if ( bFormula )
1574 nPDFType = vcl::PDFWriter::Formula;
1575 aPDFType = aFormulaString;
1577 else
1579 nPDFType = vcl::PDFWriter::Figure;
1580 aPDFType = aFigureString;
1583 else
1585 nPDFType = vcl::PDFWriter::Division;
1586 aPDFType = aDivString;
1589 break;
1591 default: break;
1594 if ( USHRT_MAX != nPDFType )
1596 BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType );
1600 void SwTaggedPDFHelper::EndStructureElements()
1602 if (mpFrameInfo != nullptr)
1604 if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
1605 { // close span at end of paragraph
1606 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
1607 ++m_nEndStructureElement;
1609 if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
1610 { // close link at end of paragraph
1611 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
1612 ++m_nEndStructureElement;
1616 while ( m_nEndStructureElement > 0 )
1618 EndTag();
1619 --m_nEndStructureElement;
1622 CheckRestoreTag();
1625 void SwTaggedPDFHelper::EndCurrentSpan()
1627 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
1628 EndTag(); // close span
1631 void SwTaggedPDFHelper::CreateCurrentSpan(
1632 SwTextPaintInfo const& rInf, OUString const& rStyleName)
1634 assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan);
1635 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.emplace(
1636 SwEnhancedPDFState::Span{
1637 rInf.GetFont()->GetUnderline(),
1638 rInf.GetFont()->GetOverline(),
1639 rInf.GetFont()->GetStrikeout(),
1640 rInf.GetFont()->GetEmphasisMark(),
1641 rInf.GetFont()->GetEscapement(),
1642 rInf.GetFont()->GetActual(),
1643 rInf.GetFont()->GetLanguage(),
1644 rStyleName});
1645 // leave it open to let next portion decide to merge or close
1646 --m_nEndStructureElement;
1649 bool SwTaggedPDFHelper::CheckContinueSpan(
1650 SwTextPaintInfo const& rInf, std::u16string_view const rStyleName,
1651 SwTextAttr const*const pInetFormatAttr)
1653 // for now, don't create span inside of link - this should be very rare
1654 // situation and it looks complicated to implement.
1655 assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan
1656 || !mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
1657 if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
1659 if (pInetFormatAttr && pInetFormatAttr == *mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
1661 return true;
1663 else
1665 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
1666 EndTag();
1667 return false;
1670 if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan && pInetFormatAttr)
1672 EndCurrentSpan();
1673 return false;
1676 if (!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
1677 return false;
1679 SwEnhancedPDFState::Span const& rCurrent(*mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan);
1681 bool const ret(rCurrent.eUnderline == rInf.GetFont()->GetUnderline()
1682 && rCurrent.eOverline == rInf.GetFont()->GetOverline()
1683 && rCurrent.eStrikeout == rInf.GetFont()->GetStrikeout()
1684 && rCurrent.eFontEmphasis == rInf.GetFont()->GetEmphasisMark()
1685 && rCurrent.nEscapement == rInf.GetFont()->GetEscapement()
1686 && rCurrent.nScript == rInf.GetFont()->GetActual()
1687 && rCurrent.nLang == rInf.GetFont()->GetLanguage()
1688 && rCurrent.StyleName == rStyleName);
1689 if (!ret)
1691 EndCurrentSpan();
1693 return ret;
1696 void SwTaggedPDFHelper::BeginInlineStructureElements()
1698 const SwLinePortion* pPor = &mpPorInfo->mrPor;
1699 const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo();
1700 const SwTextFrame* pFrame = rInf.GetTextFrame();
1702 // Lowers of NonStructureElements should not be considered:
1704 if ( lcl_IsInNonStructEnv( *pFrame ) )
1705 return;
1707 std::pair<SwTextNode const*, sal_Int32> const pos(
1708 pFrame->MapViewToModel(rInf.GetIdx()));
1709 SwTextAttr const*const pInetFormatAttr =
1710 pos.first->GetTextAttrAt(pos.second, RES_TXTATR_INETFMT);
1712 OUString sStyleName;
1713 if (!pInetFormatAttr)
1715 std::vector<SwTextAttr *> const charAttrs(
1716 pos.first->GetTextAttrsAt(pos.second, RES_TXTATR_CHARFMT));
1717 // TODO: handle more than 1 char style?
1718 const SwCharFormat* pCharFormat = (charAttrs.size())
1719 ? (*charAttrs.begin())->GetCharFormat().GetCharFormat() : nullptr;
1720 if (pCharFormat)
1721 SwStyleNameMapper::FillProgName( pCharFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
1724 // note: ILSE may be nested, so only end the span if needed to start new one
1725 bool const isContinueSpan(CheckContinueSpan(rInf, sStyleName, pInetFormatAttr));
1727 sal_uInt16 nPDFType = USHRT_MAX;
1728 OUString aPDFType;
1730 switch ( pPor->GetWhichPor() )
1732 case PortionType::Hyphen :
1733 case PortionType::SoftHyphen :
1734 // Check for alternative spelling:
1735 case PortionType::HyphenStr :
1736 case PortionType::SoftHyphenStr :
1737 nPDFType = vcl::PDFWriter::Span;
1738 aPDFType = aSpanString;
1739 break;
1741 case PortionType::Lay :
1742 case PortionType::Text :
1743 case PortionType::Para :
1745 // Check for Link:
1746 if( pInetFormatAttr )
1748 if (!isContinueSpan)
1750 nPDFType = vcl::PDFWriter::Link;
1751 aPDFType = aLinkString;
1752 assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
1753 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace(pInetFormatAttr);
1754 // leave it open to let next portion decide to merge or close
1755 --m_nEndStructureElement;
1758 // Check for Quote/Code character style:
1759 else if (sStyleName == aQuotation)
1761 if (!isContinueSpan)
1763 nPDFType = vcl::PDFWriter::Quote;
1764 aPDFType = aQuoteString;
1765 CreateCurrentSpan(rInf, sStyleName);
1768 else if (sStyleName == aSourceText)
1770 if (!isContinueSpan)
1772 nPDFType = vcl::PDFWriter::Code;
1773 aPDFType = aCodeString;
1774 CreateCurrentSpan(rInf, sStyleName);
1777 else if (!isContinueSpan)
1779 const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage();
1780 const SwFontScript nFont = rInf.GetFont()->GetActual();
1781 const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault);
1783 if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() ||
1784 LINESTYLE_NONE != rInf.GetFont()->GetOverline() ||
1785 STRIKEOUT_NONE != rInf.GetFont()->GetStrikeout() ||
1786 FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() ||
1787 0 != rInf.GetFont()->GetEscapement() ||
1788 SwFontScript::Latin != nFont ||
1789 nCurrentLanguage != nDefaultLang ||
1790 !sStyleName.isEmpty())
1792 nPDFType = vcl::PDFWriter::Span;
1793 if (!sStyleName.isEmpty())
1794 aPDFType = sStyleName;
1795 else
1796 aPDFType = aSpanString;
1797 CreateCurrentSpan(rInf, sStyleName);
1801 break;
1803 case PortionType::Footnote :
1804 nPDFType = vcl::PDFWriter::Link;
1805 aPDFType = aLinkString;
1806 break;
1808 case PortionType::Field :
1810 // check field type:
1811 TextFrameIndex const nIdx = static_cast<const SwFieldPortion*>(pPor)->IsFollow()
1812 ? rInf.GetIdx() - TextFrameIndex(1)
1813 : rInf.GetIdx();
1814 const SwTextAttr* pHint = mpPorInfo->mrTextPainter.GetAttr( nIdx );
1815 if ( pHint && RES_TXTATR_FIELD == pHint->Which() )
1817 const SwField* pField = pHint->GetFormatField().GetField();
1818 if ( SwFieldIds::GetRef == pField->Which() )
1820 nPDFType = vcl::PDFWriter::Link;
1821 aPDFType = aLinkString;
1823 else if ( SwFieldIds::TableOfAuthorities == pField->Which() )
1825 nPDFType = vcl::PDFWriter::BibEntry;
1826 aPDFType = aBibEntryString;
1830 break;
1832 // for FootnoteNum, is called twice: outer generates Lbl, inner Link
1833 case PortionType::FootnoteNum:
1834 assert(!isContinueSpan); // is at start
1835 if (!mpPorInfo->m_isNumberingLabel)
1836 { // tdf#152218 link both directions
1837 nPDFType = vcl::PDFWriter::Link;
1838 aPDFType = aLinkString;
1839 break;
1841 [[fallthrough]];
1842 case PortionType::Number:
1843 case PortionType::Bullet:
1844 case PortionType::GrfNum:
1845 assert(!isContinueSpan); // is at start
1846 if (mpPorInfo->m_isNumberingLabel)
1847 { // only works for multiple lines via wrapper from PaintSwFrame
1848 nPDFType = vcl::PDFWriter::LILabel;
1849 aPDFType = aListLabelString;
1851 break;
1853 case PortionType::Tab :
1854 case PortionType::TabRight :
1855 case PortionType::TabCenter :
1856 case PortionType::TabDecimal :
1857 nPDFType = vcl::PDFWriter::NonStructElement;
1858 break;
1859 default: break;
1862 if ( USHRT_MAX != nPDFType )
1864 BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType );
1868 bool SwTaggedPDFHelper::IsExportTaggedPDF( const OutputDevice& rOut )
1870 vcl::PDFExtOutDevData* pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() );
1871 return pPDFExtOutDevData && pPDFExtOutDevData->GetIsExportTaggedPDF();
1874 SwEnhancedPDFExportHelper::SwEnhancedPDFExportHelper( SwEditShell& rSh,
1875 OutputDevice& rOut,
1876 const OUString& rPageRange,
1877 bool bSkipEmptyPages,
1878 bool bEditEngineOnly,
1879 const SwPrintData& rPrintData )
1880 : mrSh( rSh ),
1881 mrOut( rOut ),
1882 mbSkipEmptyPages( bSkipEmptyPages ),
1883 mbEditEngineOnly( bEditEngineOnly ),
1884 mrPrintData( rPrintData )
1886 if ( !rPageRange.isEmpty() )
1887 mpRangeEnum.reset( new StringRangeEnumerator( rPageRange, 0, mrSh.GetPageCount()-1 ) );
1889 if ( mbSkipEmptyPages )
1891 maPageNumberMap.resize( mrSh.GetPageCount() );
1892 const SwPageFrame* pCurrPage =
1893 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
1894 sal_Int32 nPageNumber = 0;
1895 for ( size_t i = 0, n = maPageNumberMap.size(); i < n && pCurrPage; ++i )
1897 if ( pCurrPage->IsEmptyPage() )
1898 maPageNumberMap[i] = -1;
1899 else
1900 maPageNumberMap[i] = nPageNumber++;
1902 pCurrPage = static_cast<const SwPageFrame*>( pCurrPage->GetNext() );
1906 #if OSL_DEBUG_LEVEL > 1
1907 aStructStack.clear();
1908 #endif
1910 const sal_Int16 nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() );
1911 sal_uInt16 nLangRes = RES_CHRATR_LANGUAGE;
1913 if ( i18n::ScriptType::ASIAN == nScript )
1914 nLangRes = RES_CHRATR_CJK_LANGUAGE;
1915 else if ( i18n::ScriptType::COMPLEX == nScript )
1916 nLangRes = RES_CHRATR_CTL_LANGUAGE;
1918 auto const eLanguageDefault = static_cast<const SvxLanguageItem*>(&mrSh.GetDoc()->GetDefault( nLangRes ))->GetLanguage();
1920 EnhancedPDFExport(eLanguageDefault);
1923 SwEnhancedPDFExportHelper::~SwEnhancedPDFExportHelper()
1927 tools::Rectangle SwEnhancedPDFExportHelper::SwRectToPDFRect(const SwPageFrame* pCurrPage,
1928 const tools::Rectangle& rRectangle) const
1930 if (!::sw::IsShrinkPageForPostIts(mrSh, mrPrintData)) // tdf#148729
1932 return rRectangle;
1934 //the page has been scaled by 75% and vertically centered, so adjust these
1935 //rectangles equivalently
1936 tools::Rectangle aRect(rRectangle);
1937 Size aRectSize(aRect.GetSize());
1938 double fScale = 0.75;
1939 aRectSize.setWidth( aRectSize.Width() * fScale );
1940 aRectSize.setHeight( aRectSize.Height() * fScale );
1941 tools::Long nOrigHeight = pCurrPage->getFrameArea().Height();
1942 tools::Long nNewHeight = nOrigHeight*fScale;
1943 tools::Long nShiftY = (nOrigHeight-nNewHeight)/2;
1944 aRect.SetLeft( aRect.Left() * fScale );
1945 aRect.SetTop( aRect.Top() * fScale );
1946 aRect.Move(0, nShiftY);
1947 aRect.SetSize(aRectSize);
1948 return aRect;
1951 void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDefault)
1953 vcl::PDFExtOutDevData* pPDFExtOutDevData =
1954 dynamic_cast< vcl::PDFExtOutDevData*>( mrOut.GetExtOutDevData() );
1956 if ( !pPDFExtOutDevData )
1957 return;
1959 // set the document locale
1961 lang::Locale const aDocLocale( LanguageTag(eLanguageDefault).getLocale() );
1962 pPDFExtOutDevData->SetDocumentLocale( aDocLocale );
1964 // Prepare the output device:
1966 mrOut.Push( vcl::PushFlags::MAPMODE );
1967 MapMode aMapMode( mrOut.GetMapMode() );
1968 aMapMode.SetMapUnit( MapUnit::MapTwip );
1969 mrOut.SetMapMode( aMapMode );
1971 // Create new cursor and lock the view:
1973 SwDoc* pDoc = mrSh.GetDoc();
1974 mrSh.SwCursorShell::Push();
1975 mrSh.SwCursorShell::ClearMark();
1976 const bool bOldLockView = mrSh.IsViewLocked();
1977 mrSh.LockView( true );
1979 if ( !mbEditEngineOnly )
1981 assert(pPDFExtOutDevData->GetSwPDFState() == nullptr);
1982 pPDFExtOutDevData->SetSwPDFState(new SwEnhancedPDFState(eLanguageDefault));
1984 // POSTITS
1986 if ( pPDFExtOutDevData->GetIsExportNotes() )
1988 std::vector<SwFormatField*> vpFields;
1989 mrSh.GetFieldType(SwFieldIds::Postit, OUString())->GatherFields(vpFields);
1990 for(auto pFormatField : vpFields)
1992 const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode();
1993 OSL_ENSURE(nullptr != pTNd, "Enhanced pdf export - text node is missing");
1994 if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField))
1995 continue;
1996 // Link Rectangle
1997 const SwRect& rNoteRect = mrSh.GetCharRect();
1998 const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower());
2000 // Link PageNums
2001 std::vector<sal_Int32> aNotePageNums = CalcOutputPageNums(rNoteRect);
2002 for (sal_Int32 aNotePageNum : aNotePageNums)
2005 // Use the NumberFormatter to get the date string:
2006 const SwPostItField* pField = static_cast<SwPostItField*>(pFormatField->GetField());
2007 SvNumberFormatter* pNumFormatter = pDoc->GetNumberFormatter();
2008 const Date aDateDiff(pField->GetDate() - pNumFormatter->GetNullDate());
2009 const sal_uLong nFormat = pNumFormatter->GetStandardFormat(SvNumFormatType::DATE, pField->GetLanguage());
2010 OUString sDate;
2011 const Color* pColor;
2012 pNumFormatter->GetOutputString(aDateDiff.GetDate(), nFormat, sDate, &pColor);
2014 vcl::PDFNote aNote;
2015 // The title should consist of the author and the date:
2016 aNote.Title = pField->GetPar1() + ", " + sDate + ", " + (pField->GetResolved() ? SwResId(STR_RESOLVED) : "");
2017 // Guess what the contents contains...
2018 aNote.Contents = pField->GetText();
2020 // Link Export
2021 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rNoteRect.SVRect()));
2022 pPDFExtOutDevData->CreateNote(aRect, aNote, aNotePageNum);
2024 mrSh.SwCursorShell::ClearMark();
2028 // HYPERLINKS
2030 SwGetINetAttrs aArr;
2031 mrSh.GetINetAttrs( aArr );
2032 for( auto &rAttr : aArr )
2034 SwGetINetAttr* p = &rAttr;
2035 OSL_ENSURE( nullptr != p, "Enhanced pdf export - SwGetINetAttr is missing" );
2037 const SwTextNode* pTNd = p->rINetAttr.GetpTextNode();
2038 OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" );
2040 // 1. Check if the whole paragraph is hidden
2041 // 2. Move to the hyperlink
2042 // 3. Check for hidden text attribute
2043 if ( !pTNd->IsHidden() &&
2044 mrSh.GotoINetAttr( p->rINetAttr ) &&
2045 !mrSh.IsInHiddenRange(/*bSelect=*/false) )
2047 // Select the hyperlink:
2048 mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );
2049 if ( mrSh.SwCursorShell::SelectTextAttr( RES_TXTATR_INETFMT, true ) )
2051 // First, we create the destination, because there may be more
2052 // than one link to this destination:
2053 OUString aURL( INetURLObject::decode(
2054 p->rINetAttr.GetINetFormat().GetValue(),
2055 INetURLObject::DecodeMechanism::Unambiguous ) );
2057 // We have to distinguish between internal and real URLs
2058 const bool bInternal = '#' == aURL[0];
2060 // GetCursor_() is a SwShellCursor, which is derived from
2061 // SwSelPaintRects, therefore the rectangles of the current
2062 // selection can be easily obtained:
2063 // Note: We make a copy of the rectangles, because they may
2064 // be deleted again in JumpToSwMark.
2065 SwRects aTmp;
2066 aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() );
2067 OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
2068 OUString const altText(mrSh.GetSelText());
2070 const SwPageFrame* pSelectionPage =
2071 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2073 // Create the destination for internal links:
2074 sal_Int32 nDestId = -1;
2075 if ( bInternal )
2077 aURL = aURL.copy( 1 );
2078 mrSh.SwCursorShell::ClearMark();
2079 if (! JumpToSwMark( &mrSh, aURL ))
2081 continue; // target deleted
2084 // Destination Rectangle
2085 const SwRect& rDestRect = mrSh.GetCharRect();
2087 const SwPageFrame* pCurrPage =
2088 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2090 // Destination PageNum
2091 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2093 // Destination Export
2094 if ( -1 != nDestPageNum )
2096 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2097 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2101 if ( !bInternal || -1 != nDestId )
2103 // #i44368# Links in Header/Footer
2104 const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd );
2106 // Create links for all selected rectangles:
2107 const size_t nNumOfRects = aTmp.size();
2108 for ( size_t i = 0; i < nNumOfRects; ++i )
2110 // Link Rectangle
2111 const SwRect& rLinkRect( aTmp[ i ] );
2113 // Link PageNums
2114 std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect );
2116 for (sal_Int32 aLinkPageNum : aLinkPageNums)
2118 // Link Export
2119 tools::Rectangle aRect(SwRectToPDFRect(pSelectionPage, rLinkRect.SVRect()));
2120 const sal_Int32 nLinkId =
2121 pPDFExtOutDevData->CreateLink(aRect, altText, aLinkPageNum);
2123 // Store link info for tagged pdf output:
2124 const IdMapEntry aLinkEntry( rLinkRect, nLinkId );
2125 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
2127 // Connect Link and Destination:
2128 if ( bInternal )
2129 pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
2130 else
2131 pPDFExtOutDevData->SetLinkURL( nLinkId, aURL );
2133 // #i44368# Links in Header/Footer
2134 if ( bHeaderFooter )
2135 MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, aURL, bInternal, altText);
2141 mrSh.SwCursorShell::ClearMark();
2144 // HYPERLINKS (Graphics, Frames, OLEs )
2146 for(sw::SpzFrameFormat* pFrameFormat: *pDoc->GetSpzFrameFormats())
2148 const SwFormatURL* pItem;
2149 if ( RES_DRAWFRMFMT != pFrameFormat->Which() &&
2150 GetFrameOfModify(mrSh.GetLayout(), *pFrameFormat, SwFrameType::Fly) &&
2151 (pItem = pFrameFormat->GetAttrSet().GetItemIfSet( RES_URL )) )
2153 const SwPageFrame* pCurrPage =
2154 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2156 OUString aURL( pItem->GetURL() );
2157 if (aURL.isEmpty())
2158 continue;
2159 const bool bInternal = '#' == aURL[0];
2161 // Create the destination for internal links:
2162 sal_Int32 nDestId = -1;
2163 if ( bInternal )
2165 aURL = aURL.copy( 1 );
2166 mrSh.SwCursorShell::ClearMark();
2167 if (! JumpToSwMark( &mrSh, aURL ))
2169 continue; // target deleted
2172 // Destination Rectangle
2173 const SwRect& rDestRect = mrSh.GetCharRect();
2175 pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2177 // Destination PageNum
2178 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2180 // Destination Export
2181 if ( -1 != nDestPageNum )
2183 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2184 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2188 if ( !bInternal || -1 != nDestId )
2190 Point aNullPt;
2191 const SwRect aLinkRect = pFrameFormat->FindLayoutRect( false, &aNullPt );
2192 OUString const formatName(pFrameFormat->GetName());
2193 // Link PageNums
2194 std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( aLinkRect );
2196 // Link Export
2197 for (sal_Int32 aLinkPageNum : aLinkPageNums)
2199 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect()));
2200 const sal_Int32 nLinkId =
2201 pPDFExtOutDevData->CreateLink(aRect, formatName, aLinkPageNum);
2203 // Store link info for tagged pdf output:
2204 const IdMapEntry aLinkEntry(aLinkRect, nLinkId);
2205 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
2207 // Connect Link and Destination:
2208 if ( bInternal )
2209 pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
2210 else
2211 pPDFExtOutDevData->SetLinkURL( nLinkId, aURL );
2213 // #i44368# Links in Header/Footer
2214 const SwFormatAnchor &rAnch = pFrameFormat->GetAnchor();
2215 if (RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId())
2217 const SwNode* pAnchorNode = rAnch.GetAnchorNode();
2218 if ( pAnchorNode && pDoc->IsInHeaderFooter( *pAnchorNode ) )
2220 const SwTextNode* pTNd = pAnchorNode->GetTextNode();
2221 if ( pTNd )
2222 MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, aLinkRect, nDestId, aURL, bInternal, formatName);
2228 else if (pFrameFormat->Which() == RES_DRAWFRMFMT)
2230 // Turn media shapes into Screen annotations.
2231 if (SdrObject* pObject = pFrameFormat->FindRealSdrObject())
2233 SwRect aSnapRect(pObject->GetSnapRect());
2234 std::vector<sal_Int32> aScreenPageNums = CalcOutputPageNums(aSnapRect);
2235 if (aScreenPageNums.empty())
2236 continue;
2238 uno::Reference<drawing::XShape> xShape(pObject->getUnoShape(), uno::UNO_QUERY);
2239 if (xShape->getShapeType() == "com.sun.star.drawing.MediaShape")
2241 uno::Reference<beans::XPropertySet> xShapePropSet(xShape, uno::UNO_QUERY);
2242 OUString title;
2243 xShapePropSet->getPropertyValue("Title") >>= title;
2244 OUString description;
2245 xShapePropSet->getPropertyValue("Description") >>= description;
2246 OUString const altText(title.isEmpty()
2247 ? description
2248 : description.isEmpty()
2249 ? title
2250 : OUString::Concat(title) + OUString::Concat("\n") + OUString::Concat(description));
2252 OUString aMediaURL;
2253 xShapePropSet->getPropertyValue("MediaURL") >>= aMediaURL;
2254 if (!aMediaURL.isEmpty())
2256 OUString const mimeType(xShapePropSet->getPropertyValue("MediaMimeType").get<OUString>());
2257 const SwPageFrame* pCurrPage = mrSh.GetLayout()->GetPageAtPos(aSnapRect.Center());
2258 tools::Rectangle aPDFRect(SwRectToPDFRect(pCurrPage, aSnapRect.SVRect()));
2259 for (sal_Int32 nScreenPageNum : aScreenPageNums)
2261 sal_Int32 nScreenId = pPDFExtOutDevData->CreateScreen(aPDFRect, altText, mimeType, nScreenPageNum, pObject);
2262 if (aMediaURL.startsWith("vnd.sun.star.Package:"))
2264 // Embedded media.
2265 OUString aTempFileURL;
2266 xShapePropSet->getPropertyValue("PrivateTempFileURL") >>= aTempFileURL;
2267 pPDFExtOutDevData->SetScreenStream(nScreenId, aTempFileURL);
2269 else
2270 // Linked media.
2271 pPDFExtOutDevData->SetScreenURL(nScreenId, aMediaURL);
2277 mrSh.SwCursorShell::ClearMark();
2280 // REFERENCES
2282 std::vector<SwFormatField*> vpFields;
2283 mrSh.GetFieldType( SwFieldIds::GetRef, OUString() )->GatherFields(vpFields);
2284 for(auto pFormatField : vpFields )
2286 if( pFormatField->GetTextField() && pFormatField->IsFieldInDoc() )
2288 const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode();
2289 OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" );
2290 if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField))
2291 continue;
2292 // Select the field:
2293 mrSh.SwCursorShell::SetMark();
2294 mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );
2296 // Link Rectangles
2297 SwRects aTmp;
2298 aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() );
2299 OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
2301 mrSh.SwCursorShell::ClearMark();
2303 // Destination Rectangle
2304 const SwGetRefField* pField = static_cast<SwGetRefField*>(pFormatField->GetField());
2305 const OUString& rRefName = pField->GetSetRefName();
2306 mrSh.GotoRefMark( rRefName, pField->GetSubType(), pField->GetSeqNo() );
2307 const SwRect& rDestRect = mrSh.GetCharRect();
2309 const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2311 // Destination PageNum
2312 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2314 if ( -1 != nDestPageNum )
2316 // Destination Export
2317 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2318 const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2320 // #i44368# Links in Header/Footer
2321 const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd );
2322 OUString const content(pField->ExpandField(true, mrSh.GetLayout()));
2324 // Create links for all selected rectangles:
2325 const size_t nNumOfRects = aTmp.size();
2326 for ( size_t i = 0; i < nNumOfRects; ++i )
2328 // Link rectangle
2329 const SwRect& rLinkRect( aTmp[ i ] );
2331 // Link PageNums
2332 std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect );
2334 for (sal_Int32 aLinkPageNum : aLinkPageNums)
2336 // Link Export
2337 aRect = SwRectToPDFRect(pCurrPage, rLinkRect.SVRect());
2338 const sal_Int32 nLinkId =
2339 pPDFExtOutDevData->CreateLink(aRect, content, aLinkPageNum);
2341 // Store link info for tagged pdf output:
2342 const IdMapEntry aLinkEntry( rLinkRect, nLinkId );
2343 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
2345 // Connect Link and Destination:
2346 pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
2348 // #i44368# Links in Header/Footer
2349 if ( bHeaderFooter )
2351 MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, "", true, content);
2357 mrSh.SwCursorShell::ClearMark();
2360 ExportAuthorityEntryLinks();
2362 // FOOTNOTES
2364 const size_t nFootnoteCount = pDoc->GetFootnoteIdxs().size();
2365 for ( size_t nIdx = 0; nIdx < nFootnoteCount; ++nIdx )
2367 // Set cursor to text node that contains the footnote:
2368 const SwTextFootnote* pTextFootnote = pDoc->GetFootnoteIdxs()[ nIdx ];
2369 SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode());
2371 mrSh.GetCursor_()->GetPoint()->Assign(rTNd, pTextFootnote->GetStart());
2373 // 1. Check if the whole paragraph is hidden
2374 // 2. Check for hidden text attribute
2375 if (rTNd.GetTextNode()->IsHidden() || mrSh.IsInHiddenRange(/*bSelect=*/false)
2376 || (mrSh.GetLayout()->IsHideRedlines()
2377 && sw::IsFootnoteDeleted(pDoc->getIDocumentRedlineAccess(), *pTextFootnote)))
2379 continue;
2382 SwCursorSaveState aSaveState( *mrSh.GetCursor_() );
2384 // Select the footnote:
2385 mrSh.SwCursorShell::SetMark();
2386 mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );
2388 // Link Rectangle
2389 SwRects aTmp;
2390 aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() );
2391 OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
2393 mrSh.GetCursor_()->RestoreSavePos();
2394 mrSh.SwCursorShell::ClearMark();
2396 if (aTmp.empty())
2397 continue;
2399 const SwRect aLinkRect( aTmp[ 0 ] );
2401 // Goto footnote text:
2402 if ( mrSh.GotoFootnoteText() )
2404 // Destination Rectangle
2405 const SwRect& rDestRect = mrSh.GetCharRect();
2406 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2407 if ( -1 != nDestPageNum )
2409 const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2410 // Destination PageNum
2411 tools::Rectangle aRect = SwRectToPDFRect(pCurrPage, rDestRect.SVRect());
2412 // Back link rectangle calculation
2413 const SwPageFrame* fnBodyPage = pCurrPage->getRootFrame()->GetPageByPageNum(nDestPageNum+1);
2414 SwRect fnSymbolRect;
2415 if (fnBodyPage->IsVertical()){
2416 tools::Long fnSymbolTop = fnBodyPage->GetTopMargin() + fnBodyPage->getFrameArea().Top();
2417 tools::Long symbolHeight = rDestRect.Top() - fnSymbolTop;
2418 fnSymbolRect = SwRect(rDestRect.Pos().X(),fnSymbolTop,rDestRect.Width(),symbolHeight);
2419 } else {
2420 if (fnBodyPage->IsRightToLeft()){
2421 tools::Long fnSymbolRight = fnBodyPage->getFrameArea().Right() - fnBodyPage->GetRightMargin();
2422 tools::Long symbolWidth = fnSymbolRight - rDestRect.Right();
2423 fnSymbolRect = SwRect(rDestRect.Pos().X(),rDestRect.Pos().Y(),symbolWidth,rDestRect.Height());
2424 } else {
2425 tools::Long fnSymbolLeft = fnBodyPage->GetLeftMargin() + fnBodyPage->getFrameArea().Left();
2426 tools::Long symbolWidth = rDestRect.Left() - fnSymbolLeft;
2427 fnSymbolRect = SwRect(fnSymbolLeft,rDestRect.Pos().Y(),symbolWidth,rDestRect.Height());
2430 tools::Rectangle aFootnoteSymbolRect = SwRectToPDFRect(pCurrPage, fnSymbolRect.SVRect());
2432 OUString const numStrSymbol(pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, mrSh.GetLayout(), true));
2433 OUString const numStrRef(pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, mrSh.GetLayout(), false));
2435 // Export back link
2436 const sal_Int32 nBackLinkId = pPDFExtOutDevData->CreateLink(aFootnoteSymbolRect, numStrSymbol, nDestPageNum);
2437 // Destination Export
2438 const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2439 mrSh.GotoFootnoteAnchor();
2440 // Link PageNums
2441 sal_Int32 aLinkPageNum = CalcOutputPageNum( aLinkRect );
2442 pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2443 // Link Export
2444 aRect = SwRectToPDFRect(pCurrPage, aLinkRect.SVRect());
2445 const sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, numStrRef, aLinkPageNum);
2446 // Back link destination Export
2447 const sal_Int32 nBackDestId = pPDFExtOutDevData->CreateDest(aRect, aLinkPageNum);
2448 // Store link info for tagged pdf output:
2449 const IdMapEntry aLinkEntry( aLinkRect, nLinkId );
2450 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
2452 // Store backlink info for tagged pdf output:
2453 const IdMapEntry aBackLinkEntry( aFootnoteSymbolRect, nBackLinkId );
2454 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aBackLinkEntry);
2455 // Connect Links and Destinations:
2456 pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
2457 pPDFExtOutDevData->SetLinkDest( nBackLinkId, nBackDestId );
2462 // OUTLINE
2464 if( pPDFExtOutDevData->GetIsExportBookmarks() )
2466 typedef std::pair< sal_Int8, sal_Int32 > StackEntry;
2467 std::stack< StackEntry > aOutlineStack;
2468 aOutlineStack.push( StackEntry( -1, -1 ) ); // push default value
2470 const SwOutlineNodes::size_type nOutlineCount =
2471 mrSh.getIDocumentOutlineNodesAccess()->getOutlineNodesCount();
2472 for ( SwOutlineNodes::size_type i = 0; i < nOutlineCount; ++i )
2474 // Check if outline is hidden
2475 const SwTextNode* pTNd = mrSh.GetNodes().GetOutLineNds()[ i ]->GetTextNode();
2476 OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" );
2478 if ( pTNd->IsHidden() ||
2479 !sw::IsParaPropsNode(*mrSh.GetLayout(), *pTNd) ||
2480 // #i40292# Skip empty outlines:
2481 pTNd->GetText().isEmpty())
2482 continue;
2484 // Get parent id from stack:
2485 const sal_Int8 nLevel = static_cast<sal_Int8>(mrSh.getIDocumentOutlineNodesAccess()->getOutlineLevel( i ));
2486 sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first;
2487 while ( nLevelOnTopOfStack >= nLevel &&
2488 nLevelOnTopOfStack != -1 )
2490 aOutlineStack.pop();
2491 nLevelOnTopOfStack = aOutlineStack.top().first;
2493 const sal_Int32 nParent = aOutlineStack.top().second;
2495 // Destination rectangle
2496 mrSh.GotoOutline(i);
2497 const SwRect& rDestRect = mrSh.GetCharRect();
2499 const SwPageFrame* pCurrPage =
2500 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2502 // Destination PageNum
2503 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2505 if ( -1 != nDestPageNum )
2507 // Destination Export
2508 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2509 const sal_Int32 nDestId =
2510 pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2512 // Outline entry text
2513 const OUString& rEntry = mrSh.getIDocumentOutlineNodesAccess()->getOutlineText(
2514 i, mrSh.GetLayout(), true, false, false );
2516 // Create a new outline item:
2517 const sal_Int32 nOutlineId =
2518 pPDFExtOutDevData->CreateOutlineItem( nParent, rEntry, nDestId );
2520 // Push current level and nOutlineId on stack:
2521 aOutlineStack.push( StackEntry( nLevel, nOutlineId ) );
2526 if( pPDFExtOutDevData->GetIsExportNamedDestinations() )
2528 // #i56629# the iteration to convert the OOo bookmark (#bookmark)
2529 // into PDF named destination, see section 8.2.1 in PDF 1.4 spec
2530 // We need:
2531 // 1. a name for the destination, formed from the standard OOo bookmark name
2532 // 2. the destination, obtained from where the bookmark destination lies
2533 IDocumentMarkAccess* const pMarkAccess = mrSh.GetDoc()->getIDocumentMarkAccess();
2534 for(IDocumentMarkAccess::const_iterator_t ppMark = pMarkAccess->getBookmarksBegin();
2535 ppMark != pMarkAccess->getBookmarksEnd();
2536 ++ppMark)
2538 //get the name
2539 const ::sw::mark::IMark* pBkmk = *ppMark;
2540 mrSh.SwCursorShell::ClearMark();
2541 const OUString& sBkName = pBkmk->GetName();
2543 //jump to it
2544 if (! JumpToSwMark( &mrSh, sBkName ))
2546 continue;
2549 // Destination Rectangle
2550 const SwRect& rDestRect = mrSh.GetCharRect();
2552 const SwPageFrame* pCurrPage =
2553 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2555 // Destination PageNum
2556 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2558 // Destination Export
2559 if ( -1 != nDestPageNum )
2561 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2562 pPDFExtOutDevData->CreateNamedDest(sBkName, aRect, nDestPageNum);
2565 mrSh.SwCursorShell::ClearMark();
2566 //<--- i56629
2569 else
2572 // LINKS FROM EDITENGINE
2574 std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks();
2575 for ( const auto& rBookmark : rBookmarks )
2577 OUString aBookmarkName( rBookmark.aBookmark );
2578 const bool bInternal = '#' == aBookmarkName[0];
2579 if ( bInternal )
2581 aBookmarkName = aBookmarkName.copy( 1 );
2582 JumpToSwMark( &mrSh, aBookmarkName );
2584 // Destination Rectangle
2585 const SwRect& rDestRect = mrSh.GetCharRect();
2587 const SwPageFrame* pCurrPage =
2588 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2590 // Destination PageNum
2591 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2593 if ( -1 != nDestPageNum )
2595 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2596 if ( rBookmark.nLinkId != -1 )
2598 // Destination Export
2599 const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2601 // Connect Link and Destination:
2602 pPDFExtOutDevData->SetLinkDest( rBookmark.nLinkId, nDestId );
2604 else
2606 pPDFExtOutDevData->DescribeRegisteredDest(rBookmark.nDestId, aRect, nDestPageNum);
2610 else
2611 pPDFExtOutDevData->SetLinkURL( rBookmark.nLinkId, aBookmarkName );
2613 rBookmarks.clear();
2614 assert(pPDFExtOutDevData->GetSwPDFState());
2615 delete pPDFExtOutDevData->GetSwPDFState();
2616 pPDFExtOutDevData->SetSwPDFState(nullptr);
2619 // Restore view, cursor, and outdev:
2620 mrSh.LockView( bOldLockView );
2621 mrSh.SwCursorShell::Pop(SwCursorShell::PopMode::DeleteCurrent);
2622 mrOut.Pop();
2625 void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks()
2627 auto pPDFExtOutDevData = dynamic_cast<vcl::PDFExtOutDevData*>(mrOut.GetExtOutDevData());
2628 if (!pPDFExtOutDevData)
2630 return;
2633 // Create PDF destinations for bibliography table entries
2634 std::vector<std::tuple<const SwTOXBase*, const OUString*, sal_Int32>> vDestinations;
2635 // string is the row node text, sal_Int32 is number of the destination
2636 // Note: This way of iterating doesn't seem to take into account TOXes
2637 // that are in a frame, probably in some other cases too
2639 mrSh.GotoPage(1);
2640 while (mrSh.GotoNextTOXBase())
2642 const SwTOXBase* pIteratedTOX = nullptr;
2643 while ((pIteratedTOX = mrSh.GetCurTOX()) != nullptr
2644 && pIteratedTOX->GetType() == TOX_AUTHORITIES)
2646 if (const SwNode& rCurrentNode = mrSh.GetCursor()->GetPoint()->GetNode();
2647 rCurrentNode.GetNodeType() == SwNodeType::Text)
2649 if (mrSh.GetCursor()->GetPoint()->GetNode().FindSectionNode()->GetSection().GetType()
2650 == SectionType::ToxContent) // this checks it's not a heading
2652 // Destination Rectangle
2653 const SwRect& rDestRect = mrSh.GetCharRect();
2655 const SwPageFrame* pCurrPage =
2656 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2658 // Destination PageNum
2659 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2661 // Destination Export
2662 if ( -1 != nDestPageNum )
2664 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2665 const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2666 const OUString* vNodeText = &static_cast<const SwTextNode*>(&rCurrentNode)->GetText();
2667 vDestinations.emplace_back(pIteratedTOX, vNodeText, nDestId);
2671 mrSh.MovePara(GoNextPara, fnParaStart);
2676 // Generate links to matching entries in the bibliography tables
2677 std::vector<SwFormatField*> aFields;
2678 SwFieldType* pType = mrSh.GetFieldType(SwFieldIds::TableOfAuthorities, OUString());
2679 if (!pType)
2681 return;
2684 pType->GatherFields(aFields);
2685 const auto pPageFrame = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower());
2686 for (const auto pFormatField : aFields)
2688 if (!pFormatField->GetTextField() || !pFormatField->IsFieldInDoc())
2690 continue;
2693 const auto& rAuthorityField
2694 = *static_cast<const SwAuthorityField*>(pFormatField->GetField());
2696 if (auto targetType = rAuthorityField.GetTargetType();
2697 targetType == SwAuthorityField::TargetType::UseDisplayURL
2698 || targetType == SwAuthorityField::TargetType::UseTargetURL)
2700 // Since the target type specifies to use an URL, link to it
2701 const OUString& rURL = rAuthorityField.GetAbsoluteURL();
2702 if (rURL.getLength() == 0)
2704 continue;
2707 const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode();
2708 if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField))
2710 continue;
2713 OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout()));
2715 // Select the field.
2716 mrSh.SwCursorShell::SetMark();
2717 mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars);
2719 // Create the links.
2720 for (const auto& rLinkRect : *mrSh.SwCursorShell::GetCursor_())
2722 for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect))
2724 tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect()));
2725 sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum);
2726 IdMapEntry aLinkEntry(rLinkRect, nLinkId);
2727 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
2728 pPDFExtOutDevData->SetLinkURL(nLinkId, rURL);
2731 mrSh.SwCursorShell::ClearMark();
2733 else if (targetType == SwAuthorityField::TargetType::BibliographyTableRow)
2735 // As the target type specifies, try linking to a bibliography table row
2736 sal_Int32 nDestId = -1;
2738 std::unordered_map<const SwTOXBase*, OUString> vFormattedFieldStrings;
2739 for (const auto& rDestinationTuple : vDestinations)
2741 if (vFormattedFieldStrings.find(std::get<0>(rDestinationTuple))
2742 == vFormattedFieldStrings.end())
2743 vFormattedFieldStrings.emplace(std::get<0>(rDestinationTuple),
2744 rAuthorityField.GetAuthority(mrSh.GetLayout(),
2745 &std::get<0>(rDestinationTuple)->GetTOXForm()));
2747 if (vFormattedFieldStrings.at(std::get<0>(rDestinationTuple)) == *std::get<1>(rDestinationTuple))
2749 nDestId = std::get<2>(rDestinationTuple);
2750 break;
2754 if (nDestId == -1)
2755 continue;
2757 const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode();
2758 if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField))
2760 continue;
2763 OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout()));
2765 // Select the field.
2766 mrSh.SwCursorShell::SetMark();
2767 mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars);
2769 // Create the links.
2770 for (const auto& rLinkRect : *mrSh.SwCursorShell::GetCursor_())
2772 for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect))
2774 tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect()));
2775 sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum);
2776 IdMapEntry aLinkEntry(rLinkRect, nLinkId);
2777 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
2778 pPDFExtOutDevData->SetLinkDest(nLinkId, nDestId);
2781 mrSh.SwCursorShell::ClearMark();
2786 // Returns the page number in the output pdf on which the given rect is located.
2787 // If this page is duplicated, method will return first occurrence of it.
2788 sal_Int32 SwEnhancedPDFExportHelper::CalcOutputPageNum( const SwRect& rRect ) const
2790 std::vector< sal_Int32 > aPageNums = CalcOutputPageNums( rRect );
2791 if ( !aPageNums.empty() )
2792 return aPageNums[0];
2793 return -1;
2796 // Returns a vector of the page numbers in the output pdf on which the given
2797 // rect is located. There can be many such pages since StringRangeEnumerator
2798 // allows duplication of its entries.
2799 std::vector< sal_Int32 > SwEnhancedPDFExportHelper::CalcOutputPageNums(
2800 const SwRect& rRect ) const
2802 std::vector< sal_Int32 > aPageNums;
2804 // Document page number.
2805 sal_Int32 nPageNumOfRect = mrSh.GetPageNumAndSetOffsetForPDF( mrOut, rRect );
2806 if ( nPageNumOfRect < 0 )
2807 return aPageNums;
2809 // What will be the page numbers of page nPageNumOfRect in the output pdf?
2810 if ( mpRangeEnum )
2812 if ( mbSkipEmptyPages )
2813 // Map the page number to the range without empty pages.
2814 nPageNumOfRect = maPageNumberMap[ nPageNumOfRect ];
2816 if ( mpRangeEnum->hasValue( nPageNumOfRect ) )
2818 sal_Int32 nOutputPageNum = 0;
2819 StringRangeEnumerator::Iterator aIter = mpRangeEnum->begin();
2820 StringRangeEnumerator::Iterator aEnd = mpRangeEnum->end();
2821 for ( ; aIter != aEnd; ++aIter )
2823 if ( *aIter == nPageNumOfRect )
2824 aPageNums.push_back( nOutputPageNum );
2825 ++nOutputPageNum;
2829 else
2831 if ( mbSkipEmptyPages )
2833 sal_Int32 nOutputPageNum = 0;
2834 for ( size_t i = 0; i < maPageNumberMap.size(); ++i )
2836 if ( maPageNumberMap[i] >= 0 ) // is not empty?
2838 if ( i == static_cast<size_t>( nPageNumOfRect ) )
2840 aPageNums.push_back( nOutputPageNum );
2841 break;
2843 ++nOutputPageNum;
2847 else
2848 aPageNums.push_back( nPageNumOfRect );
2851 return aPageNums;
2854 void SwEnhancedPDFExportHelper::MakeHeaderFooterLinks( vcl::PDFExtOutDevData& rPDFExtOutDevData,
2855 const SwTextNode& rTNd,
2856 const SwRect& rLinkRect,
2857 sal_Int32 nDestId,
2858 const OUString& rURL,
2859 bool bInternal,
2860 OUString const& rContent) const
2862 // We assume, that the primary link has just been exported. Therefore
2863 // the offset of the link rectangle calculates as follows:
2864 const Point aOffset = rLinkRect.Pos() + mrOut.GetMapMode().GetOrigin();
2866 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rTNd);
2867 for ( SwTextFrame* pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() )
2869 // Add offset to current page:
2870 const SwPageFrame* pPageFrame = pTmpFrame->FindPageFrame();
2871 SwRect aHFLinkRect( rLinkRect );
2872 aHFLinkRect.Pos() = pPageFrame->getFrameArea().Pos() + aOffset;
2874 // #i97135# the gcc_x64 optimizer gets aHFLinkRect != rLinkRect wrong
2875 // fool it by comparing the position only (the width and height are the
2876 // same anyway)
2877 if ( aHFLinkRect.Pos() != rLinkRect.Pos() )
2879 // Link PageNums
2880 std::vector<sal_Int32> aHFLinkPageNums = CalcOutputPageNums( aHFLinkRect );
2882 for (sal_Int32 aHFLinkPageNum : aHFLinkPageNums)
2884 // Link Export
2885 tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, aHFLinkRect.SVRect()));
2886 const sal_Int32 nHFLinkId =
2887 rPDFExtOutDevData.CreateLink(aRect, rContent, aHFLinkPageNum);
2889 // Connect Link and Destination:
2890 if ( bInternal )
2891 rPDFExtOutDevData.SetLinkDest( nHFLinkId, nDestId );
2892 else
2893 rPDFExtOutDevData.SetLinkURL( nHFLinkId, rURL );
2899 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */