ITEM: Refactor ItemType
[LibreOffice.git] / sw / source / core / text / EnhancedPDFExportHelper.cxx
blobee221ccb66bfdaa643f8d94b98aa6d5d125b53d5
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 <vcl/pdf/PDFNote.hxx>
32 #include <tools/multisel.hxx>
33 #include <editeng/adjustitem.hxx>
34 #include <editeng/lrspitem.hxx>
35 #include <editeng/langitem.hxx>
36 #include <tools/urlobj.hxx>
37 #include <svl/languageoptions.hxx>
38 #include <svl/numformat.hxx>
39 #include <svl/zforlist.hxx>
40 #include <swatrset.hxx>
41 #include <frmatr.hxx>
42 #include <paratr.hxx>
43 #include <ndtxt.hxx>
44 #include <ndole.hxx>
45 #include <section.hxx>
46 #include <tox.hxx>
47 #include <fmtfld.hxx>
48 #include <txtinet.hxx>
49 #include <fmtinfmt.hxx>
50 #include <fchrfmt.hxx>
51 #include <charfmt.hxx>
52 #include <fmtanchr.hxx>
53 #include <fmturl.hxx>
54 #include <editsh.hxx>
55 #include <viscrs.hxx>
56 #include <txtfld.hxx>
57 #include <reffld.hxx>
58 #include <doc.hxx>
59 #include <IDocumentOutlineNodes.hxx>
60 #include <mdiexp.hxx>
61 #include <docufld.hxx>
62 #include <ftnidx.hxx>
63 #include <txtftn.hxx>
64 #include <rootfrm.hxx>
65 #include <pagefrm.hxx>
66 #include <txtfrm.hxx>
67 #include <tabfrm.hxx>
68 #include <rowfrm.hxx>
69 #include <cellfrm.hxx>
70 #include <sectfrm.hxx>
71 #include <ftnfrm.hxx>
72 #include <flyfrm.hxx>
73 #include <notxtfrm.hxx>
74 #include "porfld.hxx"
75 #include "pormulti.hxx"
76 #include <SwStyleNameMapper.hxx>
77 #include "itrpaint.hxx"
78 #include <i18nlangtag/languagetag.hxx>
79 #include <IMark.hxx>
80 #include <printdata.hxx>
81 #include <vprint.hxx>
82 #include <SwNodeNum.hxx>
83 #include <calbck.hxx>
84 #include <frmtool.hxx>
85 #include <strings.hrc>
86 #include <frameformats.hxx>
87 #include <tblafmt.hxx>
88 #include <authfld.hxx>
89 #include <dcontact.hxx>
90 #include <PostItMgr.hxx>
91 #include <AnnotationWin.hxx>
93 #include <tools/globname.hxx>
94 #include <svx/svdobj.hxx>
96 #include <stack>
97 #include <map>
98 #include <set>
99 #include <optional>
101 using namespace ::com::sun::star;
103 #if OSL_DEBUG_LEVEL > 1
105 static std::vector< sal_uInt16 > aStructStack;
107 void lcl_DBGCheckStack()
109 /* NonStructElement = 0 Document = 1 Part = 2
110 * Article = 3 Section = 4 Division = 5
111 * BlockQuote = 6 Caption = 7 TOC = 8
112 * TOCI = 9 Index = 10 Paragraph = 11
113 * Heading = 12 H1-6 = 13 - 18 List = 19
114 * ListItem = 20 LILabel = 21 LIBody = 22
115 * Table = 23 TableRow = 24 TableHeader = 25
116 * TableData = 26 Span = 27 Quote = 28
117 * Note = 29 Reference = 30 BibEntry = 31
118 * Code = 32 Link = 33 Figure = 34
119 * Formula = 35 Form = 36 Continued frame = 99
122 sal_uInt16 nElement;
123 for ( const auto& rItem : aStructStack )
125 nElement = rItem;
127 (void)nElement;
130 #endif
132 typedef std::set< tools::Long, lt_TableColumn > TableColumnsMapEntry;
133 typedef std::pair< SwRect, sal_Int32 > IdMapEntry;
134 typedef std::vector< IdMapEntry > LinkIdMap;
135 typedef std::vector< IdMapEntry > NoteIdMap;
136 typedef std::map< const SwTable*, TableColumnsMapEntry > TableColumnsMap;
137 typedef std::map< const SwNumberTreeNode*, sal_Int32 > NumListIdMap;
138 typedef std::map< const SwNumberTreeNode*, sal_Int32 > NumListBodyIdMap;
139 typedef std::set<const void*> FrameTagSet;
141 struct SwEnhancedPDFState
143 TableColumnsMap m_TableColumnsMap;
144 LinkIdMap m_LinkIdMap;
145 NoteIdMap m_NoteIdMap;
146 NumListIdMap m_NumListIdMap;
147 NumListBodyIdMap m_NumListBodyIdMap;
148 FrameTagSet m_FrameTagSet;
150 LanguageType m_eLanguageDefault;
152 struct Span
154 FontLineStyle eUnderline;
155 FontLineStyle eOverline;
156 FontStrikeout eStrikeout;
157 FontEmphasisMark eFontEmphasis;
158 short nEscapement;
159 SwFontScript nScript;
160 LanguageType nLang;
161 OUString StyleName;
164 ::std::optional<Span> m_oCurrentSpan;
165 ::std::optional<SwTextAttr const*> m_oCurrentLink;
167 SwEnhancedPDFState(LanguageType const eLanguageDefault)
168 : m_eLanguageDefault(eLanguageDefault)
173 namespace
175 // ODF Style Names:
176 constexpr OUString aTableHeadingName = u"Table Heading"_ustr;
177 constexpr OUString aQuotations = u"Quotations"_ustr;
178 constexpr OUString aCaption = u"Caption"_ustr;
179 constexpr OUString aHeading = u"Heading"_ustr;
180 constexpr OUString aQuotation = u"Quotation"_ustr;
181 constexpr OUString aSourceText = u"Source Text"_ustr;
183 // PDF Tag Names:
184 constexpr OUStringLiteral aDocumentString = u"Document";
185 constexpr OUString aDivString = u"Div"_ustr;
186 constexpr OUStringLiteral aSectString = u"Sect";
187 constexpr OUStringLiteral aHString = u"H";
188 constexpr OUStringLiteral aH1String = u"H1";
189 constexpr OUStringLiteral aH2String = u"H2";
190 constexpr OUStringLiteral aH3String = u"H3";
191 constexpr OUStringLiteral aH4String = u"H4";
192 constexpr OUStringLiteral aH5String = u"H5";
193 constexpr OUStringLiteral aH6String = u"H6";
194 constexpr OUStringLiteral aH7String = u"H7";
195 constexpr OUStringLiteral aH8String = u"H8";
196 constexpr OUStringLiteral aH9String = u"H9";
197 constexpr OUStringLiteral aH10String = u"H10";
198 constexpr OUStringLiteral aListString = u"L";
199 constexpr OUStringLiteral aListItemString = u"LI";
200 constexpr OUStringLiteral aListLabelString = u"Lbl";
201 constexpr OUString aListBodyString = u"LBody"_ustr;
202 constexpr OUStringLiteral aBlockQuoteString = u"BlockQuote";
203 constexpr OUString aCaptionString = u"Caption"_ustr;
204 constexpr OUStringLiteral aIndexString = u"Index";
205 constexpr OUStringLiteral aTOCString = u"TOC";
206 constexpr OUStringLiteral aTOCIString = u"TOCI";
207 constexpr OUStringLiteral aTableString = u"Table";
208 constexpr OUStringLiteral aTRString = u"TR";
209 constexpr OUStringLiteral aTDString = u"TD";
210 constexpr OUStringLiteral aTHString = u"TH";
211 constexpr OUStringLiteral aBibEntryString = u"BibEntry";
212 constexpr OUStringLiteral aQuoteString = u"Quote";
213 constexpr OUString aSpanString = u"Span"_ustr;
214 constexpr OUStringLiteral aCodeString = u"Code";
215 constexpr OUStringLiteral aFigureString = u"Figure";
216 constexpr OUStringLiteral aFormulaString = u"Formula";
217 constexpr OUString aLinkString = u"Link"_ustr;
218 constexpr OUStringLiteral aNoteString = u"Note";
219 constexpr OUStringLiteral aAnnotString = u"Annot";
221 // returns true if first paragraph in cell frame has 'table heading' style
222 bool lcl_IsHeadlineCell( const SwCellFrame& rCellFrame )
224 bool bRet = false;
226 const SwContentFrame *pCnt = rCellFrame.ContainsContent();
227 if ( pCnt && pCnt->IsTextFrame() )
229 SwTextNode const*const pTextNode = static_cast<const SwTextFrame*>(pCnt)->GetTextNodeForParaProps();
230 const SwFormat* pTextFormat = pTextNode->GetFormatColl();
232 OUString sStyleName;
233 SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
234 bRet = sStyleName == aTableHeadingName;
237 // tdf#153935 wild guessing for 1st row based on table autoformat
238 if (!bRet && !rCellFrame.GetUpper()->GetPrev())
240 SwTable const*const pTable(rCellFrame.FindTabFrame()->GetTable());
241 assert(pTable);
242 OUString const& rStyleName(pTable->GetTableStyleName());
243 if (!rStyleName.isEmpty())
245 if (SwTableAutoFormat const*const pTableAF =
246 pTable->GetFrameFormat()->GetDoc()->GetTableStyles().FindAutoFormat(rStyleName))
248 bRet |= pTableAF->HasHeaderRow();
253 return bRet;
256 // List all frames for which the NonStructElement tag is set:
257 bool lcl_IsInNonStructEnv( const SwFrame& rFrame )
259 bool bRet = false;
261 if ( nullptr != rFrame.FindFooterOrHeader() &&
262 !rFrame.IsHeaderFrame() && !rFrame.IsFooterFrame() )
264 bRet = true;
266 else if ( rFrame.IsInTab() && !rFrame.IsTabFrame() )
268 const SwTabFrame* pTabFrame = rFrame.FindTabFrame();
269 if ( rFrame.GetUpper() != pTabFrame &&
270 pTabFrame->IsFollow() && pTabFrame->IsInHeadline( rFrame ) )
271 bRet = true;
274 return bRet;
277 // Generate key from frame for reopening tags:
278 void const* lcl_GetKeyFromFrame( const SwFrame& rFrame )
280 void const* pKey = nullptr;
282 if ( rFrame.IsPageFrame() )
283 pKey = static_cast<void const *>(&(static_cast<const SwPageFrame&>(rFrame).GetFormat()->getIDocumentSettingAccess()));
284 else if ( rFrame.IsTextFrame() )
285 pKey = static_cast<void const *>(static_cast<const SwTextFrame&>(rFrame).GetTextNodeFirst());
286 else if ( rFrame.IsSctFrame() )
287 pKey = static_cast<void const *>(static_cast<const SwSectionFrame&>(rFrame).GetSection());
288 else if ( rFrame.IsTabFrame() )
289 pKey = static_cast<void const *>(static_cast<const SwTabFrame&>(rFrame).GetTable());
290 else if ( rFrame.IsRowFrame() )
291 pKey = static_cast<void const *>(static_cast<const SwRowFrame&>(rFrame).GetTabLine());
292 else if ( rFrame.IsCellFrame() )
294 const SwTabFrame* pTabFrame = rFrame.FindTabFrame();
295 const SwTable* pTable = pTabFrame->GetTable();
296 pKey = static_cast<void const *>(& static_cast<const SwCellFrame&>(rFrame).GetTabBox()->FindStartOfRowSpan(*pTable));
298 else if (rFrame.IsFootnoteFrame())
300 pKey = static_cast<void const*>(static_cast<SwFootnoteFrame const&>(rFrame).GetAttr());
303 return pKey;
306 bool lcl_HasPreviousParaSameNumRule(SwTextFrame const& rTextFrame, const SwTextNode& rNode)
308 bool bRet = false;
309 SwNodeIndex aIdx( rNode );
310 const SwDoc& rDoc = rNode.GetDoc();
311 const SwNodes& rNodes = rDoc.GetNodes();
312 const SwNode* pNode = &rNode;
313 const SwNumRule* pNumRule = rNode.GetNumRule();
315 while (pNode != rNodes.DocumentSectionStartNode(const_cast<SwNode*>(static_cast<SwNode const *>(&rNode))) )
317 sw::GotoPrevLayoutTextFrame(aIdx, rTextFrame.getRootFrame());
319 if (aIdx.GetNode().IsTextNode())
321 const SwTextNode *const pPrevTextNd = sw::GetParaPropsNode(
322 *rTextFrame.getRootFrame(), *aIdx.GetNode().GetTextNode());
323 const SwNumRule * pPrevNumRule = pPrevTextNd->GetNumRule();
325 // We find the previous text node. Now check, if the previous text node
326 // has the same numrule like rNode:
327 if ( (pPrevNumRule == pNumRule) &&
328 (!pPrevTextNd->IsOutline() == !rNode.IsOutline()))
329 bRet = true;
331 break;
334 pNode = &aIdx.GetNode();
336 return bRet;
339 bool lcl_TryMoveToNonHiddenField(SwEditShell& rShell, const SwTextNode& rNd, const SwFormatField& rField)
341 // 1. Check if the whole paragraph is hidden
342 // 2. Move to the field
343 // 3. Check for hidden text attribute
344 if(rNd.IsHidden())
345 return false;
346 if(!rShell.GotoFormatField(rField) || rShell.IsInHiddenRange(/*bSelect=*/false))
348 rShell.SwCursorShell::ClearMark();
349 return false;
351 return true;
354 // tdf#157816: try to check if the rectangle contains actual text
355 ::std::vector<SwRect> GetCursorRectsContainingText(SwCursorShell const& rShell)
357 ::std::vector<SwRect> ret;
358 SwRects rects;
359 rShell.GetLayout()->CalcFrameRects(*rShell.GetCursor_(), rects, SwRootFrame::RectsMode::NoAnchoredFlys);
360 for (SwRect const& rRect : rects)
362 Point center(rRect.Center());
363 SwSpecialPos special;
364 SwCursorMoveState cms(CursorMoveState::NONE);
365 cms.m_pSpecialPos = &special;
366 cms.m_bFieldInfo = true;
367 SwPosition pos(rShell.GetDoc()->GetNodes());
368 auto const [pStart, pEnd] = rShell.GetCursor_()->StartEnd();
369 if (rShell.GetLayout()->GetModelPositionForViewPoint(&pos, center, &cms)
370 && *pStart <= pos && pos <= *pEnd)
372 SwRect charRect;
373 std::pair<Point, bool> const tmp(center, false);
374 SwContentFrame const*const pFrame(
375 pos.nNode.GetNode().GetTextNode()->getLayoutFrame(rShell.GetLayout(), &pos, &tmp));
376 if (pFrame->GetCharRect(charRect, pos, &cms, false)
377 && rRect.Overlaps(charRect))
379 ret.push_back(rRect);
382 // reset stupid static var that may have gotten set now
383 SwTextCursor::SetRightMargin(false); // WTF is this crap
385 return ret;
388 } // end namespace
390 SwTaggedPDFHelper::SwTaggedPDFHelper( const Num_Info* pNumInfo,
391 const Frame_Info* pFrameInfo,
392 const Por_Info* pPorInfo,
393 OutputDevice const & rOut )
394 : m_nEndStructureElement( 0 ),
395 m_nRestoreCurrentTag( -1 ),
396 mpNumInfo( pNumInfo ),
397 mpFrameInfo( pFrameInfo ),
398 mpPorInfo( pPorInfo )
400 mpPDFExtOutDevData =
401 dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() );
403 if ( !(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()) )
404 return;
406 #if OSL_DEBUG_LEVEL > 1
407 sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
408 lcl_DBGCheckStack();
409 #endif
410 if ( mpNumInfo )
411 BeginNumberedListStructureElements();
412 else if ( mpFrameInfo )
413 BeginBlockStructureElements();
414 else if ( mpPorInfo )
415 BeginInlineStructureElements();
416 else
417 BeginTag( vcl::PDFWriter::NonStructElement, OUString() );
419 #if OSL_DEBUG_LEVEL > 1
420 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
421 lcl_DBGCheckStack();
422 (void)nCurrentStruct;
423 #endif
426 SwTaggedPDFHelper::~SwTaggedPDFHelper()
428 if ( !(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportTaggedPDF()) )
429 return;
431 #if OSL_DEBUG_LEVEL > 1
432 sal_Int32 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
433 lcl_DBGCheckStack();
434 #endif
435 EndStructureElements();
437 #if OSL_DEBUG_LEVEL > 1
438 nCurrentStruct = mpPDFExtOutDevData->GetCurrentStructureElement();
439 lcl_DBGCheckStack();
440 (void)nCurrentStruct;
441 #endif
444 void const* SwDrawContact::GetPDFAnchorStructureElementKey(SdrObject const& rObj)
446 SwFrame const*const pAnchorFrame(GetAnchoredObj(&rObj)->GetAnchorFrame());
447 return pAnchorFrame ? lcl_GetKeyFromFrame(*pAnchorFrame) : nullptr;
450 bool SwTaggedPDFHelper::CheckReopenTag()
452 bool bRet = false;
453 void const* pReopenKey(nullptr);
454 bool bContinue = false; // in some cases we just have to reopen a tag without early returning
456 if ( mpFrameInfo )
458 const SwFrame& rFrame = mpFrameInfo->mrFrame;
459 const SwFrame* pKeyFrame = nullptr;
461 // Reopen an existing structure element if
462 // - rFrame is not the first page frame (reopen Document tag)
463 // - rFrame is a follow frame (reopen Master tag)
464 // - rFrame is a fly frame anchored at content (reopen Anchor paragraph tag)
465 // - rFrame is a fly frame anchored at page (reopen Document tag)
466 // - rFrame is a follow flow row (reopen TableRow tag)
467 // - rFrame is a cell frame in a follow flow row (reopen TableData tag)
468 if ( ( rFrame.IsPageFrame() && static_cast<const SwPageFrame&>(rFrame).GetPrev() ) ||
469 ( rFrame.IsFlowFrame() && SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() ) ||
470 (rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetMaster()) ||
471 ( rFrame.IsRowFrame() && rFrame.IsInFollowFlowRow() ) ||
472 ( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetPrevCellLeaf() ) )
474 pKeyFrame = &rFrame;
476 else if (rFrame.IsFlyFrame() && !mpFrameInfo->m_isLink)
478 const SwFormatAnchor& rAnchor =
479 static_cast<const SwFlyFrame*>(&rFrame)->GetFormat()->GetAnchor();
480 if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) ||
481 (RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) ||
482 (RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()))
484 pKeyFrame = static_cast<const SwFlyFrame&>(rFrame).GetAnchorFrame();
485 bContinue = true;
489 if ( pKeyFrame )
491 void const*const pKey = lcl_GetKeyFromFrame(*pKeyFrame);
492 FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
493 if (rFrameTagSet.find(pKey) != rFrameTagSet.end()
494 || rFrame.IsFlyFrame()) // for hell layer flys
496 pReopenKey = pKey;
501 if (pReopenKey)
503 // note: it would be possible to get rid of the SetCurrentStructureElement()
504 // - which is quite ugly - for most cases by recreating the parents until the
505 // current ancestor, but there are special cases cell frame rowspan > 1 follow
506 // and footnote frame follow where the parent of the follow is different from
507 // the parent of the first one, and so in PDFExtOutDevData the wrong parent
508 // would be restored and used for next elements.
509 m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement();
510 sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pReopenKey);
511 mpPDFExtOutDevData->SetCurrentStructureElement(id);
513 bRet = true;
516 return bRet && !bContinue;
519 void SwTaggedPDFHelper::CheckRestoreTag() const
521 if ( m_nRestoreCurrentTag != -1 )
523 mpPDFExtOutDevData->SetCurrentStructureElement( m_nRestoreCurrentTag );
525 #if OSL_DEBUG_LEVEL > 1
526 aStructStack.pop_back();
527 #endif
531 void SwTaggedPDFHelper::OpenTagImpl(void const*const pKey)
533 sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pKey);
534 mpPDFExtOutDevData->BeginStructureElement(id);
535 ++m_nEndStructureElement;
537 #if OSL_DEBUG_LEVEL > 1
538 aStructStack.push_back( 99 );
539 #endif
542 sal_Int32 SwTaggedPDFHelper::BeginTagImpl(void const*const pKey,
543 vcl::PDFWriter::StructElement const eType, const OUString& rString)
545 // write new tag
546 const sal_Int32 nId = mpPDFExtOutDevData->EnsureStructureElement(pKey);
547 mpPDFExtOutDevData->InitStructureElement(nId, eType, rString);
548 mpPDFExtOutDevData->BeginStructureElement(nId);
549 ++m_nEndStructureElement;
551 #if OSL_DEBUG_LEVEL > 1
552 aStructStack.push_back( o3tl::narrowing<sal_uInt16>(eType) );
553 #endif
555 return nId;
558 void SwTaggedPDFHelper::BeginTag( vcl::PDFWriter::StructElement eType, const OUString& rString )
560 void const* pKey(nullptr);
562 if (mpFrameInfo)
564 const SwFrame& rFrame = mpFrameInfo->mrFrame;
566 if ( ( rFrame.IsPageFrame() && !static_cast<const SwPageFrame&>(rFrame).GetPrev() ) ||
567 ( rFrame.IsFlowFrame() && !SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() && SwFlowFrame::CastFlowFrame(&rFrame)->HasFollow() ) ||
568 rFrame.IsSctFrame() || // all of them, so that opening parent sections works
569 ( rFrame.IsTextFrame() && rFrame.GetDrawObjs() ) ||
570 (rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetFollow()) ||
571 ( rFrame.IsRowFrame() && rFrame.IsInSplitTableRow() ) ||
572 ( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetNextCellLeaf() ) )
574 pKey = lcl_GetKeyFromFrame(rFrame);
576 if (pKey)
578 FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
579 assert(rFrameTagSet.find(pKey) == rFrameTagSet.end());
580 rFrameTagSet.emplace(pKey);
585 sal_Int32 const nId = BeginTagImpl(pKey, eType, rString);
587 // Store the id of the current structure element if
588 // - it is a list structure element
589 // - it is a list body element with children
590 // - rFrame is the first page frame
591 // - rFrame is a master frame
592 // - rFrame has objects anchored to it
593 // - rFrame is a row frame or cell frame in a split table row
595 if ( mpNumInfo )
597 const SwTextFrame& rTextFrame = mpNumInfo->mrFrame;
598 SwTextNode const*const pTextNd = rTextFrame.GetTextNodeForParaProps();
599 const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame());
601 if ( vcl::PDFWriter::List == eType )
603 NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
604 rNumListIdMap[ pNodeNum ] = nId;
606 else if ( vcl::PDFWriter::LIBody == eType )
608 NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
609 rNumListBodyIdMap[ pNodeNum ] = nId;
613 SetAttributes( eType );
616 void SwTaggedPDFHelper::EndTag()
618 mpPDFExtOutDevData->EndStructureElement();
620 #if OSL_DEBUG_LEVEL > 1
621 aStructStack.pop_back();
622 #endif
625 namespace {
627 // link the link annotation to the link structured element
628 void LinkLinkLink(vcl::PDFExtOutDevData & rPDFExtOutDevData, SwRect const& rRect)
630 const LinkIdMap& rLinkIdMap(rPDFExtOutDevData.GetSwPDFState()->m_LinkIdMap);
631 const Point aCenter = rRect.Center();
632 auto aIter = std::find_if(rLinkIdMap.begin(), rLinkIdMap.end(),
633 [&aCenter](const IdMapEntry& rEntry) { return rEntry.first.Contains(aCenter); });
634 if (aIter != rLinkIdMap.end())
636 sal_Int32 nLinkId = (*aIter).second;
637 rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::PDFWriter::LinkAnnotation, nLinkId);
642 // Sets the attributes according to the structure type.
643 void SwTaggedPDFHelper::SetAttributes( vcl::PDFWriter::StructElement eType )
645 sal_Int32 nVal;
648 * ATTRIBUTES FOR BLSE
650 if ( mpFrameInfo )
652 vcl::PDFWriter::StructAttributeValue eVal;
653 const SwFrame* pFrame = &mpFrameInfo->mrFrame;
654 SwRectFnSet aRectFnSet(pFrame);
656 bool bPlacement = false;
657 bool bWritingMode = false;
658 bool bSpaceBefore = false;
659 bool bSpaceAfter = false;
660 bool bStartIndent = false;
661 bool bEndIndent = false;
662 bool bTextIndent = false;
663 bool bTextAlign = false;
664 bool bWidth = false;
665 bool bHeight = false;
666 bool bBox = false;
667 bool bRowSpan = false;
668 bool bAltText = false;
670 // Check which attributes to set:
672 switch ( eType )
674 case vcl::PDFWriter::Document :
675 bWritingMode = true;
676 break;
678 case vcl::PDFWriter::Note:
679 bPlacement = true;
680 break;
682 case vcl::PDFWriter::Table :
683 bPlacement =
684 bWritingMode =
685 bSpaceBefore =
686 bSpaceAfter =
687 bStartIndent =
688 bEndIndent =
689 bWidth =
690 bHeight =
691 bBox = true;
692 break;
694 case vcl::PDFWriter::TableRow :
695 bPlacement =
696 bWritingMode = true;
697 break;
699 case vcl::PDFWriter::TableHeader :
700 mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope, vcl::PDFWriter::Column);
701 [[fallthrough]];
702 case vcl::PDFWriter::TableData :
703 bPlacement =
704 bWritingMode =
705 bWidth =
706 bHeight =
707 bRowSpan = true;
708 break;
710 case vcl::PDFWriter::Caption:
711 if (pFrame->IsSctFrame())
713 break;
715 [[fallthrough]];
716 case vcl::PDFWriter::H1 :
717 case vcl::PDFWriter::H2 :
718 case vcl::PDFWriter::H3 :
719 case vcl::PDFWriter::H4 :
720 case vcl::PDFWriter::H5 :
721 case vcl::PDFWriter::H6 :
722 case vcl::PDFWriter::Paragraph :
723 case vcl::PDFWriter::Heading :
724 case vcl::PDFWriter::BlockQuote :
726 bPlacement =
727 bWritingMode =
728 bSpaceBefore =
729 bSpaceAfter =
730 bStartIndent =
731 bEndIndent =
732 bTextIndent =
733 bTextAlign = true;
734 break;
736 case vcl::PDFWriter::Formula :
737 case vcl::PDFWriter::Figure :
738 bAltText =
739 bPlacement =
740 bWidth =
741 bHeight =
742 bBox = true;
743 break;
745 case vcl::PDFWriter::Division:
746 if (pFrame->IsFlyFrame()) // this can be something else too
748 bAltText = true;
749 bBox = true;
751 break;
753 case vcl::PDFWriter::NonStructElement:
754 if (pFrame->IsHeaderFrame() || pFrame->IsFooterFrame())
756 // ISO 14289-1:2014, Clause: 7.8
757 mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Type, vcl::PDFWriter::Pagination);
758 mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Subtype,
759 pFrame->IsHeaderFrame()
760 ? vcl::PDFWriter::Header
761 : vcl::PDFWriter::Footer);
763 break;
765 default :
766 break;
769 // Set the attributes:
771 if ( bPlacement )
773 bool bIsFigureInline = false;
774 if (vcl::PDFWriter::Figure == eType)
776 const SwFrame* pKeyFrame = static_cast<const SwFlyFrame&>(*pFrame).GetAnchorFrame();
777 if (const SwLayoutFrame* pUpperFrame = pKeyFrame->GetUpper())
778 if (pUpperFrame->GetType() == SwFrameType::Body)
779 bIsFigureInline = true;
782 eVal = vcl::PDFWriter::TableHeader == eType || vcl::PDFWriter::TableData == eType
783 || bIsFigureInline
784 ? vcl::PDFWriter::Inline
785 : vcl::PDFWriter::Block;
787 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::Placement, eVal );
790 if ( bWritingMode )
792 eVal = pFrame->IsVertical() ?
793 vcl::PDFWriter::TbRl :
794 pFrame->IsRightToLeft() ?
795 vcl::PDFWriter::RlTb :
796 vcl::PDFWriter::LrTb;
798 if ( vcl::PDFWriter::LrTb != eVal )
799 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::WritingMode, eVal );
802 if ( bSpaceBefore )
804 nVal = aRectFnSet.GetTopMargin(*pFrame);
805 if ( 0 != nVal )
806 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::SpaceBefore, nVal );
809 if ( bSpaceAfter )
811 nVal = aRectFnSet.GetBottomMargin(*pFrame);
812 if ( 0 != nVal )
813 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::SpaceAfter, nVal );
816 if ( bStartIndent )
818 nVal = aRectFnSet.GetLeftMargin(*pFrame);
819 if ( 0 != nVal )
820 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::StartIndent, nVal );
823 if ( bEndIndent )
825 nVal = aRectFnSet.GetRightMargin(*pFrame);
826 if ( 0 != nVal )
827 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::EndIndent, nVal );
830 if ( bTextIndent )
832 OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" );
833 const SvxFirstLineIndentItem& rFirstLine(
834 static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet().GetFirstLineIndent());
835 nVal = rFirstLine.ResolveTextFirstLineOffset({});
836 if ( 0 != nVal )
837 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::TextIndent, nVal );
840 if ( bTextAlign )
842 OSL_ENSURE( pFrame->IsTextFrame(), "Frame type <-> tag attribute mismatch" );
843 const SwAttrSet& aSet = static_cast<const SwTextFrame*>(pFrame)->GetTextNodeForParaProps()->GetSwAttrSet();
844 const SvxAdjust nAdjust = aSet.GetAdjust().GetAdjust();
845 if ( SvxAdjust::Block == nAdjust || SvxAdjust::Center == nAdjust ||
846 ( (pFrame->IsRightToLeft() && SvxAdjust::Left == nAdjust) ||
847 (!pFrame->IsRightToLeft() && SvxAdjust::Right == nAdjust) ) )
849 eVal = SvxAdjust::Block == nAdjust ?
850 vcl::PDFWriter::Justify :
851 SvxAdjust::Center == nAdjust ?
852 vcl::PDFWriter::Center :
853 vcl::PDFWriter::End;
855 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextAlign, eVal );
859 // ISO 14289-1:2014, Clause: 7.3
860 // ISO 14289-1:2014, Clause: 7.7
861 // For images (but not embedded objects), an ObjectInfoPrimitive2D is
862 // created, but it's not evaluated by VclMetafileProcessor2D any more;
863 // that would require producing StructureTagPrimitive2D here but that
864 // looks impossible so instead duplicate the code that sets the Alt
865 // text here again.
866 if (bAltText)
868 SwFlyFrameFormat const& rFly(*static_cast<SwFlyFrame const*>(pFrame)->GetFormat());
869 OUString const sep(
870 (rFly.GetObjTitle().isEmpty() || rFly.GetObjDescription().isEmpty())
871 ? OUString() : u" - "_ustr);
872 OUString const altText(rFly.GetObjTitle() + sep + rFly.GetObjDescription());
873 if (!altText.isEmpty())
875 mpPDFExtOutDevData->SetAlternateText(altText);
879 if ( bWidth )
881 nVal = aRectFnSet.GetWidth(pFrame->getFrameArea());
882 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Width, nVal );
885 if ( bHeight )
887 nVal = aRectFnSet.GetHeight(pFrame->getFrameArea());
888 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Height, nVal );
891 if ( bBox )
893 // BBox only for non-split tables:
894 if ( vcl::PDFWriter::Table != eType ||
895 ( pFrame->IsTabFrame() &&
896 !static_cast<const SwTabFrame*>(pFrame)->IsFollow() &&
897 !static_cast<const SwTabFrame*>(pFrame)->HasFollow() ) )
899 mpPDFExtOutDevData->SetStructureBoundingBox(pFrame->getFrameArea().SVRect());
903 if ( bRowSpan )
905 if ( pFrame->IsCellFrame() )
907 const SwCellFrame* pThisCell = static_cast<const SwCellFrame*>(pFrame);
908 nVal = pThisCell->GetTabBox()->getRowSpan();
909 if ( nVal > 1 )
910 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::RowSpan, nVal );
912 // calculate colspan:
913 const SwTabFrame* pTabFrame = pThisCell->FindTabFrame();
914 const SwTable* pTable = pTabFrame->GetTable();
916 SwRectFnSet fnRectX(pTabFrame);
918 const TableColumnsMapEntry& rCols(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap[pTable]);
920 const tools::Long nLeft = fnRectX.GetLeft(pThisCell->getFrameArea());
921 const tools::Long nRight = fnRectX.GetRight(pThisCell->getFrameArea());
922 const TableColumnsMapEntry::const_iterator aLeftIter = rCols.find( nLeft );
923 const TableColumnsMapEntry::const_iterator aRightIter = rCols.find( nRight );
925 OSL_ENSURE( aLeftIter != rCols.end() && aRightIter != rCols.end(), "Colspan trouble" );
926 if ( aLeftIter != rCols.end() && aRightIter != rCols.end() )
928 nVal = std::distance( aLeftIter, aRightIter );
929 if ( nVal > 1 )
930 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::ColSpan, nVal );
935 if (mpFrameInfo->m_isLink)
937 SwRect const aRect(mpFrameInfo->mrFrame.getFrameArea());
938 LinkLinkLink(*mpPDFExtOutDevData, aRect);
943 * ATTRIBUTES FOR ILSE
945 else if ( mpPorInfo )
947 const SwLinePortion* pPor = &mpPorInfo->mrPor;
948 const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo();
950 bool bActualText = false;
951 bool bBaselineShift = false;
952 bool bTextDecorationType = false;
953 bool bLinkAttribute = false;
954 bool bAnnotAttribute = false;
955 bool bLanguage = false;
957 // Check which attributes to set:
959 switch ( eType )
961 case vcl::PDFWriter::Span :
962 case vcl::PDFWriter::Quote :
963 case vcl::PDFWriter::Code :
964 if( PortionType::HyphenStr == pPor->GetWhichPor() || PortionType::SoftHyphenStr == pPor->GetWhichPor() ||
965 PortionType::Hyphen == pPor->GetWhichPor() || PortionType::SoftHyphen == pPor->GetWhichPor() )
966 bActualText = true;
967 else
969 bBaselineShift =
970 bTextDecorationType =
971 bLanguage = true;
973 break;
975 case vcl::PDFWriter::Link :
976 case vcl::PDFWriter::BibEntry :
977 bTextDecorationType =
978 bBaselineShift =
979 bLinkAttribute =
980 bLanguage = true;
981 break;
983 case vcl::PDFWriter::RT:
985 SwRubyPortion const*const pRuby(static_cast<SwRubyPortion const*>(pPor));
986 vcl::PDFWriter::StructAttributeValue nAlign = {};
987 switch (pRuby->GetAdjustment())
989 case text::RubyAdjust_LEFT:
990 nAlign = vcl::PDFWriter::RStart;
991 break;
992 case text::RubyAdjust_CENTER:
993 nAlign = vcl::PDFWriter::RCenter;
994 break;
995 case text::RubyAdjust_RIGHT:
996 nAlign = vcl::PDFWriter::REnd;
997 break;
998 case text::RubyAdjust_BLOCK:
999 nAlign = vcl::PDFWriter::RJustify;
1000 break;
1001 case text::RubyAdjust_INDENT_BLOCK:
1002 nAlign = vcl::PDFWriter::RDistribute;
1003 break;
1004 default:
1005 assert(false);
1006 break;
1008 ::std::optional<vcl::PDFWriter::StructAttributeValue> oPos;
1009 switch (pRuby->GetRubyPosition())
1011 case RubyPosition::ABOVE:
1012 oPos = vcl::PDFWriter::RBefore;
1013 break;
1014 case RubyPosition::BELOW:
1015 oPos = vcl::PDFWriter::RAfter;
1016 break;
1017 case RubyPosition::RIGHT:
1018 break; // no such thing???
1020 mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::RubyAlign, nAlign);
1021 if (oPos)
1023 mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::RubyPosition, *oPos);
1026 break;
1028 case vcl::PDFWriter::Annot:
1029 bAnnotAttribute =
1030 bLanguage = true;
1031 break;
1033 default:
1034 break;
1037 if ( bActualText )
1039 OUString aActualText;
1040 if (pPor->GetWhichPor() == PortionType::SoftHyphen || pPor->GetWhichPor() == PortionType::Hyphen)
1041 aActualText = OUString(u'\x00ad'); // soft hyphen
1042 else
1043 aActualText = rInf.GetText().copy(sal_Int32(rInf.GetIdx()), sal_Int32(pPor->GetLen()));
1044 mpPDFExtOutDevData->SetActualText( aActualText );
1047 if ( bBaselineShift )
1049 // TODO: Calculate correct values!
1050 nVal = rInf.GetFont()->GetEscapement();
1051 if ( nVal > 0 ) nVal = 33;
1052 else if ( nVal < 0 ) nVal = -33;
1054 if ( 0 != nVal )
1056 nVal = nVal * pPor->Height() / 100;
1057 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::BaselineShift, nVal );
1061 if ( bTextDecorationType )
1063 if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() )
1064 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Underline );
1065 if ( LINESTYLE_NONE != rInf.GetFont()->GetOverline() )
1066 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Overline );
1067 if ( STRIKEOUT_NONE != rInf.GetFont()->GetStrikeout() )
1068 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::LineThrough );
1069 if ( FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() )
1070 mpPDFExtOutDevData->SetStructureAttribute( vcl::PDFWriter::TextDecorationType, vcl::PDFWriter::Overline );
1073 if ( bLanguage )
1076 const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage();
1077 const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault);
1079 if ( nDefaultLang != nCurrentLanguage )
1080 mpPDFExtOutDevData->SetStructureAttributeNumerical( vcl::PDFWriter::Language, static_cast<sal_uInt16>(nCurrentLanguage) );
1083 if ( bLinkAttribute )
1085 SwRect aPorRect;
1086 rInf.CalcRect( *pPor, &aPorRect );
1087 LinkLinkLink(*mpPDFExtOutDevData, aPorRect);
1090 if (bAnnotAttribute)
1092 SwRect aPorRect;
1093 rInf.CalcRect(*pPor, &aPorRect);
1094 const NoteIdMap& rNoteIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap);
1095 const Point aCenter = aPorRect.Center();
1096 auto aIter = std::find_if(rNoteIdMap.begin(), rNoteIdMap.end(),
1097 [&aCenter](const IdMapEntry& rEntry)
1098 { return rEntry.first.Contains(aCenter); });
1099 if (aIter != rNoteIdMap.end())
1101 sal_Int32 nNoteId = (*aIter).second;
1102 mpPDFExtOutDevData->SetStructureAttributeNumerical(vcl::PDFWriter::NoteAnnotation,
1103 nNoteId);
1107 else if (mpNumInfo && eType == vcl::PDFWriter::List)
1109 SwTextFrame const& rFrame(mpNumInfo->mrFrame);
1110 SwTextNode const& rNode(*rFrame.GetTextNodeForParaProps());
1111 SwNumRule const*const pNumRule = rNode.GetNumRule();
1112 assert(pNumRule); // was required for List
1114 auto ToPDFListNumbering = [](SvxNumberFormat const& rFormat) {
1115 switch (rFormat.GetNumberingType())
1117 case css::style::NumberingType::CHARS_UPPER_LETTER:
1118 return vcl::PDFWriter::UpperAlpha;
1119 case css::style::NumberingType::CHARS_LOWER_LETTER:
1120 return vcl::PDFWriter::LowerAlpha;
1121 case css::style::NumberingType::ROMAN_UPPER:
1122 return vcl::PDFWriter::UpperRoman;
1123 case css::style::NumberingType::ROMAN_LOWER:
1124 return vcl::PDFWriter::LowerRoman;
1125 case css::style::NumberingType::ARABIC:
1126 return vcl::PDFWriter::Decimal;
1127 case css::style::NumberingType::CHAR_SPECIAL:
1128 switch (rFormat.GetBulletChar())
1130 case u'\u2022': case u'\uE12C': case u'\uE01E': case u'\uE437':
1131 return vcl::PDFWriter::Disc;
1132 case u'\u2218': case u'\u25CB': case u'\u25E6':
1133 return vcl::PDFWriter::Circle;
1134 case u'\u25A0': case u'\u25AA': case u'\uE00A':
1135 return vcl::PDFWriter::Square;
1136 default:
1137 return vcl::PDFWriter::NONE;
1139 default: // the other 50 types
1140 return vcl::PDFWriter::NONE;
1144 // Note: for every level, BeginNumberedListStructureElements() produces
1145 // a separate List element, so even though in PDF this is limited to
1146 // the whole List we can just export the current level here.
1147 vcl::PDFWriter::StructAttributeValue const value(
1148 ToPDFListNumbering(pNumRule->Get(rNode.GetActualListLevel())));
1149 // ISO 14289-1:2014, Clause: 7.6
1150 mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::ListNumbering, value);
1154 void SwTaggedPDFHelper::BeginNumberedListStructureElements()
1156 OSL_ENSURE( mpNumInfo, "List without mpNumInfo?" );
1157 if ( !mpNumInfo )
1158 return;
1160 const SwFrame& rFrame = mpNumInfo->mrFrame;
1161 assert(rFrame.IsTextFrame());
1162 const SwTextFrame& rTextFrame = static_cast<const SwTextFrame&>(rFrame);
1164 // Lowers of NonStructureElements should not be considered:
1165 if (lcl_IsInNonStructEnv(rTextFrame))
1166 return;
1168 // do it for the first one in the follow chain that has content
1169 for (SwFlowFrame const* pPrecede = rTextFrame.GetPrecede(); pPrecede; pPrecede = pPrecede->GetPrecede())
1171 SwTextFrame const*const pText(static_cast<SwTextFrame const*>(pPrecede));
1172 if (!pText->HasPara() || pText->GetPara()->HasContentPortions())
1174 return;
1178 const SwTextNode *const pTextNd = rTextFrame.GetTextNodeForParaProps();
1179 const SwNumRule* pNumRule = pTextNd->GetNumRule();
1180 const SwNodeNum* pNodeNum = pTextNd->GetNum(rTextFrame.getRootFrame());
1182 const bool bNumbered = !pTextNd->IsOutline() && pNodeNum && pNodeNum->GetParent() && pNumRule;
1184 // Check, if we have to reopen a list or a list body:
1185 // First condition:
1186 // Paragraph is numbered/bulleted
1187 if ( !bNumbered )
1188 return;
1190 const SwNumberTreeNode* pParent = pNodeNum->GetParent();
1191 const bool bSameNumbering = lcl_HasPreviousParaSameNumRule(rTextFrame, *pTextNd);
1193 // Second condition: current numbering is not 'interrupted'
1194 if ( bSameNumbering )
1196 sal_Int32 nReopenTag = -1;
1198 // Two cases:
1199 // 1. We have to reopen an existing list body tag:
1200 // - If the current node is either the first child of its parent
1201 // and its level > 1 or
1202 // - Numbering should restart at the current node and its level > 1
1203 // - The current item has no label
1204 const bool bNewSubListStart = pParent->GetParent() && (pParent->IsFirst( pNodeNum ) || pTextNd->IsListRestart() );
1205 const bool bNoLabel = !pTextNd->IsCountedInList() && !pTextNd->IsListRestart();
1206 if ( bNewSubListStart || bNoLabel )
1208 // Fine, we try to reopen the appropriate list body
1209 NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
1211 if ( bNewSubListStart )
1213 // The list body tag associated with the parent has to be reopened
1214 // to start a new list inside the list body
1215 NumListBodyIdMap::const_iterator aIter;
1218 aIter = rNumListBodyIdMap.find( pParent );
1219 while ( aIter == rNumListBodyIdMap.end() && nullptr != ( pParent = pParent->GetParent() ) );
1221 if ( aIter != rNumListBodyIdMap.end() )
1222 nReopenTag = (*aIter).second;
1224 else // if(bNoLabel)
1226 // The list body tag of a 'counted' predecessor has to be reopened
1227 const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true);
1228 while ( pPrevious )
1230 if ( pPrevious->IsCounted())
1232 // get id of list body tag
1233 const NumListBodyIdMap::const_iterator aIter = rNumListBodyIdMap.find( pPrevious );
1234 if ( aIter != rNumListBodyIdMap.end() )
1236 nReopenTag = (*aIter).second;
1237 break;
1240 pPrevious = pPrevious->GetPred(true);
1244 // 2. We have to reopen an existing list tag:
1245 else if ( !pParent->IsFirst( pNodeNum ) && !pTextNd->IsListRestart() )
1247 // any other than the first node in a list level has to reopen the current
1248 // list. The current list is associated in a map with the first child of the list:
1249 NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
1251 // Search backwards and check if any of the previous nodes has a list associated with it:
1252 const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true);
1253 while ( pPrevious )
1255 // get id of list tag
1256 const NumListIdMap::const_iterator aIter = rNumListIdMap.find( pPrevious );
1257 if ( aIter != rNumListIdMap.end() )
1259 nReopenTag = (*aIter).second;
1260 break;
1263 pPrevious = pPrevious->GetPred(true);
1267 if ( -1 != nReopenTag )
1269 m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement();
1270 mpPDFExtOutDevData->SetCurrentStructureElement( nReopenTag );
1272 #if OSL_DEBUG_LEVEL > 1
1273 aStructStack.push_back( 99 );
1274 #endif
1277 else
1279 // clear list maps in case a list has been interrupted
1280 NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
1281 rNumListIdMap.clear();
1282 NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
1283 rNumListBodyIdMap.clear();
1286 // New tags:
1287 const bool bNewListTag = (pNodeNum->GetParent()->IsFirst( pNodeNum ) || pTextNd->IsListRestart() || !bSameNumbering);
1288 const bool bNewItemTag = bNewListTag || pTextNd->IsCountedInList(); // If the text node is not counted, we do not start a new list item:
1290 if ( bNewListTag )
1291 BeginTag( vcl::PDFWriter::List, aListString );
1293 if ( bNewItemTag )
1295 BeginTag( vcl::PDFWriter::ListItem, aListItemString );
1296 assert(rTextFrame.GetPara());
1297 // check whether to open LBody now or delay until after Lbl
1298 if (!rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering))
1300 BeginTag(vcl::PDFWriter::LIBody, aListBodyString);
1305 void SwTaggedPDFHelper::BeginBlockStructureElements()
1307 const SwFrame* pFrame = &mpFrameInfo->mrFrame;
1309 // Lowers of NonStructureElements should not be considered:
1311 if (lcl_IsInNonStructEnv(*pFrame) && !pFrame->IsFlyFrame())
1312 return;
1314 // Check if we have to reopen an existing structure element.
1315 // This has to be done e.g., if pFrame is a follow frame.
1316 if ( CheckReopenTag() )
1317 return;
1319 sal_uInt16 nPDFType = USHRT_MAX;
1320 OUString aPDFType;
1322 switch ( pFrame->GetType() )
1325 * GROUPING ELEMENTS
1328 case SwFrameType::Page :
1330 // Document: Document
1332 nPDFType = vcl::PDFWriter::Document;
1333 aPDFType = aDocumentString;
1334 break;
1336 case SwFrameType::Header :
1337 case SwFrameType::Footer :
1339 // Header, Footer: NonStructElement
1341 nPDFType = vcl::PDFWriter::NonStructElement;
1342 break;
1344 case SwFrameType::FtnCont :
1346 // Footnote container: Division
1348 nPDFType = vcl::PDFWriter::Division;
1349 aPDFType = aDivString;
1350 break;
1352 case SwFrameType::Ftn :
1354 // Footnote frame: Note
1356 // Note: vcl::PDFWriter::Note is actually a ILSE. Nevertheless
1357 // we treat it like a grouping element!
1358 nPDFType = vcl::PDFWriter::Note;
1359 aPDFType = aNoteString;
1360 break;
1362 case SwFrameType::Section :
1364 // Section: TOX, Index, or Sect
1367 const SwSection* pSection =
1368 static_cast<const SwSectionFrame*>(pFrame)->GetSection();
1370 // open all parent sections, so that the SEs of sections
1371 // are nested in the same way as their SwSectionNodes
1372 std::vector<SwSection const*> parents;
1373 for (SwSection const* pParent = pSection->GetParent();
1374 pParent != nullptr; pParent = pParent->GetParent())
1376 parents.push_back(pParent);
1378 for (auto it = parents.rbegin(); it != parents.rend(); ++it)
1380 // key is the SwSection - see lcl_GetKeyFromFrame()
1381 OpenTagImpl(*it);
1384 FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
1385 if (rFrameTagSet.find(pSection) != rFrameTagSet.end())
1387 // special case: section may have *multiple* master frames,
1388 // when it is interrupted by nested section - reopen!
1389 OpenTagImpl(pSection);
1390 break;
1392 else if (SectionType::ToxHeader == pSection->GetType())
1394 nPDFType = vcl::PDFWriter::Caption;
1395 aPDFType = aCaptionString;
1397 else if (SectionType::ToxContent == pSection->GetType())
1399 const SwTOXBase* pTOXBase = pSection->GetTOXBase();
1400 if ( pTOXBase )
1402 if ( TOX_INDEX == pTOXBase->GetType() )
1404 nPDFType = vcl::PDFWriter::Index;
1405 aPDFType = aIndexString;
1407 else
1409 nPDFType = vcl::PDFWriter::TOC;
1410 aPDFType = aTOCString;
1414 else if ( SectionType::Content == pSection->GetType() )
1416 nPDFType = vcl::PDFWriter::Section;
1417 aPDFType = aSectString;
1420 break;
1423 * BLOCK-LEVEL STRUCTURE ELEMENTS
1426 case SwFrameType::Txt :
1428 SwTextFrame const& rTextFrame(*static_cast<const SwTextFrame*>(pFrame));
1429 const SwTextNode *const pTextNd(rTextFrame.GetTextNodeForParaProps());
1431 // lazy open LBody after Lbl
1432 if (!pTextNd->IsOutline()
1433 && rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering))
1435 sal_Int32 const nId = BeginTagImpl(nullptr, vcl::PDFWriter::LIBody, aListBodyString);
1436 SwNodeNum const*const pNodeNum(pTextNd->GetNum(rTextFrame.getRootFrame()));
1437 NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
1438 rNumListBodyIdMap[ pNodeNum ] = nId;
1441 const SwFormat* pTextFormat = pTextNd->GetFormatColl();
1442 const SwFormat* pParentTextFormat = pTextFormat ? pTextFormat->DerivedFrom() : nullptr;
1444 OUString sStyleName;
1445 OUString sParentStyleName;
1447 if ( pTextFormat)
1448 SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
1449 if ( pParentTextFormat)
1450 SwStyleNameMapper::FillProgName( pParentTextFormat->GetName(), sParentStyleName, SwGetPoolIdFromName::TxtColl );
1452 // This is the default. If the paragraph could not be mapped to
1453 // any of the standard pdf tags, we write a user defined tag
1454 // <stylename> with role = P
1455 nPDFType = vcl::PDFWriter::Paragraph;
1456 aPDFType = sStyleName;
1458 // Quotations: BlockQuote
1460 if (sStyleName == aQuotations)
1462 nPDFType = vcl::PDFWriter::BlockQuote;
1463 aPDFType = aBlockQuoteString;
1466 // Caption: Caption
1468 else if (sStyleName == aCaption)
1470 nPDFType = vcl::PDFWriter::Caption;
1471 aPDFType = aCaptionString;
1474 // Caption: Caption
1476 else if (sParentStyleName == aCaption)
1478 nPDFType = vcl::PDFWriter::Caption;
1479 aPDFType = sStyleName + aCaptionString;
1482 // Heading: H
1484 else if (sStyleName == aHeading)
1486 nPDFType = vcl::PDFWriter::Heading;
1487 aPDFType = aHString;
1490 // Heading: H1 - H6
1492 if (int nRealLevel = pTextNd->GetAttrOutlineLevel() - 1;
1493 nRealLevel >= 0
1494 && !pTextNd->IsInRedlines()
1495 && sw::IsParaPropsNode(*pFrame->getRootFrame(), *pTextNd))
1497 switch(nRealLevel)
1499 case 0 :
1500 aPDFType = aH1String;
1501 break;
1502 case 1 :
1503 aPDFType = aH2String;
1504 break;
1505 case 2 :
1506 aPDFType = aH3String;
1507 break;
1508 case 3 :
1509 aPDFType = aH4String;
1510 break;
1511 case 4 :
1512 aPDFType = aH5String;
1513 break;
1514 case 5:
1515 aPDFType = aH6String;
1516 break;
1517 case 6:
1518 aPDFType = aH7String;
1519 break;
1520 case 7:
1521 aPDFType = aH8String;
1522 break;
1523 case 8:
1524 aPDFType = aH9String;
1525 break;
1526 case 9:
1527 aPDFType = aH10String;
1528 break;
1529 default:
1530 assert(false);
1531 break;
1534 // PDF/UA allows unlimited headings, but PDF only up to H6
1535 // ... and apparently the extra H7.. must be declared in
1536 // RoleMap, or veraPDF complains.
1537 nRealLevel = std::min(nRealLevel, 5);
1538 nPDFType = o3tl::narrowing<sal_uInt16>(vcl::PDFWriter::H1 + nRealLevel);
1541 // Section: TOCI
1543 else if ( pFrame->IsInSct() )
1545 const SwSectionFrame* pSctFrame = pFrame->FindSctFrame();
1546 const SwSection* pSection = pSctFrame->GetSection();
1548 if ( SectionType::ToxContent == pSection->GetType() )
1550 const SwTOXBase* pTOXBase = pSection->GetTOXBase();
1551 if ( pTOXBase && TOX_INDEX != pTOXBase->GetType() )
1553 // Special case: Open additional TOCI tag:
1554 BeginTagImpl(nullptr, vcl::PDFWriter::TOCI, aTOCIString);
1559 break;
1561 case SwFrameType::Tab :
1563 // TabFrame: Table
1565 nPDFType = vcl::PDFWriter::Table;
1566 aPDFType = aTableString;
1569 // set up table column data:
1570 const SwTabFrame* pTabFrame = static_cast<const SwTabFrame*>(pFrame);
1571 const SwTable* pTable = pTabFrame->GetTable();
1573 TableColumnsMap& rTableColumnsMap(mpPDFExtOutDevData->GetSwPDFState()->m_TableColumnsMap);
1574 const TableColumnsMap::const_iterator aIter = rTableColumnsMap.find( pTable );
1576 if ( aIter == rTableColumnsMap.end() )
1578 SwRectFnSet aRectFnSet(pTabFrame);
1579 TableColumnsMapEntry& rCols = rTableColumnsMap[ pTable ];
1581 const SwTabFrame* pMasterFrame = pTabFrame->IsFollow() ? pTabFrame->FindMaster( true ) : pTabFrame;
1583 while ( pMasterFrame )
1585 const SwRowFrame* pRowFrame = static_cast<const SwRowFrame*>(pMasterFrame->GetLower());
1587 while ( pRowFrame )
1589 const SwFrame* pCellFrame = pRowFrame->GetLower();
1591 const tools::Long nLeft = aRectFnSet.GetLeft(pCellFrame->getFrameArea());
1592 rCols.insert( nLeft );
1594 while ( pCellFrame )
1596 const tools::Long nRight = aRectFnSet.GetRight(pCellFrame->getFrameArea());
1597 rCols.insert( nRight );
1598 pCellFrame = pCellFrame->GetNext();
1600 pRowFrame = static_cast<const SwRowFrame*>(pRowFrame->GetNext());
1602 pMasterFrame = pMasterFrame->GetFollow();
1607 break;
1610 * TABLE ELEMENTS
1613 case SwFrameType::Row :
1615 // RowFrame: TR
1617 if ( !static_cast<const SwRowFrame*>(pFrame)->IsRepeatedHeadline() )
1619 nPDFType = vcl::PDFWriter::TableRow;
1620 aPDFType = aTRString;
1622 else
1624 nPDFType = vcl::PDFWriter::NonStructElement;
1626 break;
1628 case SwFrameType::Cell :
1630 // CellFrame: TH, TD
1633 const SwTabFrame* pTable = static_cast<const SwCellFrame*>(pFrame)->FindTabFrame();
1634 if ( pTable->IsInHeadline( *pFrame ) || lcl_IsHeadlineCell( *static_cast<const SwCellFrame*>(pFrame) ) )
1636 nPDFType = vcl::PDFWriter::TableHeader;
1637 aPDFType = aTHString;
1639 else
1641 nPDFType = vcl::PDFWriter::TableData;
1642 aPDFType = aTDString;
1645 break;
1648 * ILLUSTRATION
1651 case SwFrameType::Fly :
1653 // FlyFrame: Figure, Formula, Control
1654 // fly in content or fly at page
1655 if (mpFrameInfo->m_isLink)
1656 { // tdf#154939 additional inner link element for flys
1657 nPDFType = vcl::PDFWriter::Link;
1658 aPDFType = aLinkString;
1660 else
1662 const SwFlyFrame* pFly = static_cast<const SwFlyFrame*>(pFrame);
1663 if (pFly->GetAnchorFrame()->FindFooterOrHeader() != nullptr
1664 || pFly->GetFrameFormat()->GetAttrSet().Get(RES_DECORATIVE).GetValue())
1666 nPDFType = vcl::PDFWriter::NonStructElement;
1668 else if (pFly->Lower() && pFly->Lower()->IsNoTextFrame())
1670 bool bFormula = false;
1672 const SwNoTextFrame* pNoTextFrame = static_cast<const SwNoTextFrame*>(pFly->Lower());
1673 SwOLENode* pOLENd = const_cast<SwOLENode*>(pNoTextFrame->GetNode()->GetOLENode());
1674 if ( pOLENd )
1676 SwOLEObj& aOLEObj = pOLENd->GetOLEObj();
1677 uno::Reference< embed::XEmbeddedObject > aRef = aOLEObj.GetOleRef();
1678 if ( aRef.is() )
1680 bFormula = 0 != SotExchange::IsMath( SvGlobalName( aRef->getClassID() ) );
1683 if ( bFormula )
1685 nPDFType = vcl::PDFWriter::Formula;
1686 aPDFType = aFormulaString;
1688 else
1690 nPDFType = vcl::PDFWriter::Figure;
1691 aPDFType = aFigureString;
1694 else
1696 nPDFType = vcl::PDFWriter::Division;
1697 aPDFType = aDivString;
1700 break;
1702 default: break;
1705 if ( USHRT_MAX != nPDFType )
1707 BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType );
1711 void SwTaggedPDFHelper::EndStructureElements()
1713 if (mpFrameInfo != nullptr)
1715 if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
1716 { // close span at end of paragraph
1717 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
1718 ++m_nEndStructureElement;
1720 if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
1721 { // close link at end of paragraph
1722 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
1723 ++m_nEndStructureElement;
1727 while ( m_nEndStructureElement > 0 )
1729 EndTag();
1730 --m_nEndStructureElement;
1733 CheckRestoreTag();
1736 void SwTaggedPDFHelper::EndCurrentLink(OutputDevice const& rOut)
1738 vcl::PDFExtOutDevData *const pPDFExtOutDevData(
1739 dynamic_cast<vcl::PDFExtOutDevData *>(rOut.GetExtOutDevData()));
1740 if (pPDFExtOutDevData && pPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
1742 pPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
1743 pPDFExtOutDevData->EndStructureElement();
1744 #if OSL_DEBUG_LEVEL > 1
1745 aStructStack.pop_back();
1746 #endif
1750 void SwTaggedPDFHelper::EndCurrentAll()
1752 if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
1754 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
1756 if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
1758 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
1762 void SwTaggedPDFHelper::EndCurrentSpan()
1764 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
1765 EndTag(); // close span
1768 void SwTaggedPDFHelper::CreateCurrentSpan(
1769 SwTextPaintInfo const& rInf, OUString const& rStyleName)
1771 assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan);
1772 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.emplace(
1773 SwEnhancedPDFState::Span{
1774 rInf.GetFont()->GetUnderline(),
1775 rInf.GetFont()->GetOverline(),
1776 rInf.GetFont()->GetStrikeout(),
1777 rInf.GetFont()->GetEmphasisMark(),
1778 rInf.GetFont()->GetEscapement(),
1779 rInf.GetFont()->GetActual(),
1780 rInf.GetFont()->GetLanguage(),
1781 rStyleName});
1782 // leave it open to let next portion decide to merge or close
1783 --m_nEndStructureElement;
1786 bool SwTaggedPDFHelper::CheckContinueSpan(
1787 SwTextPaintInfo const& rInf, std::u16string_view const rStyleName,
1788 SwTextAttr const*const pInetFormatAttr)
1790 // for now, don't create span inside of link - this should be very rare
1791 // situation and it looks complicated to implement.
1792 assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan
1793 || !mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
1794 if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
1796 if (pInetFormatAttr && pInetFormatAttr == *mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
1798 return true;
1800 else
1802 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
1803 EndTag();
1804 return false;
1807 if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan && pInetFormatAttr)
1809 EndCurrentSpan();
1810 return false;
1813 if (!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
1814 return false;
1816 SwEnhancedPDFState::Span const& rCurrent(*mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan);
1818 bool const ret(rCurrent.eUnderline == rInf.GetFont()->GetUnderline()
1819 && rCurrent.eOverline == rInf.GetFont()->GetOverline()
1820 && rCurrent.eStrikeout == rInf.GetFont()->GetStrikeout()
1821 && rCurrent.eFontEmphasis == rInf.GetFont()->GetEmphasisMark()
1822 && rCurrent.nEscapement == rInf.GetFont()->GetEscapement()
1823 && rCurrent.nScript == rInf.GetFont()->GetActual()
1824 && rCurrent.nLang == rInf.GetFont()->GetLanguage()
1825 && rCurrent.StyleName == rStyleName);
1826 if (!ret)
1828 EndCurrentSpan();
1830 return ret;
1833 void SwTaggedPDFHelper::BeginInlineStructureElements()
1835 const SwLinePortion* pPor = &mpPorInfo->mrPor;
1836 const SwTextPaintInfo& rInf = mpPorInfo->mrTextPainter.GetInfo();
1837 const SwTextFrame* pFrame = rInf.GetTextFrame();
1839 // Lowers of NonStructureElements should not be considered:
1841 if ( lcl_IsInNonStructEnv( *pFrame ) )
1842 return;
1844 std::pair<SwTextNode const*, sal_Int32> const pos(
1845 pFrame->MapViewToModel(rInf.GetIdx()));
1846 SwTextAttr const*const pInetFormatAttr =
1847 pos.first->GetTextAttrAt(pos.second, RES_TXTATR_INETFMT);
1849 OUString sStyleName;
1850 if (!pInetFormatAttr)
1852 std::vector<SwTextAttr *> const charAttrs(
1853 pos.first->GetTextAttrsAt(pos.second, RES_TXTATR_CHARFMT));
1854 // TODO: handle more than 1 char style?
1855 const SwCharFormat* pCharFormat = (charAttrs.size())
1856 ? (*charAttrs.begin())->GetCharFormat().GetCharFormat() : nullptr;
1857 if (pCharFormat)
1858 SwStyleNameMapper::FillProgName( pCharFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
1861 // note: ILSE may be nested, so only end the span if needed to start new one
1862 bool const isContinueSpan(CheckContinueSpan(rInf, sStyleName, pInetFormatAttr));
1864 sal_uInt16 nPDFType = USHRT_MAX;
1865 OUString aPDFType;
1867 switch ( pPor->GetWhichPor() )
1869 case PortionType::PostIts:
1870 if (!mpPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap.empty())
1872 nPDFType = vcl::PDFWriter::Annot;
1873 aPDFType = aAnnotString;
1875 break;
1877 case PortionType::Hyphen :
1878 case PortionType::SoftHyphen :
1879 // Check for alternative spelling:
1880 case PortionType::HyphenStr :
1881 case PortionType::SoftHyphenStr :
1882 nPDFType = vcl::PDFWriter::Span;
1883 aPDFType = aSpanString;
1884 break;
1886 case PortionType::Fly:
1887 // if a link is split by a fly overlap, then there will be multiple
1888 // annotations for the link, and hence there must be multiple SEs,
1889 // so every annotation has its own SE.
1890 if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
1892 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
1893 EndTag();
1895 break;
1897 case PortionType::Lay :
1898 case PortionType::Text :
1899 case PortionType::Para :
1901 // Check for Link:
1902 if( pInetFormatAttr )
1904 if (!isContinueSpan)
1906 nPDFType = vcl::PDFWriter::Link;
1907 aPDFType = aLinkString;
1908 assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
1909 mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace(pInetFormatAttr);
1910 // leave it open to let next portion decide to merge or close
1911 --m_nEndStructureElement;
1914 // Check for Quote/Code character style:
1915 else if (sStyleName == aQuotation)
1917 if (!isContinueSpan)
1919 nPDFType = vcl::PDFWriter::Quote;
1920 aPDFType = aQuoteString;
1921 CreateCurrentSpan(rInf, sStyleName);
1924 else if (sStyleName == aSourceText)
1926 if (!isContinueSpan)
1928 nPDFType = vcl::PDFWriter::Code;
1929 aPDFType = aCodeString;
1930 CreateCurrentSpan(rInf, sStyleName);
1933 else if (!isContinueSpan)
1935 const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage();
1936 const SwFontScript nFont = rInf.GetFont()->GetActual();
1937 const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault);
1939 if ( LINESTYLE_NONE != rInf.GetFont()->GetUnderline() ||
1940 LINESTYLE_NONE != rInf.GetFont()->GetOverline() ||
1941 STRIKEOUT_NONE != rInf.GetFont()->GetStrikeout() ||
1942 FontEmphasisMark::NONE != rInf.GetFont()->GetEmphasisMark() ||
1943 0 != rInf.GetFont()->GetEscapement() ||
1944 SwFontScript::Latin != nFont ||
1945 nCurrentLanguage != nDefaultLang ||
1946 !sStyleName.isEmpty())
1948 nPDFType = vcl::PDFWriter::Span;
1949 if (!sStyleName.isEmpty())
1950 aPDFType = sStyleName;
1951 else
1952 aPDFType = aSpanString;
1953 CreateCurrentSpan(rInf, sStyleName);
1957 break;
1959 case PortionType::Footnote :
1960 nPDFType = vcl::PDFWriter::Link;
1961 aPDFType = aLinkString;
1962 break;
1964 case PortionType::Field :
1966 // check field type:
1967 TextFrameIndex const nIdx = static_cast<const SwFieldPortion*>(pPor)->IsFollow()
1968 ? rInf.GetIdx() - TextFrameIndex(1)
1969 : rInf.GetIdx();
1970 const SwTextAttr* pHint = mpPorInfo->mrTextPainter.GetAttr( nIdx );
1971 if ( pHint && RES_TXTATR_FIELD == pHint->Which() )
1973 const SwField* pField = pHint->GetFormatField().GetField();
1974 if ( SwFieldIds::GetRef == pField->Which() )
1976 nPDFType = vcl::PDFWriter::Link;
1977 aPDFType = aLinkString;
1979 else if ( SwFieldIds::TableOfAuthorities == pField->Which() )
1981 nPDFType = vcl::PDFWriter::BibEntry;
1982 aPDFType = aBibEntryString;
1986 break;
1988 case PortionType::Multi:
1990 SwMultiPortion const*const pMulti(static_cast<SwMultiPortion const*>(pPor));
1991 if (pMulti->IsRuby())
1993 EndCurrentAll();
1994 switch (mpPorInfo->m_Mode)
1996 case 0:
1997 nPDFType = vcl::PDFWriter::Ruby;
1998 aPDFType = "Ruby";
1999 break;
2000 case 1:
2001 nPDFType = vcl::PDFWriter::RT;
2002 aPDFType = "RT";
2003 break;
2004 case 2:
2005 nPDFType = vcl::PDFWriter::RB;
2006 aPDFType = "RB";
2007 break;
2010 else if (pMulti->IsDouble())
2012 EndCurrentAll();
2013 switch (mpPorInfo->m_Mode)
2015 case 0:
2016 nPDFType = vcl::PDFWriter::Warichu;
2017 aPDFType = "Warichu";
2018 break;
2019 case 1:
2020 nPDFType = vcl::PDFWriter::WP;
2021 aPDFType = "WP";
2022 break;
2023 case 2:
2024 nPDFType = vcl::PDFWriter::WT;
2025 aPDFType = "WT";
2026 break;
2030 break;
2033 // for FootnoteNum, is called twice: outer generates Lbl, inner Link
2034 case PortionType::FootnoteNum:
2035 assert(!isContinueSpan); // is at start
2036 if (mpPorInfo->m_Mode == 0)
2037 { // tdf#152218 link both directions
2038 nPDFType = vcl::PDFWriter::Link;
2039 aPDFType = aLinkString;
2040 break;
2042 [[fallthrough]];
2043 case PortionType::Number:
2044 case PortionType::Bullet:
2045 case PortionType::GrfNum:
2046 assert(!isContinueSpan); // is at start
2047 if (mpPorInfo->m_Mode == 1)
2048 { // only works for multiple lines via wrapper from PaintSwFrame
2049 nPDFType = vcl::PDFWriter::LILabel;
2050 aPDFType = aListLabelString;
2052 break;
2054 case PortionType::Tab :
2055 case PortionType::TabRight :
2056 case PortionType::TabCenter :
2057 case PortionType::TabDecimal :
2058 nPDFType = vcl::PDFWriter::NonStructElement;
2059 break;
2060 default: break;
2063 if ( USHRT_MAX != nPDFType )
2065 BeginTag( static_cast<vcl::PDFWriter::StructElement>(nPDFType), aPDFType );
2069 bool SwTaggedPDFHelper::IsExportTaggedPDF( const OutputDevice& rOut )
2071 vcl::PDFExtOutDevData* pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData*>( rOut.GetExtOutDevData() );
2072 return pPDFExtOutDevData && pPDFExtOutDevData->GetIsExportTaggedPDF();
2075 SwEnhancedPDFExportHelper::SwEnhancedPDFExportHelper( SwEditShell& rSh,
2076 OutputDevice& rOut,
2077 const OUString& rPageRange,
2078 bool bSkipEmptyPages,
2079 bool bEditEngineOnly,
2080 const SwPrintData& rPrintData )
2081 : mrSh( rSh ),
2082 mrOut( rOut ),
2083 mbSkipEmptyPages( bSkipEmptyPages ),
2084 mbEditEngineOnly( bEditEngineOnly ),
2085 mrPrintData( rPrintData )
2087 if ( !rPageRange.isEmpty() )
2088 mpRangeEnum.reset( new StringRangeEnumerator( rPageRange, 0, mrSh.GetPageCount()-1 ) );
2090 if ( mbSkipEmptyPages )
2092 maPageNumberMap.resize( mrSh.GetPageCount() );
2093 const SwPageFrame* pCurrPage =
2094 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2095 sal_Int32 nPageNumber = 0;
2096 for ( size_t i = 0, n = maPageNumberMap.size(); i < n && pCurrPage; ++i )
2098 if ( pCurrPage->IsEmptyPage() )
2099 maPageNumberMap[i] = -1;
2100 else
2101 maPageNumberMap[i] = nPageNumber++;
2103 pCurrPage = static_cast<const SwPageFrame*>( pCurrPage->GetNext() );
2107 #if OSL_DEBUG_LEVEL > 1
2108 aStructStack.clear();
2109 #endif
2111 const sal_Int16 nScript = SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetAppLanguage() );
2112 TypedWhichId<SvxLanguageItem> nLangRes = RES_CHRATR_LANGUAGE;
2114 if ( i18n::ScriptType::ASIAN == nScript )
2115 nLangRes = RES_CHRATR_CJK_LANGUAGE;
2116 else if ( i18n::ScriptType::COMPLEX == nScript )
2117 nLangRes = RES_CHRATR_CTL_LANGUAGE;
2119 const SvxLanguageItem& rLangItem = mrSh.GetDoc()->GetDefault( nLangRes );
2120 auto const eLanguageDefault = rLangItem.GetLanguage();
2122 EnhancedPDFExport(eLanguageDefault);
2125 SwEnhancedPDFExportHelper::~SwEnhancedPDFExportHelper()
2129 tools::Rectangle SwEnhancedPDFExportHelper::SwRectToPDFRect(const SwPageFrame* pCurrPage,
2130 const tools::Rectangle& rRectangle) const
2132 if (!::sw::IsShrinkPageForPostIts(mrSh, mrPrintData)) // tdf#148729
2134 return rRectangle;
2136 return MapSwRectToPDFRect(pCurrPage, rRectangle);
2139 double SwEnhancedPDFExportHelper::GetSwRectToPDFRectScale()
2141 return 0.75;
2144 tools::Rectangle SwEnhancedPDFExportHelper::MapSwRectToPDFRect(const SwPageFrame* pCurrPage,
2145 const tools::Rectangle& rRectangle)
2147 //the page has been scaled by 75% and vertically centered, so adjust these
2148 //rectangles equivalently
2149 tools::Rectangle aRect(rRectangle);
2150 Size aRectSize(aRect.GetSize());
2151 double fScale = GetSwRectToPDFRectScale();
2152 aRectSize.setWidth( aRectSize.Width() * fScale );
2153 aRectSize.setHeight( aRectSize.Height() * fScale );
2154 tools::Long nOrigHeight = pCurrPage->getFrameArea().Height();
2155 tools::Long nNewHeight = nOrigHeight*fScale;
2156 tools::Long nShiftY = (nOrigHeight-nNewHeight)/2;
2157 aRect.SetLeft( aRect.Left() * fScale );
2158 aRect.SetTop( aRect.Top() * fScale );
2159 aRect.Move(0, nShiftY);
2160 aRect.SetSize(aRectSize);
2161 return aRect;
2164 void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDefault)
2166 vcl::PDFExtOutDevData* pPDFExtOutDevData =
2167 dynamic_cast< vcl::PDFExtOutDevData*>( mrOut.GetExtOutDevData() );
2169 if ( !pPDFExtOutDevData )
2170 return;
2172 // set the document locale
2174 lang::Locale const aDocLocale( LanguageTag(eLanguageDefault).getLocale() );
2175 pPDFExtOutDevData->SetDocumentLocale( aDocLocale );
2177 // Prepare the output device:
2179 mrOut.Push( vcl::PushFlags::MAPMODE );
2180 MapMode aMapMode( mrOut.GetMapMode() );
2181 aMapMode.SetMapUnit( MapUnit::MapTwip );
2182 mrOut.SetMapMode( aMapMode );
2184 // Create new cursor and lock the view:
2186 SwDoc* pDoc = mrSh.GetDoc();
2187 mrSh.SwCursorShell::Push();
2188 mrSh.SwCursorShell::ClearMark();
2189 const bool bOldLockView = mrSh.IsViewLocked();
2190 mrSh.LockView( true );
2192 if ( !mbEditEngineOnly )
2194 assert(pPDFExtOutDevData->GetSwPDFState() == nullptr);
2195 pPDFExtOutDevData->SetSwPDFState(new SwEnhancedPDFState(eLanguageDefault));
2197 // POSTITS
2199 if ( pPDFExtOutDevData->GetIsExportNotes() )
2201 std::vector<SwFormatField*> vpFields;
2202 mrSh.GetFieldType(SwFieldIds::Postit, OUString())->GatherFields(vpFields);
2203 for(auto pFormatField : vpFields)
2205 const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode();
2206 OSL_ENSURE(nullptr != pTNd, "Enhanced pdf export - text node is missing");
2207 if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField))
2208 continue;
2209 // Link Rectangle
2210 const SwRect& rNoteRect = mrSh.GetCharRect();
2211 const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower());
2213 // Link PageNums
2214 std::vector<sal_Int32> aNotePageNums = CalcOutputPageNums(rNoteRect);
2215 for (sal_Int32 aNotePageNum : aNotePageNums)
2218 // Use the NumberFormatter to get the date string:
2219 const SwPostItField* pField = static_cast<SwPostItField*>(pFormatField->GetField());
2220 SvNumberFormatter* pNumFormatter = pDoc->GetNumberFormatter();
2221 const Date aDateDiff(pField->GetDate() - pNumFormatter->GetNullDate());
2222 const sal_uLong nFormat = pNumFormatter->GetStandardFormat(SvNumFormatType::DATE, pField->GetLanguage());
2223 OUString sDate;
2224 const Color* pColor;
2225 pNumFormatter->GetOutputString(aDateDiff.GetDate(), nFormat, sDate, &pColor);
2227 vcl::pdf::PDFNote aNote;
2228 // The title should consist of the author and the date:
2229 aNote.maTitle = pField->GetPar1() + ", " + sDate + ", " + (pField->GetResolved() ? SwResId(STR_RESOLVED) : u""_ustr);
2230 // Guess what the contents contains...
2231 aNote.maContents = pField->GetText();
2233 tools::Rectangle aPopupRect(0, 0);
2234 SwPostItMgr* pPostItMgr = pDoc->GetEditShell()->GetPostItMgr();
2235 for (auto it = pPostItMgr->begin(); it != pPostItMgr->end(); ++it)
2237 sw::annotation::SwAnnotationWin* pWin = it->get()->mpPostIt;
2238 if (pWin)
2240 const SwRect& aAnnotRect = pWin->GetAnchorRect();
2241 if (aAnnotRect.Contains(rNoteRect))
2243 Point aPt(pDoc->GetEditShell()->GetWin()->PixelToLogic(pWin->GetPosPixel()));
2244 Size aSize(pDoc->GetEditShell()->GetWin()->PixelToLogic(pWin->GetSizePixel()));
2245 aPopupRect = tools::Rectangle(aPt, aSize);
2250 // Link Export
2251 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rNoteRect.SVRect()));
2252 sal_Int32 nNoteId = pPDFExtOutDevData->CreateNote(aRect, aNote, aPopupRect, aNotePageNum);
2254 if (mrPrintData.GetPrintPostIts() != SwPostItMode::InMargins)
2256 const IdMapEntry aNoteEntry(aRect, nNoteId);
2257 pPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap.push_back(aNoteEntry);
2260 mrSh.SwCursorShell::ClearMark();
2264 // HYPERLINKS
2266 SwGetINetAttrs aArr;
2267 mrSh.GetINetAttrs( aArr );
2268 for( auto &rAttr : aArr )
2270 SwGetINetAttr* p = &rAttr;
2271 OSL_ENSURE( nullptr != p, "Enhanced pdf export - SwGetINetAttr is missing" );
2273 const SwTextNode* pTNd = p->rINetAttr.GetpTextNode();
2274 OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" );
2276 // 1. Check if the whole paragraph is hidden
2277 // 2. Move to the hyperlink
2278 // 3. Check for hidden text attribute
2279 if ( !pTNd->IsHidden() &&
2280 mrSh.GotoINetAttr( p->rINetAttr ) &&
2281 !mrSh.IsInHiddenRange(/*bSelect=*/false) )
2283 // Select the hyperlink:
2284 mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );
2285 if ( mrSh.SwCursorShell::SelectTextAttr( RES_TXTATR_INETFMT, true ) )
2287 // First, we create the destination, because there may be more
2288 // than one link to this destination:
2289 OUString aURL( INetURLObject::decode(
2290 p->rINetAttr.GetINetFormat().GetValue(),
2291 INetURLObject::DecodeMechanism::Unambiguous ) );
2293 // We have to distinguish between internal and real URLs
2294 const bool bInternal = '#' == aURL[0];
2296 // GetCursor_() is a SwShellCursor, which is derived from
2297 // SwSelPaintRects, therefore the rectangles of the current
2298 // selection can be easily obtained:
2299 // Note: We make a copy of the rectangles, because they may
2300 // be deleted again in JumpToSwMark.
2301 SwRects const aTmp(GetCursorRectsContainingText(mrSh));
2302 OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
2303 OUString altText(p->rINetAttr.GetINetFormat().GetName());
2304 if (altText.isEmpty())
2305 altText = mrSh.GetSelText();
2307 const SwPageFrame* pSelectionPage =
2308 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2310 // Create the destination for internal links:
2311 sal_Int32 nDestId = -1;
2312 if ( bInternal )
2314 aURL = aURL.copy( 1 );
2315 mrSh.SwCursorShell::ClearMark();
2316 if (! JumpToSwMark( &mrSh, aURL ))
2318 continue; // target deleted
2321 // Destination Rectangle
2322 const SwRect& rDestRect = mrSh.GetCharRect();
2324 const SwPageFrame* pCurrPage =
2325 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2327 // Destination PageNum
2328 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2330 // Destination Export
2331 if ( -1 != nDestPageNum )
2333 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2334 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2338 if ( !bInternal || -1 != nDestId )
2340 // #i44368# Links in Header/Footer
2341 const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd );
2343 // Create links for all selected rectangles:
2344 const size_t nNumOfRects = aTmp.size();
2345 for ( size_t i = 0; i < nNumOfRects; ++i )
2347 // Link Rectangle
2348 const SwRect& rLinkRect( aTmp[ i ] );
2350 // Link PageNums
2351 std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect );
2353 for (sal_Int32 aLinkPageNum : aLinkPageNums)
2355 // Link Export
2356 tools::Rectangle aRect(SwRectToPDFRect(pSelectionPage, rLinkRect.SVRect()));
2357 const sal_Int32 nLinkId =
2358 pPDFExtOutDevData->CreateLink(aRect, altText, aLinkPageNum);
2360 // Store link info for tagged pdf output:
2361 const IdMapEntry aLinkEntry( rLinkRect, nLinkId );
2362 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
2364 // Connect Link and Destination:
2365 if ( bInternal )
2366 pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
2367 else
2368 pPDFExtOutDevData->SetLinkURL( nLinkId, aURL );
2370 // #i44368# Links in Header/Footer
2371 if ( bHeaderFooter )
2372 MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, aURL, bInternal, altText);
2378 mrSh.SwCursorShell::ClearMark();
2381 // HYPERLINKS (Graphics, Frames, OLEs )
2383 for(sw::SpzFrameFormat* pFrameFormat: *pDoc->GetSpzFrameFormats())
2385 const SwFormatURL* pItem;
2386 if ( RES_DRAWFRMFMT != pFrameFormat->Which() &&
2387 GetFrameOfModify(mrSh.GetLayout(), *pFrameFormat, SwFrameType::Fly) &&
2388 (pItem = pFrameFormat->GetAttrSet().GetItemIfSet( RES_URL )) )
2390 const SwPageFrame* pCurrPage =
2391 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2393 OUString aURL( pItem->GetURL() );
2394 if (aURL.isEmpty())
2395 continue;
2396 const bool bInternal = '#' == aURL[0];
2398 // Create the destination for internal links:
2399 sal_Int32 nDestId = -1;
2400 if ( bInternal )
2402 aURL = aURL.copy( 1 );
2403 mrSh.SwCursorShell::ClearMark();
2404 if (! JumpToSwMark( &mrSh, aURL ))
2406 continue; // target deleted
2409 // Destination Rectangle
2410 const SwRect& rDestRect = mrSh.GetCharRect();
2412 pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2414 // Destination PageNum
2415 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2417 // Destination Export
2418 if ( -1 != nDestPageNum )
2420 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2421 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2425 if ( !bInternal || -1 != nDestId )
2427 Point aNullPt;
2428 const SwRect aLinkRect = pFrameFormat->FindLayoutRect( false, &aNullPt );
2429 OUString const formatName(pFrameFormat->GetName());
2430 // Link PageNums
2431 std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( aLinkRect );
2433 // Link Export
2434 for (sal_Int32 aLinkPageNum : aLinkPageNums)
2436 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect()));
2437 const sal_Int32 nLinkId =
2438 pPDFExtOutDevData->CreateLink(aRect, formatName, aLinkPageNum);
2440 // Store link info for tagged pdf output:
2441 const IdMapEntry aLinkEntry(aLinkRect, nLinkId);
2442 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
2444 // Connect Link and Destination:
2445 if ( bInternal )
2446 pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
2447 else
2448 pPDFExtOutDevData->SetLinkURL( nLinkId, aURL );
2450 // #i44368# Links in Header/Footer
2451 const SwFormatAnchor &rAnch = pFrameFormat->GetAnchor();
2452 if (RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId())
2454 const SwNode* pAnchorNode = rAnch.GetAnchorNode();
2455 if ( pAnchorNode && pDoc->IsInHeaderFooter( *pAnchorNode ) )
2457 const SwTextNode* pTNd = pAnchorNode->GetTextNode();
2458 if ( pTNd )
2459 MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, aLinkRect, nDestId, aURL, bInternal, formatName);
2465 else if (pFrameFormat->Which() == RES_DRAWFRMFMT)
2467 // Turn media shapes into Screen annotations.
2468 if (SdrObject* pObject = pFrameFormat->FindRealSdrObject())
2470 SwRect aSnapRect(pObject->GetSnapRect());
2471 std::vector<sal_Int32> aScreenPageNums = CalcOutputPageNums(aSnapRect);
2472 if (aScreenPageNums.empty())
2473 continue;
2475 uno::Reference<drawing::XShape> xShape(pObject->getUnoShape(), uno::UNO_QUERY);
2476 if (xShape->getShapeType() == "com.sun.star.drawing.MediaShape")
2478 uno::Reference<beans::XPropertySet> xShapePropSet(xShape, uno::UNO_QUERY);
2479 OUString title;
2480 xShapePropSet->getPropertyValue(u"Title"_ustr) >>= title;
2481 OUString description;
2482 xShapePropSet->getPropertyValue(u"Description"_ustr) >>= description;
2483 OUString const altText(title.isEmpty()
2484 ? description
2485 : description.isEmpty()
2486 ? title
2487 : OUString::Concat(title) + OUString::Concat("\n") + OUString::Concat(description));
2489 OUString aMediaURL;
2490 xShapePropSet->getPropertyValue(u"MediaURL"_ustr) >>= aMediaURL;
2491 if (!aMediaURL.isEmpty())
2493 OUString const mimeType(xShapePropSet->getPropertyValue(u"MediaMimeType"_ustr).get<OUString>());
2494 const SwPageFrame* pCurrPage = mrSh.GetLayout()->GetPageAtPos(aSnapRect.Center());
2495 tools::Rectangle aPDFRect(SwRectToPDFRect(pCurrPage, aSnapRect.SVRect()));
2496 for (sal_Int32 nScreenPageNum : aScreenPageNums)
2498 sal_Int32 nScreenId = pPDFExtOutDevData->CreateScreen(aPDFRect, altText, mimeType, nScreenPageNum, pObject);
2499 if (aMediaURL.startsWith("vnd.sun.star.Package:"))
2501 // Embedded media.
2502 OUString aTempFileURL;
2503 xShapePropSet->getPropertyValue(u"PrivateTempFileURL"_ustr) >>= aTempFileURL;
2504 pPDFExtOutDevData->SetScreenStream(nScreenId, aTempFileURL);
2506 else
2507 // Linked media.
2508 pPDFExtOutDevData->SetScreenURL(nScreenId, aMediaURL);
2514 mrSh.SwCursorShell::ClearMark();
2517 // REFERENCES
2519 std::vector<SwFormatField*> vpFields;
2520 mrSh.GetFieldType( SwFieldIds::GetRef, OUString() )->GatherFields(vpFields);
2521 for(auto pFormatField : vpFields )
2523 if( pFormatField->GetTextField() && pFormatField->IsFieldInDoc() )
2525 const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode();
2526 OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" );
2527 if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField))
2528 continue;
2529 // Select the field:
2530 mrSh.SwCursorShell::SetMark();
2531 mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );
2533 // Link Rectangles
2534 SwRects const aTmp(GetCursorRectsContainingText(mrSh));
2535 OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
2537 mrSh.SwCursorShell::ClearMark();
2539 // Destination Rectangle
2540 const SwGetRefField* pField = static_cast<SwGetRefField*>(pFormatField->GetField());
2541 const OUString& rRefName = pField->GetSetRefName();
2542 mrSh.GotoRefMark( rRefName, pField->GetSubType(), pField->GetSeqNo(), pField->GetFlags() );
2543 const SwRect& rDestRect = mrSh.GetCharRect();
2545 const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2547 // Destination PageNum
2548 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2550 if ( -1 != nDestPageNum )
2552 // Destination Export
2553 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2554 const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2556 // #i44368# Links in Header/Footer
2557 const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd );
2558 OUString const content(pField->ExpandField(true, mrSh.GetLayout()));
2560 // Create links for all selected rectangles:
2561 const size_t nNumOfRects = aTmp.size();
2562 for ( size_t i = 0; i < nNumOfRects; ++i )
2564 // Link rectangle
2565 const SwRect& rLinkRect( aTmp[ i ] );
2567 // Link PageNums
2568 std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect );
2570 for (sal_Int32 aLinkPageNum : aLinkPageNums)
2572 // Link Export
2573 aRect = SwRectToPDFRect(pCurrPage, rLinkRect.SVRect());
2574 const sal_Int32 nLinkId =
2575 pPDFExtOutDevData->CreateLink(aRect, content, aLinkPageNum);
2577 // Store link info for tagged pdf output:
2578 const IdMapEntry aLinkEntry( rLinkRect, nLinkId );
2579 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
2581 // Connect Link and Destination:
2582 pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
2584 // #i44368# Links in Header/Footer
2585 if ( bHeaderFooter )
2587 MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, rLinkRect, nDestId, u""_ustr, true, content);
2593 mrSh.SwCursorShell::ClearMark();
2596 ExportAuthorityEntryLinks();
2598 // FOOTNOTES
2600 const size_t nFootnoteCount = pDoc->GetFootnoteIdxs().size();
2601 for ( size_t nIdx = 0; nIdx < nFootnoteCount; ++nIdx )
2603 // Set cursor to text node that contains the footnote:
2604 const SwTextFootnote* pTextFootnote = pDoc->GetFootnoteIdxs()[ nIdx ];
2605 SwTextNode& rTNd = const_cast<SwTextNode&>(pTextFootnote->GetTextNode());
2607 mrSh.GetCursor_()->GetPoint()->Assign(rTNd, pTextFootnote->GetStart());
2609 // 1. Check if the whole paragraph is hidden
2610 // 2. Check for hidden text attribute
2611 if (rTNd.GetTextNode()->IsHidden() || mrSh.IsInHiddenRange(/*bSelect=*/false)
2612 || (mrSh.GetLayout()->IsHideRedlines()
2613 && sw::IsFootnoteDeleted(pDoc->getIDocumentRedlineAccess(), *pTextFootnote)))
2615 continue;
2618 SwCursorSaveState aSaveState( *mrSh.GetCursor_() );
2620 // Select the footnote:
2621 mrSh.SwCursorShell::SetMark();
2622 mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars );
2624 // Link Rectangle
2625 SwRects aTmp;
2626 aTmp.insert( aTmp.begin(), mrSh.SwCursorShell::GetCursor_()->begin(), mrSh.SwCursorShell::GetCursor_()->end() );
2627 OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
2629 mrSh.GetCursor_()->RestoreSavePos();
2630 mrSh.SwCursorShell::ClearMark();
2632 if (aTmp.empty())
2633 continue;
2635 const SwRect aLinkRect( aTmp[ 0 ] );
2637 // Goto footnote text:
2638 if ( mrSh.GotoFootnoteText() )
2640 // Destination Rectangle
2641 const SwRect& rDestRect = mrSh.GetCharRect();
2642 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2643 if ( -1 != nDestPageNum )
2645 const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2646 // Destination PageNum
2647 tools::Rectangle aRect = SwRectToPDFRect(pCurrPage, rDestRect.SVRect());
2648 // Back link rectangle calculation
2649 const SwPageFrame* fnBodyPage = pCurrPage->getRootFrame()->GetPageByPageNum(nDestPageNum+1);
2650 SwRect fnSymbolRect;
2651 if (fnBodyPage->IsVertical()){
2652 tools::Long fnSymbolTop = fnBodyPage->GetTopMargin() + fnBodyPage->getFrameArea().Top();
2653 tools::Long symbolHeight = rDestRect.Top() - fnSymbolTop;
2654 fnSymbolRect = SwRect(rDestRect.Pos().X(),fnSymbolTop,rDestRect.Width(),symbolHeight);
2655 } else {
2656 if (fnBodyPage->IsRightToLeft()){
2657 tools::Long fnSymbolRight = fnBodyPage->getFrameArea().Right() - fnBodyPage->GetRightMargin();
2658 tools::Long symbolWidth = fnSymbolRight - rDestRect.Right();
2659 fnSymbolRect = SwRect(rDestRect.Pos().X(),rDestRect.Pos().Y(),symbolWidth,rDestRect.Height());
2660 } else {
2661 tools::Long fnSymbolLeft = fnBodyPage->GetLeftMargin() + fnBodyPage->getFrameArea().Left();
2662 tools::Long symbolWidth = rDestRect.Left() - fnSymbolLeft;
2663 fnSymbolRect = SwRect(fnSymbolLeft,rDestRect.Pos().Y(),symbolWidth,rDestRect.Height());
2666 tools::Rectangle aFootnoteSymbolRect = SwRectToPDFRect(pCurrPage, fnSymbolRect.SVRect());
2668 OUString const numStrSymbol(pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, mrSh.GetLayout(), true));
2669 OUString const numStrRef(pTextFootnote->GetFootnote().GetViewNumStr(*pDoc, mrSh.GetLayout(), false));
2671 // Export back link
2672 const sal_Int32 nBackLinkId = pPDFExtOutDevData->CreateLink(aFootnoteSymbolRect, numStrSymbol, nDestPageNum);
2673 // Destination Export
2674 const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2675 mrSh.GotoFootnoteAnchor();
2676 // Link PageNums
2677 sal_Int32 aLinkPageNum = CalcOutputPageNum( aLinkRect );
2678 pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2679 // Link Export
2680 aRect = SwRectToPDFRect(pCurrPage, aLinkRect.SVRect());
2681 const sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, numStrRef, aLinkPageNum);
2682 // Back link destination Export
2683 const sal_Int32 nBackDestId = pPDFExtOutDevData->CreateDest(aRect, aLinkPageNum);
2684 // Store link info for tagged pdf output:
2685 const IdMapEntry aLinkEntry( aLinkRect, nLinkId );
2686 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
2688 // Store backlink info for tagged pdf output:
2689 const IdMapEntry aBackLinkEntry( aFootnoteSymbolRect, nBackLinkId );
2690 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aBackLinkEntry);
2691 // Connect Links and Destinations:
2692 pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
2693 pPDFExtOutDevData->SetLinkDest( nBackLinkId, nBackDestId );
2698 // OUTLINE
2700 if( pPDFExtOutDevData->GetIsExportBookmarks() )
2702 typedef std::pair< sal_Int8, sal_Int32 > StackEntry;
2703 std::stack< StackEntry > aOutlineStack;
2704 aOutlineStack.push( StackEntry( -1, -1 ) ); // push default value
2706 // outlines inside flys (text frames) collected before the normal
2707 // outlines by GetOutLineNds(), so store them with page/position data
2708 // to insert later on the right page and position:
2709 // tuple< nDestPageNum, rDestRect, nLevel, rEntry, nDestId >
2710 typedef std::tuple< sal_Int32, SwRect, sal_Int32, const OUString, sal_Int32 > FlyEntry;
2711 std::vector< FlyEntry > aFlyVector;
2712 sal_Int32 nStartFly = 0; // first not processed item in aFlyVector
2714 const SwOutlineNodes::size_type nOutlineCount =
2715 mrSh.getIDocumentOutlineNodesAccess()->getOutlineNodesCount();
2716 for ( SwOutlineNodes::size_type i = 0; i < nOutlineCount; ++i )
2718 // Check if outline is hidden
2719 const SwTextNode* pTNd = mrSh.GetNodes().GetOutLineNds()[ i ]->GetTextNode();
2720 assert(pTNd && "Enhanced pdf export - text node is missing");
2722 if ( pTNd->IsHidden() ||
2723 !sw::IsParaPropsNode(*mrSh.GetLayout(), *pTNd) ||
2724 // #i40292# Skip empty outlines:
2725 pTNd->GetText().isEmpty())
2726 continue;
2728 // Check if outline is inside a text frame
2729 bool bFlyOutline = pTNd->GetFlyFormat();
2731 // save outline stack to use for postponed fly outlines
2732 std::stack< StackEntry > aSavedOutlineStack;
2733 if ( !aFlyVector.empty() && !bFlyOutline )
2734 aSavedOutlineStack = aOutlineStack;
2736 // Get parent id from stack:
2737 const sal_Int8 nLevel = static_cast<sal_Int8>(mrSh.getIDocumentOutlineNodesAccess()->getOutlineLevel( i ));
2738 sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first;
2739 while ( nLevelOnTopOfStack >= nLevel &&
2740 nLevelOnTopOfStack != -1 )
2742 aOutlineStack.pop();
2743 nLevelOnTopOfStack = aOutlineStack.top().first;
2745 const sal_Int32 nParent = aOutlineStack.top().second;
2747 // Destination rectangle
2748 mrSh.GotoOutline(i);
2749 const SwRect& rDestRect = mrSh.GetCharRect();
2751 const SwPageFrame* pCurrPage =
2752 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2754 // Destination PageNum
2755 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2757 if ( -1 != nDestPageNum )
2759 // Destination Export
2760 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2761 const sal_Int32 nDestId =
2762 pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2764 // Outline entry text
2765 const OUString aEntry = mrSh.getIDocumentOutlineNodesAccess()->getOutlineText(
2766 i, mrSh.GetLayout(), true, false, false );
2768 // postpone fly outlines
2769 if ( bFlyOutline )
2771 aFlyVector.push_back(
2772 FlyEntry(nDestPageNum, rDestRect, nLevel, aEntry, nDestId) );
2773 continue;
2776 // create new outline items from postponed fly outlines, if they are before
2777 // the recent not fly outline (and after the already created fly outlines)
2778 for (size_t j = nStartFly; j < aFlyVector.size(); ++j)
2780 if ( std::get<0>(aFlyVector[j]) < nDestPageNum ||
2781 ( std::get<0>(aFlyVector[j]) == nDestPageNum &&
2782 std::get<1>(aFlyVector[j]).Pos().Y() < rDestRect.Pos().Y() ) )
2784 sal_Int32 nFlyLevel = std::get<2>(aFlyVector[j]);
2785 sal_Int8 nLevelOnTopOfSavedStack = aSavedOutlineStack.top().first;
2786 while ( nLevelOnTopOfSavedStack >= nFlyLevel &&
2787 nLevelOnTopOfSavedStack != -1 )
2789 aSavedOutlineStack.pop();
2790 nLevelOnTopOfSavedStack = aSavedOutlineStack.top().first;
2792 const sal_Int32 nFlyParent = aSavedOutlineStack.top().second;
2793 const sal_Int32 nId = pPDFExtOutDevData->CreateOutlineItem( nFlyParent,
2794 std::get<3>(aFlyVector[j]),
2795 std::get<4>(aFlyVector[j]) );
2796 // Push current level and outline id on saved stack:
2797 aSavedOutlineStack.push( StackEntry( nFlyLevel, nId ) );
2798 ++nStartFly;
2800 else
2801 break;
2804 // Create a new outline item:
2805 const sal_Int32 nOutlineId =
2806 pPDFExtOutDevData->CreateOutlineItem( nParent, aEntry, nDestId );
2808 // Push current level and nOutlineId on stack:
2809 aOutlineStack.push( StackEntry( nLevel, nOutlineId ) );
2813 // create remaining fly outlines
2814 for (size_t j = nStartFly; j < aFlyVector.size(); ++j)
2816 sal_Int32 nLevel = std::get<2>(aFlyVector[j]);
2817 sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first;
2818 while ( nLevelOnTopOfStack >= nLevel &&
2819 nLevelOnTopOfStack != -1 )
2821 aOutlineStack.pop();
2822 nLevelOnTopOfStack = aOutlineStack.top().first;
2824 const sal_Int32 nParent = aOutlineStack.top().second;
2826 const sal_Int32 nOutlineId = pPDFExtOutDevData->CreateOutlineItem( nParent,
2827 std::get<3>(aFlyVector[j]),
2828 std::get<4>(aFlyVector[j]) );
2829 aOutlineStack.push( StackEntry( std::get<2>(aFlyVector[j]), nOutlineId ) );
2833 if( pPDFExtOutDevData->GetIsExportNamedDestinations() )
2835 // #i56629# the iteration to convert the OOo bookmark (#bookmark)
2836 // into PDF named destination, see section 8.2.1 in PDF 1.4 spec
2837 // We need:
2838 // 1. a name for the destination, formed from the standard OOo bookmark name
2839 // 2. the destination, obtained from where the bookmark destination lies
2840 IDocumentMarkAccess* const pMarkAccess = mrSh.GetDoc()->getIDocumentMarkAccess();
2841 for(auto ppMark = pMarkAccess->getBookmarksBegin();
2842 ppMark != pMarkAccess->getBookmarksEnd();
2843 ++ppMark)
2845 //get the name
2846 const ::sw::mark::MarkBase* pBkmk = *ppMark;
2847 mrSh.SwCursorShell::ClearMark();
2848 const OUString& sBkName = pBkmk->GetName();
2850 //jump to it
2851 if (! JumpToSwMark( &mrSh, sBkName ))
2853 continue;
2856 // Destination Rectangle
2857 const SwRect& rDestRect = mrSh.GetCharRect();
2859 const SwPageFrame* pCurrPage =
2860 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2862 // Destination PageNum
2863 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2865 // Destination Export
2866 if ( -1 != nDestPageNum )
2868 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2869 pPDFExtOutDevData->CreateNamedDest(sBkName, aRect, nDestPageNum);
2872 mrSh.SwCursorShell::ClearMark();
2873 //<--- i56629
2876 else
2879 // LINKS FROM EDITENGINE
2881 std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks();
2882 for ( const auto& rBookmark : rBookmarks )
2884 OUString aBookmarkName( rBookmark.aBookmark );
2885 const bool bInternal = '#' == aBookmarkName[0];
2886 if ( bInternal )
2888 aBookmarkName = aBookmarkName.copy( 1 );
2889 JumpToSwMark( &mrSh, aBookmarkName );
2891 // Destination Rectangle
2892 const SwRect& rDestRect = mrSh.GetCharRect();
2894 const SwPageFrame* pCurrPage =
2895 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2897 // Destination PageNum
2898 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2900 if ( -1 != nDestPageNum )
2902 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2903 if ( rBookmark.nLinkId != -1 )
2905 // Destination Export
2906 const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2908 // Connect Link and Destination:
2909 pPDFExtOutDevData->SetLinkDest( rBookmark.nLinkId, nDestId );
2911 else
2913 pPDFExtOutDevData->DescribeRegisteredDest(rBookmark.nDestId, aRect, nDestPageNum);
2917 else
2918 pPDFExtOutDevData->SetLinkURL( rBookmark.nLinkId, aBookmarkName );
2920 rBookmarks.clear();
2921 assert(pPDFExtOutDevData->GetSwPDFState());
2922 delete pPDFExtOutDevData->GetSwPDFState();
2923 pPDFExtOutDevData->SetSwPDFState(nullptr);
2926 // Restore view, cursor, and outdev:
2927 mrSh.LockView( bOldLockView );
2928 mrSh.SwCursorShell::Pop(SwCursorShell::PopMode::DeleteCurrent);
2929 mrOut.Pop();
2932 void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks()
2934 auto pPDFExtOutDevData = dynamic_cast<vcl::PDFExtOutDevData*>(mrOut.GetExtOutDevData());
2935 if (!pPDFExtOutDevData)
2937 return;
2940 // Create PDF destinations for bibliography table entries
2941 std::vector<std::tuple<const SwTOXBase*, const OUString*, sal_Int32>> vDestinations;
2942 // string is the row node text, sal_Int32 is number of the destination
2943 // Note: This way of iterating doesn't seem to take into account TOXes
2944 // that are in a frame, probably in some other cases too
2946 mrSh.GotoPage(1);
2947 while (mrSh.GotoNextTOXBase())
2949 const SwTOXBase* pIteratedTOX = nullptr;
2950 while ((pIteratedTOX = mrSh.GetCurTOX()) != nullptr
2951 && pIteratedTOX->GetType() == TOX_AUTHORITIES)
2953 if (const SwNode& rCurrentNode = mrSh.GetCursor()->GetPoint()->GetNode();
2954 rCurrentNode.GetNodeType() == SwNodeType::Text)
2956 if (mrSh.GetCursor()->GetPoint()->GetNode().FindSectionNode()->GetSection().GetType()
2957 == SectionType::ToxContent) // this checks it's not a heading
2959 // Destination Rectangle
2960 const SwRect& rDestRect = mrSh.GetCharRect();
2962 const SwPageFrame* pCurrPage =
2963 static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
2965 // Destination PageNum
2966 const sal_Int32 nDestPageNum = CalcOutputPageNum( rDestRect );
2968 // Destination Export
2969 if ( -1 != nDestPageNum )
2971 tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
2972 const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
2973 const OUString* vNodeText = &static_cast<const SwTextNode*>(&rCurrentNode)->GetText();
2974 vDestinations.emplace_back(pIteratedTOX, vNodeText, nDestId);
2978 if (!mrSh.MovePara(GoNextPara, fnParaStart))
2979 { // Cursor is stuck in the TOX due to document ending immediately afterwards
2980 break;
2986 // Generate links to matching entries in the bibliography tables
2987 std::vector<SwFormatField*> aFields;
2988 SwFieldType* pType = mrSh.GetFieldType(SwFieldIds::TableOfAuthorities, OUString());
2989 if (!pType)
2991 return;
2994 pType->GatherFields(aFields);
2995 const auto pPageFrame = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower());
2996 for (const auto pFormatField : aFields)
2998 if (!pFormatField->GetTextField() || !pFormatField->IsFieldInDoc())
3000 continue;
3003 const auto& rAuthorityField
3004 = *static_cast<const SwAuthorityField*>(pFormatField->GetField());
3006 if (auto targetType = rAuthorityField.GetTargetType();
3007 targetType == SwAuthorityField::TargetType::UseDisplayURL
3008 || targetType == SwAuthorityField::TargetType::UseTargetURL)
3010 // Since the target type specifies to use an URL, link to it
3011 const OUString aURL = rAuthorityField.GetAbsoluteURL();
3012 if (aURL.getLength() == 0)
3014 continue;
3017 const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode();
3018 if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField))
3020 continue;
3023 OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout()));
3025 // Select the field.
3026 mrSh.SwCursorShell::SetMark();
3027 mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars);
3029 // Create the links.
3030 SwRects const rects(GetCursorRectsContainingText(mrSh));
3031 for (const auto& rLinkRect : rects)
3033 for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect))
3035 tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect()));
3036 sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum);
3037 IdMapEntry aLinkEntry(rLinkRect, nLinkId);
3038 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
3039 pPDFExtOutDevData->SetLinkURL(nLinkId, aURL);
3042 mrSh.SwCursorShell::ClearMark();
3044 else if (targetType == SwAuthorityField::TargetType::BibliographyTableRow)
3046 // As the target type specifies, try linking to a bibliography table row
3047 sal_Int32 nDestId = -1;
3049 std::unordered_map<const SwTOXBase*, OUString> vFormattedFieldStrings;
3050 for (const auto& rDestinationTuple : vDestinations)
3052 if (vFormattedFieldStrings.find(std::get<0>(rDestinationTuple))
3053 == vFormattedFieldStrings.end())
3054 vFormattedFieldStrings.emplace(std::get<0>(rDestinationTuple),
3055 rAuthorityField.GetAuthority(mrSh.GetLayout(),
3056 &std::get<0>(rDestinationTuple)->GetTOXForm()));
3058 if (vFormattedFieldStrings.at(std::get<0>(rDestinationTuple)) == *std::get<1>(rDestinationTuple))
3060 nDestId = std::get<2>(rDestinationTuple);
3061 break;
3065 if (nDestId == -1)
3066 continue;
3068 const SwTextNode& rTextNode = pFormatField->GetTextField()->GetTextNode();
3069 if (!lcl_TryMoveToNonHiddenField(mrSh, rTextNode, *pFormatField))
3071 continue;
3074 OUString const content(rAuthorityField.ExpandField(true, mrSh.GetLayout()));
3076 // Select the field.
3077 mrSh.SwCursorShell::SetMark();
3078 mrSh.SwCursorShell::Right(1, SwCursorSkipMode::Chars);
3080 // Create the links.
3081 SwRects const rects(GetCursorRectsContainingText(mrSh));
3082 for (const auto& rLinkRect : rects)
3084 for (const auto& rLinkPageNum : CalcOutputPageNums(rLinkRect))
3086 tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, rLinkRect.SVRect()));
3087 sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, content, rLinkPageNum);
3088 IdMapEntry aLinkEntry(rLinkRect, nLinkId);
3089 pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
3090 pPDFExtOutDevData->SetLinkDest(nLinkId, nDestId);
3093 mrSh.SwCursorShell::ClearMark();
3098 // Returns the page number in the output pdf on which the given rect is located.
3099 // If this page is duplicated, method will return first occurrence of it.
3100 sal_Int32 SwEnhancedPDFExportHelper::CalcOutputPageNum( const SwRect& rRect ) const
3102 std::vector< sal_Int32 > aPageNums = CalcOutputPageNums( rRect );
3103 if ( !aPageNums.empty() )
3104 return aPageNums[0];
3105 return -1;
3108 // Returns a vector of the page numbers in the output pdf on which the given
3109 // rect is located. There can be many such pages since StringRangeEnumerator
3110 // allows duplication of its entries.
3111 std::vector< sal_Int32 > SwEnhancedPDFExportHelper::CalcOutputPageNums(
3112 const SwRect& rRect ) const
3114 std::vector< sal_Int32 > aPageNums;
3116 // Document page number.
3117 sal_Int32 nPageNumOfRect = mrSh.GetPageNumAndSetOffsetForPDF( mrOut, rRect );
3118 if ( nPageNumOfRect < 0 )
3119 return aPageNums;
3121 // What will be the page numbers of page nPageNumOfRect in the output pdf?
3122 if ( mpRangeEnum )
3124 if ( mbSkipEmptyPages )
3125 // Map the page number to the range without empty pages.
3126 nPageNumOfRect = maPageNumberMap[ nPageNumOfRect ];
3128 if ( mpRangeEnum->hasValue( nPageNumOfRect ) )
3130 sal_Int32 nOutputPageNum = 0;
3131 StringRangeEnumerator::Iterator aIter = mpRangeEnum->begin();
3132 StringRangeEnumerator::Iterator aEnd = mpRangeEnum->end();
3133 for ( ; aIter != aEnd; ++aIter )
3135 if ( *aIter == nPageNumOfRect )
3136 aPageNums.push_back( nOutputPageNum );
3137 ++nOutputPageNum;
3141 else
3143 if ( mbSkipEmptyPages )
3145 sal_Int32 nOutputPageNum = 0;
3146 for ( size_t i = 0; i < maPageNumberMap.size(); ++i )
3148 if ( maPageNumberMap[i] >= 0 ) // is not empty?
3150 if ( i == static_cast<size_t>( nPageNumOfRect ) )
3152 aPageNums.push_back( nOutputPageNum );
3153 break;
3155 ++nOutputPageNum;
3159 else
3160 aPageNums.push_back( nPageNumOfRect );
3163 return aPageNums;
3166 void SwEnhancedPDFExportHelper::MakeHeaderFooterLinks( vcl::PDFExtOutDevData& rPDFExtOutDevData,
3167 const SwTextNode& rTNd,
3168 const SwRect& rLinkRect,
3169 sal_Int32 nDestId,
3170 const OUString& rURL,
3171 bool bInternal,
3172 OUString const& rContent) const
3174 // We assume, that the primary link has just been exported. Therefore
3175 // the offset of the link rectangle calculates as follows:
3176 const Point aOffset = rLinkRect.Pos() + mrOut.GetMapMode().GetOrigin();
3178 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(rTNd);
3179 for ( SwTextFrame* pTmpFrame = aIter.First(); pTmpFrame; pTmpFrame = aIter.Next() )
3181 // Add offset to current page:
3182 const SwPageFrame* pPageFrame = pTmpFrame->FindPageFrame();
3183 SwRect aHFLinkRect( rLinkRect );
3184 aHFLinkRect.Pos() = pPageFrame->getFrameArea().Pos() + aOffset;
3186 // #i97135# the gcc_x64 optimizer gets aHFLinkRect != rLinkRect wrong
3187 // fool it by comparing the position only (the width and height are the
3188 // same anyway)
3189 if ( aHFLinkRect.Pos() != rLinkRect.Pos() )
3191 // Link PageNums
3192 std::vector<sal_Int32> aHFLinkPageNums = CalcOutputPageNums( aHFLinkRect );
3194 for (sal_Int32 aHFLinkPageNum : aHFLinkPageNums)
3196 // Link Export
3197 tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, aHFLinkRect.SVRect()));
3198 const sal_Int32 nHFLinkId =
3199 rPDFExtOutDevData.CreateLink(aRect, rContent, aHFLinkPageNum);
3201 // Connect Link and Destination:
3202 if ( bInternal )
3203 rPDFExtOutDevData.SetLinkDest( nHFLinkId, nDestId );
3204 else
3205 rPDFExtOutDevData.SetLinkURL( nHFLinkId, rURL );
3211 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */