/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
// It is possible to add multiple captions to a table, // both above and below, either simultaneously or separately. // Start by checking the next frame, and if we don't find a table frame // or if the next frame is not a caption, we return to the current caption // and perform the same operation backwards using the previous frames. const SwFrame* pRetFrame = rFrame.GetNext(); if (!pRetFrame)
{
bPrevFrame = true;
pRetFrame = rFrame.GetPrev();
}
while (pRetFrame)
{ if (pRetFrame->IsTabFrame())
{
pTabFrame = static_cast<const SwTabFrame*>(pRetFrame); break;
}
// Check if the next or the previous frame is a caption frame bool bIsCaption = lcl_IsCaptionFrame(*pRetFrame); if (bIsCaption && pRetFrame->GetNext())
{
pRetFrame = !bPrevFrame ? pRetFrame->GetNext() : pRetFrame->GetPrev();
} elseif (!bPrevFrame && rFrame.GetPrev())
{ // If no table was found while checking the GetNext() frames, // jump back to the current caption and // start checking the GetPrev() frames.
bPrevFrame = true;
pRetFrame = rFrame.GetPrev();
} else // This part handles the case // when the table has been deleted, // but the caption has not. break;
}
return pTabFrame;
}
// List all frames for which the NonStructElement tag is set: bool lcl_IsInNonStructEnv( const SwFrame& rFrame )
{ bool bRet = false;
// We find the previous text node. Now check, if the previous text node // has the same numrule like rNode: if ( (pPrevNumRule == pNumRule) &&
(!pPrevTextNd->IsOutline() == !rNode.IsOutline()))
bRet = true;
break;
}
pNode = &aIdx.GetNode();
} return bRet;
}
bool lcl_TryMoveToNonHiddenField(SwEditShell& rShell, const SwTextNode& rNd, const SwFormatField& rField)
{ // 1. Check if the whole paragraph is hidden // 2. Move to the field // 3. Check for hidden text attribute if(rNd.IsHidden()) returnfalse; if(!rShell.GotoFormatField(rField) || rShell.IsInHiddenRange(/*bSelect=*/false))
{
rShell.SwCursorShell::ClearMark(); returnfalse;
} returntrue;
};
// tdf#157816: try to check if the rectangle contains actual text
::std::vector<SwRect> GetCursorRectsContainingText(SwCursorShell const& rShell)
{
::std::vector<SwRect> ret;
SwRects rects;
rShell.GetLayout()->CalcFrameRects(*rShell.GetCursor_(), rects, SwRootFrame::RectsMode::NoAnchoredFlys); for (SwRect const& rRect : rects)
{
Point center(rRect.Center());
SwSpecialPos special;
SwCursorMoveState cms(CursorMoveState::NONE);
cms.m_pSpecialPos = &special;
cms.m_bFieldInfo = true;
SwPosition pos(rShell.GetDoc()->GetNodes()); autoconst [pStart, pEnd] = rShell.GetCursor_()->StartEnd(); if (rShell.GetLayout()->GetModelPositionForViewPoint(&pos, center, &cms)
&& *pStart <= pos && pos <= *pEnd)
{
SwRect charRect;
std::pair<Point, bool> const tmp(center, false);
SwContentFrame const*const pFrame(
pos.nNode.GetNode().GetTextNode()->getLayoutFrame(rShell.GetLayout(), &pos, &tmp)); if (pFrame->GetCharRect(charRect, pos, &cms, false)
&& rRect.Overlaps(charRect))
{
ret.push_back(rRect);
}
} // reset stupid static var that may have gotten set now
SwTextCursor::SetRightMargin(false); // WTF is this crap
} return ret;
}
bool SwTaggedPDFHelper::CheckReopenTag()
{ bool bRet = false; voidconst* pReopenKey(nullptr); bool bContinue = false; // in some cases we just have to reopen a tag without early returning
// Reopen an existing structure element if // - rFrame is not the first page frame (reopen Document tag) // - rFrame is a follow frame (reopen Master tag) // - rFrame is a fly frame anchored at content (reopen Anchor paragraph tag) // - rFrame is a fly frame anchored at page (reopen Document tag) // - rFrame is a follow flow row (reopen TableRow tag) // - rFrame is a cell frame in a follow flow row (reopen TableData tag) if ( ( rFrame.IsPageFrame() && static_cast<const SwPageFrame&>(rFrame).GetPrev() ) ||
( rFrame.IsFlowFrame() && SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() ) ||
(rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetMaster()) ||
( rFrame.IsRowFrame() && rFrame.IsInFollowFlowRow() ) ||
( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetPrevCellLeaf() ) )
{
pKeyFrame = &rFrame;
} elseif (rFrame.IsFlyFrame() && !mpFrameInfo->m_isLink)
{ const SwFormatAnchor& rAnchor = static_cast<const SwFlyFrame*>(&rFrame)->GetFormat()->GetAnchor(); if ((RndStdIds::FLY_AT_PARA == rAnchor.GetAnchorId()) ||
(RndStdIds::FLY_AT_CHAR == rAnchor.GetAnchorId()) ||
(RndStdIds::FLY_AT_PAGE == rAnchor.GetAnchorId()))
{
pKeyFrame = static_cast<const SwFlyFrame&>(rFrame).GetAnchorFrame();
bContinue = true;
}
}
if ( pKeyFrame )
{ voidconst*const pKey = lcl_GetKeyFromFrame(*pKeyFrame);
FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet); if (rFrameTagSet.contains(pKey)
|| rFrame.IsFlyFrame()) // for hell layer flys
{
pReopenKey = pKey;
}
}
}
if (pReopenKey)
{ // note: it would be possible to get rid of the SetCurrentStructureElement() // - which is quite ugly - for most cases by recreating the parents until the // current ancestor, but there are special cases cell frame rowspan > 1 follow // and footnote frame follow where the parent of the follow is different from // the parent of the first one, and so in PDFExtOutDevData the wrong parent // would be restored and used for next elements.
m_nRestoreCurrentTag = mpPDFExtOutDevData->GetCurrentStructureElement();
sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement(pReopenKey);
mpPDFExtOutDevData->SetCurrentStructureElement(id);
if (mpFrameInfo)
{ const SwFrame& rFrame = mpFrameInfo->mrFrame;
if ( ( rFrame.IsPageFrame() && !static_cast<const SwPageFrame&>(rFrame).GetPrev() ) ||
( rFrame.IsFlowFrame() && !SwFlowFrame::CastFlowFrame(&rFrame)->IsFollow() && SwFlowFrame::CastFlowFrame(&rFrame)->HasFollow() ) ||
rFrame.IsSctFrame() || // all of them, so that opening parent sections works
( rFrame.IsTextFrame() && rFrame.GetDrawObjs() ) ||
(rFrame.IsFootnoteFrame() && static_cast<SwFootnoteFrame const&>(rFrame).GetFollow()) ||
( rFrame.IsRowFrame() && rFrame.IsInSplitTableRow() ) ||
( rFrame.IsCellFrame() && const_cast<SwFrame&>(rFrame).GetNextCellLeaf() ) ||
rFrame.IsTabFrame() )
{
pKey = lcl_GetKeyFromFrame(rFrame);
if (pKey)
{
FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet);
assert(!rFrameTagSet.contains(pKey));
rFrameTagSet.emplace(pKey);
}
}
}
sal_Int32 const nId = BeginTagImpl(pKey, eType, rString);
// Store the id of the current structure element if // - it is a list structure element // - it is a list body element with children // - rFrame is the first page frame // - rFrame is a master frame // - rFrame has objects anchored to it // - rFrame is a row frame or cell frame in a split table row
case vcl::pdf::StructElement::TableRow:
bPlacement =
bWritingMode = true; break;
case vcl::pdf::StructElement::TableHeader:
mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope, vcl::PDFWriter::Column);
[[fallthrough]]; case vcl::pdf::StructElement::TableData:
bPlacement =
bWritingMode =
bWidth =
bHeight =
bRowSpan = true; break;
case vcl::pdf::StructElement::Caption: if (pFrame->IsSctFrame())
{ break;
}
[[fallthrough]]; case vcl::pdf::StructElement::H1: case vcl::pdf::StructElement::H2: case vcl::pdf::StructElement::H3: case vcl::pdf::StructElement::H4: case vcl::pdf::StructElement::H5: case vcl::pdf::StructElement::H6: case vcl::pdf::StructElement::Paragraph: case vcl::pdf::StructElement::Heading: case vcl::pdf::StructElement::BlockQuote: case vcl::pdf::StructElement::Title:
bPlacement =
bWritingMode =
bSpaceBefore =
bSpaceAfter =
bStartIndent =
bEndIndent =
bTextIndent =
bTextAlign = true; break;
case vcl::pdf::StructElement::Formula: case vcl::pdf::StructElement::Figure:
bAltText =
bPlacement =
bWidth =
bHeight =
bBox = true; break;
case vcl::pdf::StructElement::Division: if (pFrame->IsFlyFrame()) // this can be something else too
{
bAltText = true;
bBox = true;
} break;
case vcl::pdf::StructElement::NonStructElement: if (pFrame->IsHeaderFrame() || pFrame->IsFooterFrame())
{ // ISO 14289-1:2014, Clause: 7.8
mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Type, vcl::PDFWriter::Pagination);
mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Subtype,
pFrame->IsHeaderFrame()
? vcl::PDFWriter::Header
: vcl::PDFWriter::Footer);
} break;
default : break;
}
// Set the attributes:
if ( bPlacement )
{ bool bIsFigureInline = false; if (vcl::pdf::StructElement::Figure == eType)
{ const SwFrame* pKeyFrame = static_cast<const SwFlyFrame&>(*pFrame).GetAnchorFrame(); if (const SwLayoutFrame* pUpperFrame = pKeyFrame->GetUpper()) if (pUpperFrame->GetType() == SwFrameType::Body)
bIsFigureInline = true;
}
// ISO 14289-1:2014, Clause: 7.3 // ISO 14289-1:2014, Clause: 7.7 // For images (but not embedded objects), an ObjectInfoPrimitive2D is // created, but it's not evaluated by VclMetafileProcessor2D any more; // that would require producing StructureTagPrimitive2D here but that // looks impossible so instead duplicate the code that sets the Alt // text here again. if (bAltText)
{
SwFlyFrameFormat const& rFly(*static_cast<SwFlyFrame const*>(pFrame)->GetFormat());
OUString const sep(
(rFly.GetObjTitle().isEmpty() || rFly.GetObjDescription().isEmpty())
? OUString() : u" - "_ustr);
OUString const altText(rFly.GetObjTitle() + sep + rFly.GetObjDescription()); if (!altText.isEmpty())
{
mpPDFExtOutDevData->SetAlternateText(altText);
}
}
if (bAnnotAttribute)
{
SwRect aPorRect;
rInf.CalcRect(*pPor, &aPorRect); const NoteIdMap& rNoteIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap); const Point aCenter = aPorRect.Center(); auto aIter = std::find_if(rNoteIdMap.begin(), rNoteIdMap.end(),
[&aCenter](const IdMapEntry& rEntry)
{ return rEntry.first.Contains(aCenter); }); if (aIter != rNoteIdMap.end())
{
sal_Int32 nNoteId = (*aIter).second;
mpPDFExtOutDevData->SetStructureAttributeNumerical(vcl::PDFWriter::NoteAnnotation,
nNoteId);
}
}
} elseif (mpNumInfo && eType == vcl::pdf::StructElement::List)
{
SwTextFrame const& rFrame(mpNumInfo->mrFrame);
SwTextNode const& rNode(*rFrame.GetTextNodeForParaProps());
SwNumRule const*const pNumRule = rNode.GetNumRule();
assert(pNumRule); // was required for List
auto ToPDFListNumbering = [](SvxNumberFormat const& rFormat) { switch (rFormat.GetNumberingType())
{ case css::style::NumberingType::CHARS_UPPER_LETTER: return vcl::PDFWriter::UpperAlpha; case css::style::NumberingType::CHARS_LOWER_LETTER: return vcl::PDFWriter::LowerAlpha; case css::style::NumberingType::ROMAN_UPPER: return vcl::PDFWriter::UpperRoman; case css::style::NumberingType::ROMAN_LOWER: return vcl::PDFWriter::LowerRoman; case css::style::NumberingType::ARABIC: return vcl::PDFWriter::Decimal; case css::style::NumberingType::CHAR_SPECIAL: switch (rFormat.GetBulletChar())
{ case u'\u2022': case u'\uE12C': case u'\uE01E': case u'\uE437': return vcl::PDFWriter::Disc; case u'\u2218': case u'\u25CB': case u'\u25E6': return vcl::PDFWriter::Circle; case u'\u25A0': case u'\u25AA': case u'\uE00A': return vcl::PDFWriter::Square; default: return vcl::PDFWriter::NONE;
} default: // the other 50 types return vcl::PDFWriter::NONE;
}
};
// Note: for every level, BeginNumberedListStructureElements() produces // a separate List element, so even though in PDF this is limited to // the whole List we can just export the current level here.
vcl::PDFWriter::StructAttributeValue const value(
ToPDFListNumbering(pNumRule->Get(rNode.GetActualListLevel()))); // ISO 14289-1:2014, Clause: 7.6
mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::ListNumbering, value);
}
}
void SwTaggedPDFHelper::BeginNumberedListStructureElements()
{
OSL_ENSURE( mpNumInfo, "List without mpNumInfo?" ); if ( !mpNumInfo ) return;
// Lowers of NonStructureElements should not be considered: if (lcl_IsInNonStructEnv(rTextFrame)) return;
// do it for the first one in the follow chain that has content for (SwFlowFrame const* pPrecede = rTextFrame.GetPrecede(); pPrecede; pPrecede = pPrecede->GetPrecede())
{
SwTextFrame const*const pText(static_cast<SwTextFrame const*>(pPrecede)); if (!pText->HasPara() || pText->GetPara()->HasContentPortions())
{ return;
}
}
// Second condition: current numbering is not 'interrupted' if ( bSameNumbering )
{
sal_Int32 nReopenTag = -1;
// Two cases: // 1. We have to reopen an existing list body tag: // - If the current node is either the first child of its parent // and its level > 1 or // - Numbering should restart at the current node and its level > 1 // - The current item has no label constbool bNewSubListStart = pParent->GetParent() && (pParent->IsFirst( pNodeNum ) || pTextNd->IsListRestart() ); constbool bNoLabel = !pTextNd->IsCountedInList() && !pTextNd->IsListRestart(); if ( bNewSubListStart || bNoLabel )
{ // Fine, we try to reopen the appropriate list body
NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
if ( bNewSubListStart )
{ // The list body tag associated with the parent has to be reopened // to start a new list inside the list body
NumListBodyIdMap::const_iterator aIter;
do
aIter = rNumListBodyIdMap.find( pParent ); while ( aIter == rNumListBodyIdMap.end() && nullptr != ( pParent = pParent->GetParent() ) );
if ( aIter != rNumListBodyIdMap.end() )
nReopenTag = (*aIter).second;
} else// if(bNoLabel)
{ // The list body tag of a 'counted' predecessor has to be reopened const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true); while ( pPrevious )
{ if ( pPrevious->IsCounted())
{ // get id of list body tag const NumListBodyIdMap::const_iterator aIter = rNumListBodyIdMap.find( pPrevious ); if ( aIter != rNumListBodyIdMap.end() )
{
nReopenTag = (*aIter).second; break;
}
}
pPrevious = pPrevious->GetPred(true);
}
}
} // 2. We have to reopen an existing list tag: elseif ( !pParent->IsFirst( pNodeNum ) && !pTextNd->IsListRestart() )
{ // any other than the first node in a list level has to reopen the current // list. The current list is associated in a map with the first child of the list:
NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
// Search backwards and check if any of the previous nodes has a list associated with it: const SwNumberTreeNode* pPrevious = pNodeNum->GetPred(true); while ( pPrevious )
{ // get id of list tag const NumListIdMap::const_iterator aIter = rNumListIdMap.find( pPrevious ); if ( aIter != rNumListIdMap.end() )
{
nReopenTag = (*aIter).second; break;
}
#if OSL_DEBUG_LEVEL > 1
aStructStack.push_back( 99 ); #endif
}
} else
{ // clear list maps in case a list has been interrupted
NumListIdMap& rNumListIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListIdMap);
rNumListIdMap.clear();
NumListBodyIdMap& rNumListBodyIdMap(mpPDFExtOutDevData->GetSwPDFState()->m_NumListBodyIdMap);
rNumListBodyIdMap.clear();
}
// New tags: constbool bNewListTag = (pNodeNum->GetParent()->IsFirst( pNodeNum ) || pTextNd->IsListRestart() || !bSameNumbering); constbool bNewItemTag = bNewListTag || pTextNd->IsCountedInList(); // If the text node is not counted, we do not start a new list item:
if ( bNewListTag )
BeginTag(vcl::pdf::StructElement::List, aListString);
if ( bNewItemTag )
{
BeginTag(vcl::pdf::StructElement::ListItem, aListItemString);
assert(rTextFrame.GetPara()); // check whether to open LBody now or delay until after Lbl if (!rTextFrame.GetPara()->HasNumberingPortion(SwParaPortion::OnlyNumbering))
{
BeginTag(vcl::pdf::StructElement::LIBody, aListBodyString);
}
}
}
// Note: vcl::PDFWriter::Note is actually a ILSE. Nevertheless // we treat it like a grouping element!
nPDFType = sal_uInt16(vcl::pdf::StructElement::Note);
aPDFType = aNoteString; break;
if (SwSectionNode const* pFormatSectionNode = pSection->GetFormat()->GetSectionNode())
{ // open all parent sections, so that the SEs of sections // are nested in the same way as their SwSectionNodes
std::vector<SwSection const*> parents; // iterate only *direct* parents - do not leave table cell! for (SwSectionNode const* pSectionNode{pFormatSectionNode->StartOfSectionNode()->GetSectionNode()};
pSectionNode != nullptr;
pSectionNode = pSectionNode->StartOfSectionNode()->GetSectionNode())
{
parents.push_back(&pSectionNode->GetSection());
} for (auto it = parents.rbegin(); it != parents.rend(); ++it)
{ // key is the SwSection - see lcl_GetKeyFromFrame()
OpenTagImpl(*it);
}
}
FrameTagSet& rFrameTagSet(mpPDFExtOutDevData->GetSwPDFState()->m_FrameTagSet); if (rFrameTagSet.contains(pSection))
{ // special case: section may have *multiple* master frames, // when it is interrupted by nested section - reopen!
OpenTagImpl(pSection); break;
} elseif (SectionType::ToxHeader == pSection->GetType())
{
nPDFType = sal_uInt16(vcl::pdf::StructElement::Caption);
aPDFType = aCaptionString;
} elseif (SectionType::ToxContent == pSection->GetType())
{ const SwTOXBase* pTOXBase = pSection->GetTOXBase(); if ( pTOXBase )
{ if ( TOX_INDEX == pTOXBase->GetType() )
{
nPDFType = sal_uInt16(vcl::pdf::StructElement::Index);
aPDFType = aIndexString;
} else
{
nPDFType = sal_uInt16(vcl::pdf::StructElement::TOC);
aPDFType = aTOCString;
}
}
} elseif ( SectionType::Content == pSection->GetType() )
{
nPDFType = sal_uInt16(vcl::pdf::StructElement::Section);
aPDFType = aSectString;
}
} break;
if ( pTextFormat)
SwStyleNameMapper::FillProgName( pTextFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl ); if ( pParentTextFormat)
SwStyleNameMapper::FillProgName( pParentTextFormat->GetName(), sParentStyleName, SwGetPoolIdFromName::TxtColl );
// This is the default. If the paragraph could not be mapped to // any of the standard pdf tags, we write a user defined tag // <stylename> with role = P
nPDFType = sal_uInt16(vcl::pdf::StructElement::Paragraph);
aPDFType = sStyleName.toString();
if (!pFrame->IsInFly()) // Table caption
{
TableCaptionsMap& rTableCaptionsMap(
mpPDFExtOutDevData->GetSwPDFState()->m_TableCaptionsMap);
const SwTabFrame* pTabFrame = lcl_FindTableForCaption(*pFrame); if (pTabFrame)
{ const SwTable* pTable = pTabFrame->GetTable(); if (rTableCaptionsMap.contains(pTable))
{ // Reopen Caption tag: // - if the table has an above and below caption // - if the table has multiple above or below captions
m_nRestoreCurrentTag
= mpPDFExtOutDevData->GetCurrentStructureElement();
if (int nRealLevel = pTextNd->GetAttrOutlineLevel() - 1;
nRealLevel >= 0
&& !pTextNd->IsInRedlines()
&& sw::IsParaPropsNode(*pFrame->getRootFrame(), *pTextNd))
{ switch(nRealLevel)
{ case 0 :
aPDFType = aH1String; break; case 1 :
aPDFType = aH2String; break; case 2 :
aPDFType = aH3String; break; case 3 :
aPDFType = aH4String; break; case 4 :
aPDFType = aH5String; break; case 5:
aPDFType = aH6String; break; case 6:
aPDFType = aH7String; break; case 7:
aPDFType = aH8String; break; case 8:
aPDFType = aH9String; break; case 9:
aPDFType = aH10String; break; default:
assert(false); break;
}
// PDF/UA allows unlimited headings, but PDF only up to H6 // ... and apparently the extra H7.. must be declared in // RoleMap, or veraPDF complains.
nRealLevel = std::min(nRealLevel, 5);
nPDFType = o3tl::narrowing<sal_uInt16>(sal_uInt16(vcl::pdf::StructElement::H1) + nRealLevel);
}
void SwTaggedPDFHelper::EndStructureElements()
{ if (mpFrameInfo != nullptr)
{ if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
{ // close span at end of paragraph
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
++m_nEndStructureElement;
} if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
{ // close link at end of paragraph
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
++m_nEndStructureElement;
}
}
while ( m_nEndStructureElement > 0 )
{
EndTag();
--m_nEndStructureElement;
}
void SwTaggedPDFHelper::EndCurrentAll()
{ if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
{
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
} if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
{
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
}
}
void SwTaggedPDFHelper::EndCurrentSpan()
{
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.reset();
EndTag(); // close span
}
void SwTaggedPDFHelper::CreateCurrentSpan(
SwTextPaintInfo const& rInf, OUString const& rStyleName)
{
assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan);
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.emplace(
SwEnhancedPDFState::Span{
rInf.GetFont()->GetUnderline(),
rInf.GetFont()->GetOverline(),
rInf.GetFont()->GetStrikeout(),
rInf.GetFont()->GetEmphasisMark(),
rInf.GetFont()->GetEscapement(),
rInf.GetFont()->GetActual(),
rInf.GetFont()->GetLanguage(),
rStyleName}); // leave it open to let next portion decide to merge or close
--m_nEndStructureElement;
}
bool SwTaggedPDFHelper::CheckContinueSpan(
SwTextPaintInfo const& rInf, std::u16string_view const rStyleName,
SwTextAttr const*const pInetFormatAttr)
{ // for now, don't create span inside of link - this should be very rare // situation and it looks complicated to implement.
assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan
|| !mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink); if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
{ if (pInetFormatAttr && pInetFormatAttr == *mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
{ returntrue;
} else
{
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
EndTag(); returnfalse;
}
} if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan && pInetFormatAttr)
{
EndCurrentSpan(); returnfalse;
}
if (!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan) returnfalse;
ProgName sStyleName; if (!pInetFormatAttr)
{
std::vector<SwTextAttr *> const charAttrs(
pos.first->GetTextAttrsAt(pos.second, RES_TXTATR_CHARFMT)); // TODO: handle more than 1 char style? const SwCharFormat* pCharFormat = (charAttrs.size())
? (*charAttrs.begin())->GetCharFormat().GetCharFormat() : nullptr; if (pCharFormat)
SwStyleNameMapper::FillProgName( pCharFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
}
// note: ILSE may be nested, so only end the span if needed to start new one boolconst isContinueSpan(CheckContinueSpan(rInf, sStyleName.toString(), pInetFormatAttr));
switch ( pPor->GetWhichPor() )
{ case PortionType::PostIts: if (!mpPDFExtOutDevData->GetSwPDFState()->m_NoteIdMap.empty())
{
nPDFType = sal_uInt16(vcl::pdf::StructElement::Annot);
aPDFType = aAnnotString;
} break;
case PortionType::Hyphen : case PortionType::SoftHyphen : // Check for alternative spelling: case PortionType::HyphenStr : case PortionType::SoftHyphenStr :
nPDFType = sal_uInt16(vcl::pdf::StructElement::Span);
aPDFType = aSpanString; break;
case PortionType::Fly: // if a link is split by a fly overlap, then there will be multiple // annotations for the link, and hence there must be multiple SEs, // so every annotation has its own SE. if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
{
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
EndTag();
} break;
case PortionType::Lay : case PortionType::Text : case PortionType::Para :
{ // Check for Link: if( pInetFormatAttr )
{ if (!isContinueSpan)
{
nPDFType = sal_uInt16(vcl::pdf::StructElement::Link);
aPDFType = aLinkString;
assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace(pInetFormatAttr); // leave it open to let next portion decide to merge or close
--m_nEndStructureElement;
}
} // Emphasis elseif (sStyleName == constEmphasisStyleName)
{ if (!isContinueSpan)
{
nPDFType = sal_uInt16(vcl::pdf::StructElement::Emphasis);
aPDFType = constEmphasisStyleName;
CreateCurrentSpan(rInf, sStyleName.toString());
}
} // Strong elseif (sStyleName == constStrongEmphasisStyleName)
{ if (!isContinueSpan)
{
nPDFType = sal_uInt16(vcl::pdf::StructElement::Strong);
aPDFType = constStrongEmphasisStyleName;
CreateCurrentSpan(rInf, sStyleName.toString());
}
} // Check for Quote/Code character style: elseif (sStyleName == aQuotation)
{ if (!isContinueSpan)
{
nPDFType = sal_uInt16(vcl::pdf::StructElement::Quote);
aPDFType = aQuoteString;
CreateCurrentSpan(rInf, sStyleName.toString());
}
} elseif (sStyleName == aSourceText)
{ if (!isContinueSpan)
{
nPDFType = sal_uInt16(vcl::pdf::StructElement::Code);
aPDFType = aCodeString;
CreateCurrentSpan(rInf, sStyleName.toString());
}
} elseif (!isContinueSpan)
{ const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage(); const SwFontScript nFont = rInf.GetFont()->GetActual(); const LanguageType nDefaultLang(mpPDFExtOutDevData->GetSwPDFState()->m_eLanguageDefault);
// for FootnoteNum, is called twice: outer generates Lbl, inner Link case PortionType::FootnoteNum:
assert(!isContinueSpan); // is at start if (mpPorInfo->m_Mode == 0)
{ // tdf#152218 link both directions
nPDFType = sal_uInt16(vcl::pdf::StructElement::Link);
aPDFType = aLinkString; break;
}
[[fallthrough]]; case PortionType::Number: case PortionType::Bullet: case PortionType::GrfNum:
assert(!isContinueSpan); // is at start if (mpPorInfo->m_Mode == 1)
{ // only works for multiple lines via wrapper from PaintSwFrame
nPDFType = sal_uInt16(vcl::pdf::StructElement::LILabel);
aPDFType = aListLabelString;
} break;
case PortionType::Tab : case PortionType::TabRight : case PortionType::TabCenter : case PortionType::TabDecimal :
nPDFType = sal_uInt16(vcl::pdf::StructElement::NonStructElement); break; default: break;
}
if ( !mbEditEngineOnly )
{
assert(pPDFExtOutDevData->GetSwPDFState() == nullptr);
pPDFExtOutDevData->SetSwPDFState(new SwEnhancedPDFState(eLanguageDefault));
// POSTITS
if ( pPDFExtOutDevData->GetIsExportNotes() )
{
std::vector<SwFormatField*> vpFields;
mrSh.GetFieldType(SwFieldIds::Postit, OUString())->GatherFields(vpFields); for(auto pFormatField : vpFields)
{ const SwTextNode* pTNd = pFormatField->GetTextField()->GetpTextNode();
OSL_ENSURE(nullptr != pTNd, "Enhanced pdf export - text node is missing"); if(!lcl_TryMoveToNonHiddenField(mrSh, *pTNd, *pFormatField)) continue; // Link Rectangle const SwRect& rNoteRect = mrSh.GetCharRect(); const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>(mrSh.GetLayout()->Lower());
// Link PageNums
std::vector<sal_Int32> aNotePageNums = CalcOutputPageNums(rNoteRect); for (sal_Int32 aNotePageNum : aNotePageNums)
{
// Use the NumberFormatter to get the date string: const SwPostItField* pField = static_cast<SwPostItField*>(pFormatField->GetField());
SvNumberFormatter* pNumFormatter = pDoc->GetNumberFormatter(); const Date aDateDiff(pField->GetDate() - pNumFormatter->GetNullDate()); const sal_uLong nFormat = pNumFormatter->GetStandardFormat(SvNumFormatType::DATE, pField->GetLanguage());
OUString sDate; const Color* pColor;
pNumFormatter->GetOutputString(aDateDiff.GetDate(), nFormat, sDate, &pColor);
vcl::pdf::PDFNote aNote; // The title should consist of the author and the date:
aNote.maTitle = pField->GetPar1() + ", " + sDate + ", " + (pField->GetResolved() ? SwResId(STR_RESOLVED) : u""_ustr); // Guess what the contents contains...
aNote.maContents = pField->GetText();
tools::Rectangle aPopupRect(0, 0);
SwPostItMgr* pPostItMgr = pDoc->GetEditShell()->GetPostItMgr(); for (auto it = pPostItMgr->begin(); it != pPostItMgr->end(); ++it)
{
sw::annotation::SwAnnotationWin* pWin = it->get()->mpPostIt; if (pWin)
{ const SwRect& aAnnotRect = pWin->GetAnchorRect(); if (aAnnotRect.Contains(rNoteRect))
{
Point aPt(pDoc->GetEditShell()->GetWin()->PixelToLogic(pWin->GetPosPixel()));
Size aSize(pDoc->GetEditShell()->GetWin()->PixelToLogic(pWin->GetSizePixel()));
aPopupRect = tools::Rectangle(aPt, aSize);
}
}
}
SwGetINetAttrs aArr;
mrSh.GetINetAttrs( aArr ); for( auto &rAttr : aArr )
{
SwGetINetAttr* p = &rAttr;
OSL_ENSURE( nullptr != p, "Enhanced pdf export - SwGetINetAttr is missing" );
const SwTextNode* pTNd = p->rINetAttr.GetpTextNode();
OSL_ENSURE( nullptr != pTNd, "Enhanced pdf export - text node is missing" );
// 1. Check if the whole paragraph is hidden // 2. Move to the hyperlink // 3. Check for hidden text attribute if ( !pTNd->IsHidden() &&
mrSh.GotoINetAttr( p->rINetAttr ) &&
!mrSh.IsInHiddenRange(/*bSelect=*/false) )
{ // Select the hyperlink:
mrSh.SwCursorShell::Right( 1, SwCursorSkipMode::Chars ); if ( mrSh.SwCursorShell::SelectTextAttr( RES_TXTATR_INETFMT, true ) )
{ // First, we create the destination, because there may be more // than one link to this destination:
OUString aURL( INetURLObject::decode(
p->rINetAttr.GetINetFormat().GetValue(),
INetURLObject::DecodeMechanism::Unambiguous ) );
// We have to distinguish between internal and real URLs constbool bInternal = '#' == aURL[0];
// GetCursor_() is a SwShellCursor, which is derived from // SwSelPaintRects, therefore the rectangles of the current // selection can be easily obtained: // Note: We make a copy of the rectangles, because they may // be deleted again in JumpToSwMark.
SwRects const aTmp(GetCursorRectsContainingText(mrSh));
OSL_ENSURE( !aTmp.empty(), "Enhanced pdf export - rectangles are missing" );
OUString altText(p->rINetAttr.GetINetFormat().GetName());
if ( !bInternal || -1 != nDestId )
{ // #i44368# Links in Header/Footer constbool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd );
// Create links for all selected rectangles: const size_t nNumOfRects = aTmp.size(); for ( size_t i = 0; i < nNumOfRects; ++i )
{ // Link Rectangle const SwRect& rLinkRect( aTmp[ i ] );
// Link PageNums
std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect );
for (sal_Int32 aLinkPageNum : aLinkPageNums)
{ // Link Export
tools::Rectangle aRect(SwRectToPDFRect(pSelectionPage, rLinkRect.SVRect())); const sal_Int32 nLinkId =
pPDFExtOutDevData->CreateLink(aRect, altText, aLinkPageNum);
// Store link info for tagged pdf output: const IdMapEntry aLinkEntry( rLinkRect, nLinkId );
pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
// Connect Link and Destination: if ( bInternal )
pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); else
pPDFExtOutDevData->SetLinkURL( nLinkId, aURL );
// Link Export for (sal_Int32 aLinkPageNum : aLinkPageNums)
{
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect())); const sal_Int32 nLinkId =
pPDFExtOutDevData->CreateLink(aRect, linkName.toString(), aLinkPageNum);
// Store link info for tagged pdf output: const IdMapEntry aLinkEntry(aLinkRect, nLinkId);
pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
// Connect Link and Destination: if ( bInternal )
pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId ); else
pPDFExtOutDevData->SetLinkURL( nLinkId, aURL );
// #i44368# Links in Header/Footer const SwFormatAnchor &rAnch = pFrameFormat->GetAnchor(); if (RndStdIds::FLY_AT_PAGE != rAnch.GetAnchorId())
{ const SwNode* pAnchorNode = rAnch.GetAnchorNode(); if ( pAnchorNode && pDoc->IsInHeaderFooter( *pAnchorNode ) )
{ const SwTextNode* pTNd = pAnchorNode->GetTextNode(); if ( pTNd )
MakeHeaderFooterLinks(*pPDFExtOutDevData, *pTNd, aLinkRect, nDestId, aURL, bInternal, linkName.toString());
}
}
}
}
} elseif (pFrameFormat->Which() == RES_DRAWFRMFMT)
{ // Turn media shapes into Screen annotations. if (SdrObject* pObject = pFrameFormat->FindRealSdrObject())
{
SwRect aSnapRect(pObject->GetSnapRect());
std::vector<sal_Int32> aScreenPageNums = CalcOutputPageNums(aSnapRect); if (aScreenPageNums.empty()) continue;
// Create links for all selected rectangles: const size_t nNumOfRects = aTmp.size(); for ( size_t i = 0; i < nNumOfRects; ++i )
{ // Link rectangle const SwRect& rLinkRect( aTmp[ i ] );
// Link PageNums
std::vector<sal_Int32> aLinkPageNums = CalcOutputPageNums( rLinkRect );
for (sal_Int32 aLinkPageNum : aLinkPageNums)
{ // Link Export
aRect = SwRectToPDFRect(pCurrPage, rLinkRect.SVRect()); const sal_Int32 nLinkId =
pPDFExtOutDevData->CreateLink(aRect, rRefName.toString(), aLinkPageNum);
// Store link info for tagged pdf output: const IdMapEntry aLinkEntry( rLinkRect, nLinkId );
pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
// Connect Link and Destination:
pPDFExtOutDevData->SetLinkDest( nLinkId, nDestId );
// 1. Check if the whole paragraph is hidden // 2. Check for hidden text attribute if (rTNd.GetTextNode()->IsHidden() || mrSh.IsInHiddenRange(/*bSelect=*/false)
|| (mrSh.GetLayout()->IsHideRedlines()
&& sw::IsFootnoteDeleted(pDoc->getIDocumentRedlineAccess(), *pTextFootnote)))
{ continue;
}
// outlines inside flys (text frames) collected before the normal // outlines by GetOutLineNds(), so store them with page/position data // to insert later on the right page and position: // tuple< nDestPageNum, rDestRect, nLevel, rEntry, nDestId > typedef std::tuple< sal_Int32, SwRect, sal_Int32, const OUString, sal_Int32 > FlyEntry;
std::vector< FlyEntry > aFlyVector;
sal_Int32 nStartFly = 0; // first not processed item in aFlyVector
const SwOutlineNodes::size_type nOutlineCount =
mrSh.getIDocumentOutlineNodesAccess()->getOutlineNodesCount(); for ( SwOutlineNodes::size_type i = 0; i < nOutlineCount; ++i )
{ // Check if outline is hidden const SwTextNode* pTNd = mrSh.GetNodes().GetOutLineNds()[ i ]->GetTextNode();
assert(pTNd && "Enhanced pdf export - text node is missing");
// Check if outline is inside a text frame bool bFlyOutline = pTNd->GetFlyFormat();
// save outline stack to use for postponed fly outlines
std::stack< StackEntry > aSavedOutlineStack; if ( !aFlyVector.empty() && !bFlyOutline )
aSavedOutlineStack = aOutlineStack;
// Get parent id from stack: const sal_Int8 nLevel = static_cast<sal_Int8>(mrSh.getIDocumentOutlineNodesAccess()->getOutlineLevel( i ));
sal_Int8 nLevelOnTopOfStack = aOutlineStack.top().first; while ( nLevelOnTopOfStack >= nLevel &&
nLevelOnTopOfStack != -1 )
{
aOutlineStack.pop();
nLevelOnTopOfStack = aOutlineStack.top().first;
} const sal_Int32 nParent = aOutlineStack.top().second;
if( pPDFExtOutDevData->GetIsExportNamedDestinations() )
{ // #i56629# the iteration to convert the OOo bookmark (#bookmark) // into PDF named destination, see section 8.2.1 in PDF 1.4 spec // We need: // 1. a name for the destination, formed from the standard OOo bookmark name // 2. the destination, obtained from where the bookmark destination lies
IDocumentMarkAccess* const pMarkAccess = mrSh.GetDoc()->getIDocumentMarkAccess(); for(auto ppMark = pMarkAccess->getBookmarksBegin();
ppMark != pMarkAccess->getBookmarksEnd();
++ppMark)
{ //get the name const ::sw::mark::MarkBase* pBkmk = *ppMark;
mrSh.SwCursorShell::ClearMark(); const SwMarkName& sBkName = pBkmk->GetName();
//jump to it if (! JumpToSwMark( &mrSh, sBkName ))
{ continue;
}
void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks()
{ auto pPDFExtOutDevData = dynamic_cast<vcl::PDFExtOutDevData*>(mrOut.GetExtOutDevData()); if (!pPDFExtOutDevData)
{ return;
}
// Create PDF destinations for bibliography table entries
std::vector<std::tuple<const SwTOXBase*, const OUString*, sal_Int32>> vDestinations; // string is the row node text, sal_Int32 is number of the destination // Note: This way of iterating doesn't seem to take into account TOXes // that are in a frame, probably in some other cases too
{
mrSh.GotoPage(1); while (mrSh.GotoNextTOXBase())
{ const SwTOXBase* pIteratedTOX = nullptr; while ((pIteratedTOX = mrSh.GetCurTOX()) != nullptr
&& pIteratedTOX->GetType() == TOX_AUTHORITIES)
{ if (const SwNode& rCurrentNode = mrSh.GetCursor()->GetPoint()->GetNode();
rCurrentNode.GetNodeType() == SwNodeType::Text)
{ if (mrSh.GetCursor()->GetPoint()->GetNode().FindSectionNode()->GetSection().GetType()
== SectionType::ToxContent) // this checks it's not a heading
{ // Destination Rectangle const SwRect& rDestRect = mrSh.GetCharRect();
if (auto targetType = rAuthorityField.GetTargetType();
targetType == SwAuthorityField::TargetType::UseDisplayURL
|| targetType == SwAuthorityField::TargetType::UseTargetURL)
{ // Since the target type specifies to use an URL, link to it const OUString aURL = rAuthorityField.GetAbsoluteURL(); if (aURL.getLength() == 0)
{ continue;
}
// Returns the page number in the output pdf on which the given rect is located. // If this page is duplicated, method will return first occurrence of it.
sal_Int32 SwEnhancedPDFExportHelper::CalcOutputPageNum( const SwRect& rRect ) const
{
std::vector< sal_Int32 > aPageNums = CalcOutputPageNums( rRect ); if ( !aPageNums.empty() ) return aPageNums[0]; return -1;
}
// Returns a vector of the page numbers in the output pdf on which the given // rect is located. There can be many such pages since StringRangeEnumerator // allows duplication of its entries.
std::vector< sal_Int32 > SwEnhancedPDFExportHelper::CalcOutputPageNums( const SwRect& rRect ) const
{
std::vector< sal_Int32 > aPageNums;
// What will be the page numbers of page nPageNumOfRect in the output pdf? if ( mpRangeEnum )
{ if ( mbSkipEmptyPages ) // Map the page number to the range without empty pages.
nPageNumOfRect = maPageNumberMap[ nPageNumOfRect ];
if ( mpRangeEnum->hasValue( nPageNumOfRect ) )
{
sal_Int32 nOutputPageNum = 0;
StringRangeEnumerator::Iterator aIter = mpRangeEnum->begin();
StringRangeEnumerator::Iterator aEnd = mpRangeEnum->end(); for ( ; aIter != aEnd; ++aIter )
{ if ( *aIter == nPageNumOfRect )
aPageNums.push_back( nOutputPageNum );
++nOutputPageNum;
}
}
} else
{ if ( mbSkipEmptyPages )
{
sal_Int32 nOutputPageNum = 0; for ( size_t i = 0; i < maPageNumberMap.size(); ++i )
{ if ( maPageNumberMap[i] >= 0 ) // is not empty?
{ if ( i == static_cast<size_t>( nPageNumOfRect ) )
{
aPageNums.push_back( nOutputPageNum ); break;
}
++nOutputPageNum;
}
}
} else
aPageNums.push_back( nPageNumOfRect );
}
return aPageNums;
}
void SwEnhancedPDFExportHelper::MakeHeaderFooterLinks( vcl::PDFExtOutDevData& rPDFExtOutDevData, const SwTextNode& rTNd, const SwRect& rLinkRect,
sal_Int32 nDestId, const OUString& rURL, bool bInternal,
OUString const& rContent) const
{ // We assume, that the primary link has just been exported. Therefore // the offset of the link rectangle calculates as follows: const Point aOffset = rLinkRect.Pos() + mrOut.GetMapMode().GetOrigin();
// #i97135# the gcc_x64 optimizer gets aHFLinkRect != rLinkRect wrong // fool it by comparing the position only (the width and height are the // same anyway) if ( aHFLinkRect.Pos() != rLinkRect.Pos() )
{ // Link PageNums
std::vector<sal_Int32> aHFLinkPageNums = CalcOutputPageNums( aHFLinkRect );
for (sal_Int32 aHFLinkPageNum : aHFLinkPageNums)
{ // Link Export
tools::Rectangle aRect(SwRectToPDFRect(pPageFrame, aHFLinkRect.SVRect())); const sal_Int32 nHFLinkId =
rPDFExtOutDevData.CreateLink(aRect, rContent, aHFLinkPageNum);
// Connect Link and Destination: if ( bInternal )
rPDFExtOutDevData.SetLinkDest( nHFLinkId, nDestId ); else
rPDFExtOutDevData.SetLinkURL( nHFLinkId, rURL );
}
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.