tdf#171022 sw,vcl: name the element a link points at
Exporting a document to PDF/UA-2 produced a file that fails ISO 14289-2 clause 8.8, once per outline item and internal link. veraPDF reports Destination in Outline item, OpenAction or Link annotation is not a structure destination The destination named the page and an area on it, and that is all: what was linked to could only be worked out by looking at what the layout had put at that position. A destination for a link or an outline item is now written as a GoTo action carrying a structure destination beside the page one (ISO 32000-2 12.3.2.3): /A<</Type/Action/S/GoTo/D[<page> 0 R/XYZ ...]/SD[<elem> 0 R/XYZ ...]>> sw creates a destination before its target is tagged, so the node is remembered while the cursor is on it, and matched against the tags in the pass that already runs after the last page. Creating one and remembering its node is a single call now, which every destination a link or an outline item points at goes through: bibliography entries and links in the text of a shape are named as well. The cursor is not always on the target: a jump that selects a frame or a drawing object, and one that finds nothing, move it nowhere, and where it was left standing on the link, that link's own element must not become the target. The pass over a shape's links can instead start with the cursor on the target already, where a jump that did reach it shows no move either, so there the mark names its own node. Those keep writing /Dest, as do a target that was never tagged and one whose element was never emitted. The element carries its role and its content, not a name of its own: a heading becomes /S/H1 with no /Alt or /T. veraPDF does not check what a structure destination points at, so the test does. Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com> Change-Id: Ic76d80d46f130d549ebaacf175393a3d6ec77998 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12300 Reviewed-by: Miklos Vajna <vmiklos@collabora.com> Tested-by: Jenkins CPCI <releng@collaboraoffice.com> (cherry picked from commit a6502c6e96d13425bcb4a3bf5731a998982d5957) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211150 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
committed by
Xisco Fauli
parent
31e2c176d0
commit
32dec34df6
@@ -296,6 +296,8 @@ public:
|
||||
the dest the link shall point to
|
||||
*/
|
||||
void SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId );
|
||||
/** Name the structure element a destination points at, for ISO 14289-2 8.8 */
|
||||
void SetDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId);
|
||||
/** Set the URL for a link
|
||||
<p>will change a dest type link to a URL type link if necessary</p>
|
||||
@param nLinkId
|
||||
|
||||
@@ -1031,6 +1031,8 @@ public:
|
||||
the dest the link shall point to
|
||||
*/
|
||||
void SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId );
|
||||
/** Name the structure element a destination points at, for ISO 14289-2 8.8 */
|
||||
void SetDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId);
|
||||
/** Set the URL for a link
|
||||
will change a dest type link to a URL type link if necessary
|
||||
@param nLinkId
|
||||
|
||||
@@ -14,6 +14,7 @@ $(eval $(call gb_CppunitTest_CppunitTest,sw_core_text))
|
||||
$(eval $(call gb_CppunitTest_use_common_precompiled_header,sw_core_text))
|
||||
|
||||
$(eval $(call gb_CppunitTest_add_exception_objects,sw_core_text, \
|
||||
sw/qa/core/text/EnhancedPDFExportHelper \
|
||||
sw/qa/core/text/frmform \
|
||||
sw/qa/core/text/itratr \
|
||||
sw/qa/core/text/itrpaint \
|
||||
|
||||
@@ -41,6 +41,7 @@ class SwEditShell;
|
||||
class StringRangeEnumerator;
|
||||
class SwTextAttr;
|
||||
class SwTextNode;
|
||||
struct SwPosition;
|
||||
class SwTextPaintInfo;
|
||||
class SwTextFrame;
|
||||
|
||||
@@ -232,6 +233,13 @@ class SwEnhancedPDFExportHelper
|
||||
/// Exports bibliography entry links.
|
||||
void ExportAuthorityEntryLinks();
|
||||
|
||||
/// Creates a destination, and remembers the node it points at, to name it once tagging is done.
|
||||
/// The target is either the node the cursor jumped to since rBeforeJump, or one known outright.
|
||||
sal_Int32 CreateDestination(const SwPageFrame* pCurrPage, const SwRect& rRect,
|
||||
sal_Int32 nPageNum, const SwPosition& rBeforeJump);
|
||||
sal_Int32 CreateDestination(const SwPageFrame* pCurrPage, const SwRect& rRect,
|
||||
sal_Int32 nPageNum, const SwTextNode* pTarget);
|
||||
|
||||
sal_Int32 CalcOutputPageNum( const SwRect& rRect ) const;
|
||||
std::vector< sal_Int32 > CalcOutputPageNums( const SwRect& rRect ) const;
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
||||
/*
|
||||
* Copyright the LibreOffice contributors.
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
#include <swmodeltestbase.hxx>
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include <comphelper/propertyvalue.hxx>
|
||||
#include <tools/stream.hxx>
|
||||
#include <vcl/filter/pdfdocument.hxx>
|
||||
|
||||
namespace
|
||||
{
|
||||
/// Covers sw/source/core/text/EnhancedPDFExportHelper.cxx fixes.
|
||||
class Test : public SwModelTestBase
|
||||
{
|
||||
public:
|
||||
Test()
|
||||
: SwModelTestBase(u"/sw/qa/core/text/data/"_ustr)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(Test, testTdf171022)
|
||||
{
|
||||
createSwDoc("structure-destinations.fodt");
|
||||
|
||||
uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true),
|
||||
comphelper::makePropertyValue(u"SelectPdfVersion"_ustr,
|
||||
sal_Int32(20)) };
|
||||
save(TestFilter::PDF_WRITER,
|
||||
{ comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) });
|
||||
|
||||
vcl::filter::PDFDocument aDocument;
|
||||
CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ)));
|
||||
|
||||
// the outline item, the footnote's two links, the shape's link and the citation's
|
||||
std::vector<OString> aOutlineTypes;
|
||||
std::vector<OString> aLinkTypes;
|
||||
for (const auto& rDocElement : aDocument.GetElements())
|
||||
{
|
||||
auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
|
||||
if (!pObject)
|
||||
continue;
|
||||
auto pAction = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pObject->Lookup("A"_ostr));
|
||||
if (!pAction)
|
||||
continue;
|
||||
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pAction->LookupElement("S"_ostr));
|
||||
if (!pType || pType->GetValue() != "GoTo")
|
||||
continue;
|
||||
// ISO 14289-2 8.8: the destination inside the document is a structure destination, so
|
||||
// it names the element and not only the page. veraPDF passes a structure destination
|
||||
// that points at nothing, so check what it points at.
|
||||
auto pPage = dynamic_cast<vcl::filter::PDFArrayElement*>(pAction->LookupElement("D"_ostr));
|
||||
auto pStructure
|
||||
= dynamic_cast<vcl::filter::PDFArrayElement*>(pAction->LookupElement("SD"_ostr));
|
||||
CPPUNIT_ASSERT(pPage);
|
||||
CPPUNIT_ASSERT(pStructure);
|
||||
CPPUNIT_ASSERT(!pPage->GetElements().empty());
|
||||
CPPUNIT_ASSERT(!pStructure->GetElements().empty());
|
||||
|
||||
auto pPageRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pPage->GetElements()[0]);
|
||||
auto pStructureRef
|
||||
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pStructure->GetElements()[0]);
|
||||
CPPUNIT_ASSERT(pPageRef);
|
||||
CPPUNIT_ASSERT(pStructureRef);
|
||||
|
||||
CPPUNIT_ASSERT(pPageRef->LookupObject());
|
||||
CPPUNIT_ASSERT(pStructureRef->LookupObject());
|
||||
auto pPageType = dynamic_cast<vcl::filter::PDFNameElement*>(
|
||||
pPageRef->LookupObject()->Lookup("Type"_ostr));
|
||||
auto pStructureType = dynamic_cast<vcl::filter::PDFNameElement*>(
|
||||
pStructureRef->LookupObject()->Lookup("Type"_ostr));
|
||||
CPPUNIT_ASSERT(pPageType);
|
||||
CPPUNIT_ASSERT(pStructureType);
|
||||
CPPUNIT_ASSERT_EQUAL("Page"_ostr, pPageType->GetValue());
|
||||
CPPUNIT_ASSERT_EQUAL("StructElem"_ostr, pStructureType->GetValue());
|
||||
|
||||
// which element it is, not merely that it is one: a jump that leaves the cursor behind
|
||||
// names the paragraph the link sits in, and that reads as a plausible structure
|
||||
// destination to a validator
|
||||
auto pStructureS = dynamic_cast<vcl::filter::PDFNameElement*>(
|
||||
pStructureRef->LookupObject()->Lookup("S"_ostr));
|
||||
CPPUNIT_ASSERT(pStructureS);
|
||||
// an outline item has a Title, a link annotation does not
|
||||
if (pObject->Lookup("Title"_ostr))
|
||||
aOutlineTypes.push_back(pStructureS->GetValue());
|
||||
else
|
||||
aLinkTypes.push_back(pStructureS->GetValue());
|
||||
}
|
||||
|
||||
// the outline item names the heading it was made from
|
||||
CPPUNIT_ASSERT_EQUAL(size_t(1), aOutlineTypes.size());
|
||||
CPPUNIT_ASSERT_EQUAL("H1"_ostr, aOutlineTypes[0]);
|
||||
|
||||
// the shape's bookmark sits at the document's first position on purpose: the cursor is
|
||||
// already there, and nothing moves
|
||||
std::sort(aLinkTypes.begin(), aLinkTypes.end());
|
||||
CPPUNIT_ASSERT_EQUAL(size_t(4), aLinkTypes.size());
|
||||
CPPUNIT_ASSERT_EQUAL("Bibliography 1"_ostr, aLinkTypes[0]);
|
||||
CPPUNIT_ASSERT_EQUAL("Footnote"_ostr, aLinkTypes[1]);
|
||||
CPPUNIT_ASSERT_EQUAL("H1"_ostr, aLinkTypes[2]);
|
||||
CPPUNIT_ASSERT_EQUAL("Standard"_ostr, aLinkTypes[3]);
|
||||
}
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
||||
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
|
||||
<office:meta><dc:title>Structure destinations</dc:title></office:meta>
|
||||
<office:font-face-decls>
|
||||
<style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/>
|
||||
</office:font-face-decls>
|
||||
<office:styles>
|
||||
<style:default-style style:family="paragraph">
|
||||
<style:text-properties style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="US" style:font-name-asian="Liberation Serif" style:font-size-asian="12pt" style:font-name-complex="Liberation Serif" style:font-size-complex="12pt"/>
|
||||
</style:default-style>
|
||||
<style:style style:name="Standard" style:family="paragraph" style:class="text"/>
|
||||
<style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Standard" style:default-outline-level="1" style:class="text">
|
||||
<style:text-properties fo:font-size="16pt" fo:font-weight="bold"/>
|
||||
</style:style>
|
||||
<style:style style:name="Footnote" style:family="paragraph" style:parent-style-name="Standard" style:class="extra"/>
|
||||
<style:style style:name="Bibliography_20_1" style:display-name="Bibliography 1" style:family="paragraph" style:parent-style-name="Standard" style:class="index"/>
|
||||
</office:styles>
|
||||
<office:automatic-styles>
|
||||
<style:style style:name="gr1" style:family="graphic">
|
||||
<style:text-properties style:font-name="Liberation Serif" fo:font-size="12pt"/>
|
||||
</style:style>
|
||||
<style:page-layout style:name="pm1">
|
||||
<style:page-layout-properties fo:page-width="21cm" fo:page-height="29.7cm" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb"/>
|
||||
</style:page-layout>
|
||||
</office:automatic-styles>
|
||||
<office:master-styles>
|
||||
<style:master-page style:name="Standard" style:page-layout-name="pm1"/>
|
||||
</office:master-styles>
|
||||
<office:body>
|
||||
<office:text>
|
||||
<text:h text:style-name="Heading_20_1" text:outline-level="1"><text:bookmark text:name="mark1"/>A heading</text:h>
|
||||
<text:p text:style-name="Standard"><draw:custom-shape draw:style-name="gr1" text:anchor-type="paragraph" svg:width="5cm" svg:height="1cm" svg:x="1cm" svg:y="1cm"><text:p text:style-name="Standard"><text:a xlink:type="simple" xlink:href="#mark1">Jump to the heading</text:a></text:p><draw:enhanced-geometry draw:type="rectangle"/></draw:custom-shape></text:p>
|
||||
<text:p text:style-name="Standard">Body text with a footnote.<text:note text:id="ftn1" text:note-class="footnote"><text:note-citation>1</text:note-citation><text:note-body><text:p text:style-name="Footnote">The footnote body.</text:p></text:note-body></text:note></text:p>
|
||||
<text:p text:style-name="Standard">A citation <text:bibliography-mark text:identifier="AAA" text:bibliography-type="book" text:author="An author" text:title="A title" loext:target-type="3">[AAA]</text:bibliography-mark></text:p>
|
||||
<text:bibliography text:name="Bibliography1" text:protected="true">
|
||||
<text:bibliography-source>
|
||||
<text:bibliography-entry-template text:bibliography-type="book" text:style-name="Bibliography_20_1">
|
||||
<text:index-entry-bibliography text:bibliography-data-field="identifier"/>
|
||||
</text:bibliography-entry-template>
|
||||
</text:bibliography-source>
|
||||
<text:index-body>
|
||||
<text:p text:style-name="Bibliography_20_1">AAA</text:p>
|
||||
</text:index-body>
|
||||
</text:bibliography>
|
||||
</office:text>
|
||||
</office:body>
|
||||
</office:document>
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <EnhancedPDFExportHelper.hxx>
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <com/sun/star/embed/XEmbeddedObject.hpp>
|
||||
#include <com/sun/star/i18n/ScriptType.hpp>
|
||||
@@ -151,6 +152,8 @@ struct SwEnhancedPDFState
|
||||
NumListIdMap m_NumListIdMap;
|
||||
NumListBodyIdMap m_NumListBodyIdMap;
|
||||
FrameTagSet m_FrameTagSet;
|
||||
std::unordered_map<sal_Int32, const SwTextNode*> m_DestNodeMap;
|
||||
std::unordered_map<const SwTextNode*, sal_Int32> m_NodeTagIdMap;
|
||||
|
||||
LanguageType m_eLanguageDefault;
|
||||
|
||||
@@ -442,6 +445,22 @@ bool lcl_HasPreviousParaSameNumRule(SwTextFrame const& rTextFrame, const SwTextN
|
||||
return bRet;
|
||||
}
|
||||
|
||||
// a jump that selects an object, or finds nothing, moves the cursor nowhere and names no node
|
||||
const SwTextNode* lcl_JumpedToNode(const SwEditShell& rSh, const SwPosition& rBeforeJump)
|
||||
{
|
||||
const SwPosition& rPoint = *rSh.GetCursor_()->GetPoint();
|
||||
return rPoint == rBeforeJump ? nullptr : rPoint.GetNode().GetTextNode();
|
||||
}
|
||||
|
||||
// a destination is made before its target is tagged, so remember the node
|
||||
void lcl_RememberDestNode(vcl::PDFExtOutDevData& rPDFExtOutDevData, const SwTextNode* pTextNode,
|
||||
const sal_Int32 nDestId)
|
||||
{
|
||||
SwEnhancedPDFState* pState = rPDFExtOutDevData.GetSwPDFState();
|
||||
if (pState && nDestId >= 0 && pTextNode)
|
||||
pState->m_DestNodeMap.emplace(nDestId, pTextNode);
|
||||
}
|
||||
|
||||
bool lcl_TryMoveToNonHiddenField(SwEditShell& rShell, const SwTextNode& rNd, const SwFormatField& rField)
|
||||
{
|
||||
// 1. Check if the whole paragraph is hidden
|
||||
@@ -694,6 +713,16 @@ void SwTaggedPDFHelper::BeginTag(vcl::pdf::StructElement eType, const OUString&
|
||||
|
||||
sal_Int32 const nId = BeginTagImpl(pKey, eType, rString);
|
||||
|
||||
// which tag a destination pointing at this node names
|
||||
if (mpFrameInfo && mpFrameInfo->mrFrame.IsTextFrame())
|
||||
{
|
||||
const SwTextFrame& rTextFrame(static_cast<const SwTextFrame&>(mpFrameInfo->mrFrame));
|
||||
if (const SwTextNode* pTextNode = rTextFrame.GetTextNodeFirst())
|
||||
{
|
||||
mpPDFExtOutDevData->GetSwPDFState()->m_NodeTagIdMap.emplace(pTextNode, nId);
|
||||
}
|
||||
}
|
||||
|
||||
// Store the id of the current structure element if
|
||||
// - it is a list structure element
|
||||
// - it is a list body element with children
|
||||
@@ -2446,6 +2475,27 @@ tools::Rectangle SwEnhancedPDFExportHelper::MapSwRectToPDFRect(const SwPageFrame
|
||||
return aRect;
|
||||
}
|
||||
|
||||
sal_Int32 SwEnhancedPDFExportHelper::CreateDestination(const SwPageFrame* pCurrPage,
|
||||
const SwRect& rRect,
|
||||
const sal_Int32 nPageNum,
|
||||
const SwPosition& rBeforeJump)
|
||||
{
|
||||
return CreateDestination(pCurrPage, rRect, nPageNum, lcl_JumpedToNode(mrSh, rBeforeJump));
|
||||
}
|
||||
|
||||
sal_Int32 SwEnhancedPDFExportHelper::CreateDestination(const SwPageFrame* pCurrPage,
|
||||
const SwRect& rRect,
|
||||
const sal_Int32 nPageNum,
|
||||
const SwTextNode* pTarget)
|
||||
{
|
||||
assert(dynamic_cast<vcl::PDFExtOutDevData*>(mrOut.GetExtOutDevData()));
|
||||
auto& rPDFExtOutDevData = *static_cast<vcl::PDFExtOutDevData*>(mrOut.GetExtOutDevData());
|
||||
const sal_Int32 nDestId
|
||||
= rPDFExtOutDevData.CreateDest(SwRectToPDFRect(pCurrPage, rRect.SVRect()), nPageNum);
|
||||
lcl_RememberDestNode(rPDFExtOutDevData, pTarget, nDestId);
|
||||
return nDestId;
|
||||
}
|
||||
|
||||
void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDefault)
|
||||
{
|
||||
vcl::PDFExtOutDevData* pPDFExtOutDevData =
|
||||
@@ -2600,6 +2650,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
{
|
||||
aURL = aURL.copy( 1 );
|
||||
mrSh.SwCursorShell::ClearMark();
|
||||
const SwPosition aBeforeJump(*mrSh.GetCursor_()->GetPoint());
|
||||
if (! JumpToSwMark( &mrSh, SwMarkName(aURL) ))
|
||||
{
|
||||
continue; // target deleted
|
||||
@@ -2617,8 +2668,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
// Destination Export
|
||||
if ( -1 != nDestPageNum )
|
||||
{
|
||||
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
|
||||
nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
|
||||
nDestId = CreateDestination(pCurrPage, rDestRect, nDestPageNum,
|
||||
aBeforeJump);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2688,6 +2739,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
{
|
||||
aURL = aURL.copy( 1 );
|
||||
mrSh.SwCursorShell::ClearMark();
|
||||
const SwPosition aBeforeJump(*mrSh.GetCursor_()->GetPoint());
|
||||
if (! JumpToSwMark( &mrSh, SwMarkName(aURL) ))
|
||||
{
|
||||
continue; // target deleted
|
||||
@@ -2704,8 +2756,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
// Destination Export
|
||||
if ( -1 != nDestPageNum )
|
||||
{
|
||||
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
|
||||
nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
|
||||
nDestId
|
||||
= CreateDestination(pCurrPage, rDestRect, nDestPageNum, aBeforeJump);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2838,6 +2890,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
// Destination Rectangle
|
||||
const SwGetRefField* pField = static_cast<SwGetRefField*>(pFormatField->GetField());
|
||||
const SwMarkName& rRefName = pField->GetSetRefName();
|
||||
const SwPosition aBeforeJump(*mrSh.GetCursor_()->GetPoint());
|
||||
mrSh.GotoRefMark( rRefName, pField->GetSubType(), pField->GetSeqNo(), pField->GetFlags() );
|
||||
const SwRect& rDestRect = mrSh.GetCharRect();
|
||||
|
||||
@@ -2849,8 +2902,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
if ( -1 != nDestPageNum )
|
||||
{
|
||||
// Destination Export
|
||||
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
|
||||
const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
|
||||
const sal_Int32 nDestId
|
||||
= CreateDestination(pCurrPage, rDestRect, nDestPageNum, aBeforeJump);
|
||||
|
||||
// #i44368# Links in Header/Footer
|
||||
const bool bHeaderFooter = pDoc->IsInHeaderFooter( *pTNd );
|
||||
@@ -2868,7 +2921,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
for (sal_Int32 aLinkPageNum : aLinkPageNums)
|
||||
{
|
||||
// Link Export
|
||||
aRect = SwRectToPDFRect(pCurrPage, rLinkRect.SVRect());
|
||||
const tools::Rectangle aRect(
|
||||
SwRectToPDFRect(pCurrPage, rLinkRect.SVRect()));
|
||||
const sal_Int32 nLinkId =
|
||||
pPDFExtOutDevData->CreateLink(aRect, rRefName.toString(), aLinkPageNum);
|
||||
|
||||
@@ -2932,6 +2986,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
const SwRect aLinkRect( aTmp[ 0 ] );
|
||||
|
||||
// Goto footnote text:
|
||||
const SwPosition aBeforeFootnote(*mrSh.GetCursor_()->GetPoint());
|
||||
if ( mrSh.GotoFootnoteText() )
|
||||
{
|
||||
// Destination Rectangle
|
||||
@@ -2940,8 +2995,6 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
if ( -1 != nDestPageNum )
|
||||
{
|
||||
const SwPageFrame* pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
|
||||
// Destination PageNum
|
||||
tools::Rectangle aRect = SwRectToPDFRect(pCurrPage, rDestRect.SVRect());
|
||||
// Back link rectangle calculation
|
||||
const SwPageFrame* fnBodyPage = pCurrPage->getRootFrame()->GetPageByPageNum(nDestPageNum+1);
|
||||
SwRect fnSymbolRect;
|
||||
@@ -2968,16 +3021,19 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
// Export back link
|
||||
const sal_Int32 nBackLinkId = pPDFExtOutDevData->CreateLink(aFootnoteSymbolRect, numStrRef, nDestPageNum);
|
||||
// Destination Export
|
||||
const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
|
||||
const sal_Int32 nDestId
|
||||
= CreateDestination(pCurrPage, rDestRect, nDestPageNum, aBeforeFootnote);
|
||||
const SwPosition aBeforeAnchor(*mrSh.GetCursor_()->GetPoint());
|
||||
mrSh.GotoFootnoteAnchor();
|
||||
// Link PageNums
|
||||
sal_Int32 aLinkPageNum = CalcOutputPageNum( aLinkRect );
|
||||
pCurrPage = static_cast<const SwPageFrame*>( mrSh.GetLayout()->Lower() );
|
||||
// Link Export
|
||||
aRect = SwRectToPDFRect(pCurrPage, aLinkRect.SVRect());
|
||||
const tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, aLinkRect.SVRect()));
|
||||
const sal_Int32 nLinkId = pPDFExtOutDevData->CreateLink(aRect, numStrSymbol, aLinkPageNum);
|
||||
// Back link destination Export
|
||||
const sal_Int32 nBackDestId = pPDFExtOutDevData->CreateDest(aRect, aLinkPageNum);
|
||||
const sal_Int32 nBackDestId
|
||||
= CreateDestination(pCurrPage, aLinkRect, aLinkPageNum, aBeforeAnchor);
|
||||
// Store link info for tagged pdf output:
|
||||
const IdMapEntry aLinkEntry( aLinkRect, nLinkId );
|
||||
pPDFExtOutDevData->GetSwPDFState()->m_LinkIdMap.push_back(aLinkEntry);
|
||||
@@ -3042,6 +3098,7 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
const sal_Int32 nParent = aOutlineStack.top().second;
|
||||
|
||||
// Destination rectangle
|
||||
const SwPosition aBeforeJump(*mrSh.GetCursor_()->GetPoint());
|
||||
mrSh.GotoOutline(i);
|
||||
const SwRect& rDestRect = mrSh.GetCharRect();
|
||||
|
||||
@@ -3054,9 +3111,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
if ( -1 != nDestPageNum )
|
||||
{
|
||||
// Destination Export
|
||||
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
|
||||
const sal_Int32 nDestId =
|
||||
pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
|
||||
const sal_Int32 nDestId
|
||||
= CreateDestination(pCurrPage, rDestRect, nDestPageNum, aBeforeJump);
|
||||
|
||||
// Outline entry text
|
||||
const OUString aEntry = mrSh.getIDocumentOutlineNodesAccess()->getOutlineText(
|
||||
@@ -3183,7 +3239,8 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
if ( bInternal )
|
||||
{
|
||||
aBookmarkName = aBookmarkName.copy( 1 );
|
||||
JumpToSwMark( &mrSh, SwMarkName(aBookmarkName) );
|
||||
const SwPosition aBeforeJump(*mrSh.GetCursor_()->GetPoint());
|
||||
const bool bJumped = JumpToSwMark(&mrSh, SwMarkName(aBookmarkName));
|
||||
|
||||
// Destination Rectangle
|
||||
const SwRect& rDestRect = mrSh.GetCharRect();
|
||||
@@ -3196,18 +3253,32 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
|
||||
if ( -1 != nDestPageNum )
|
||||
{
|
||||
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
|
||||
if ( rBookmark.nLinkId != -1 )
|
||||
{
|
||||
const SwTextNode* pTarget = lcl_JumpedToNode(mrSh, aBeforeJump);
|
||||
if (!pTarget && bJumped)
|
||||
{
|
||||
// this pass can begin with the cursor on the target already, where a
|
||||
// successful jump shows no move, and the mark names its own node
|
||||
const IDocumentMarkAccess& rMarks = *pDoc->getIDocumentMarkAccess();
|
||||
const auto ppMark = rMarks.findMark(SwMarkName(INetURLObject::decode(
|
||||
aBookmarkName, INetURLObject::DecodeMechanism::WithCharset)));
|
||||
if (ppMark != rMarks.getAllMarksEnd())
|
||||
pTarget = (*ppMark)->GetMarkStart().GetNode().GetTextNode();
|
||||
}
|
||||
|
||||
// Destination Export
|
||||
const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
|
||||
const sal_Int32 nDestId
|
||||
= CreateDestination(pCurrPage, rDestRect, nDestPageNum, pTarget);
|
||||
|
||||
// Connect Link and Destination:
|
||||
pPDFExtOutDevData->SetLinkDest( rBookmark.nLinkId, nDestId );
|
||||
}
|
||||
else
|
||||
{
|
||||
pPDFExtOutDevData->DescribeRegisteredDest(rBookmark.nDestId, aRect, nDestPageNum);
|
||||
pPDFExtOutDevData->DescribeRegisteredDest(
|
||||
rBookmark.nDestId, SwRectToPDFRect(pCurrPage, rDestRect.SVRect()),
|
||||
nDestPageNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3217,6 +3288,18 @@ void SwEnhancedPDFExportHelper::EnhancedPDFExport(LanguageType const eLanguageDe
|
||||
rBookmarks.clear();
|
||||
assert(pPDFExtOutDevData->GetSwPDFState());
|
||||
assert(pPDFExtOutDevData->GetSwPDFState()->m_DeferredTags.empty());
|
||||
|
||||
// everything is tagged now, so each destination can name its element
|
||||
SwEnhancedPDFState& rState(*pPDFExtOutDevData->GetSwPDFState());
|
||||
for (const auto& rDest : rState.m_DestNodeMap)
|
||||
{
|
||||
const auto it(rState.m_NodeTagIdMap.find(rDest.second));
|
||||
if (it != rState.m_NodeTagIdMap.end())
|
||||
{
|
||||
pPDFExtOutDevData->SetDestStructureElement(rDest.first, it->second);
|
||||
}
|
||||
}
|
||||
|
||||
delete pPDFExtOutDevData->GetSwPDFState();
|
||||
pPDFExtOutDevData->SetSwPDFState(nullptr);
|
||||
}
|
||||
@@ -3265,9 +3348,10 @@ void SwEnhancedPDFExportHelper::ExportAuthorityEntryLinks()
|
||||
// Destination Export
|
||||
if ( -1 != nDestPageNum )
|
||||
{
|
||||
tools::Rectangle aRect(SwRectToPDFRect(pCurrPage, rDestRect.SVRect()));
|
||||
const sal_Int32 nDestId = pPDFExtOutDevData->CreateDest(aRect, nDestPageNum);
|
||||
const OUString* vNodeText = &static_cast<const SwTextNode*>(&rCurrentNode)->GetText();
|
||||
auto& rTextNode = static_cast<const SwTextNode&>(rCurrentNode);
|
||||
const sal_Int32 nDestId
|
||||
= CreateDestination(pCurrPage, rDestRect, nDestPageNum, &rTextNode);
|
||||
const OUString* vNodeText = &rTextNode.GetText();
|
||||
vDestinations.emplace_back(pIteratedTOX, vNodeText, nDestId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,6 +386,8 @@ struct PDFDest
|
||||
sal_Int32 m_nPage;
|
||||
PDFWriter::DestAreaType m_eType;
|
||||
tools::Rectangle m_aRect;
|
||||
/// index into m_aStructure, -1 when the target is not tagged
|
||||
sal_Int32 m_nStructElement = -1;
|
||||
};
|
||||
|
||||
//--->i56629
|
||||
@@ -995,6 +997,10 @@ private:
|
||||
sal_Int32 emitResources();
|
||||
// appends a dest
|
||||
bool appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer );
|
||||
/// ISO 14289-2 requires a GoTo action to name the structure element, not only the page
|
||||
bool appendStructureDest(sal_Int32 nDestID, OStringBuffer& rBuffer);
|
||||
/// writes /Dest, or the GoTo action carrying /SD when the target is tagged
|
||||
void appendDestOrGoTo(sal_Int32 nDestID, OStringBuffer& rBuffer);
|
||||
// write all links
|
||||
bool emitLinkAnnotations();
|
||||
// Write all screen annotations.
|
||||
@@ -1314,6 +1320,7 @@ public:
|
||||
sal_Int32 createDest( const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType );
|
||||
sal_Int32 registerDestReference( sal_Int32 nDestId, const tools::Rectangle& rRect, sal_Int32 nPageNr, PDFWriter::DestAreaType eType );
|
||||
void setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId );
|
||||
void setDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId);
|
||||
void setLinkURL( sal_Int32 nLinkId, const OUString& rURL );
|
||||
void setLinkPropertyId( sal_Int32 nLinkId, sal_Int32 nPropertyId );
|
||||
|
||||
|
||||
@@ -2200,9 +2200,9 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf173162)
|
||||
loadFromFile(u"StructureNamespaces.fodt");
|
||||
|
||||
// Tagged rather than PDF/UA, because the harness validates a PDF/UA export and this document
|
||||
// still fails ISO 14289-2 8.8 (a link destination is not a structure destination, tdf#171022),
|
||||
// 8.2.5.8 (a TOCI needs Ref) and 8.2.5.14 (PDF/UA-2 wants FENote, not Note). TODO: ask for
|
||||
// PDF/UA here once those are fixed. The namespaces below depend on the version alone.
|
||||
// still fails ISO 14289-2 8.2.5.8 (a TOCI needs Ref) and 8.2.5.14 (PDF/UA-2 wants FENote,
|
||||
// not Note). TODO: ask for PDF/UA here once those are fixed. The namespaces below depend on
|
||||
// the version alone.
|
||||
uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true),
|
||||
comphelper::makePropertyValue(u"SelectPdfVersion"_ustr,
|
||||
sal_Int32(20)) };
|
||||
|
||||
@@ -75,6 +75,11 @@ struct SetLinkDest {
|
||||
sal_Int32 mnLinkId;
|
||||
sal_Int32 mnDestId;
|
||||
};
|
||||
struct SetDestStructureElement
|
||||
{
|
||||
sal_Int32 mnDestId;
|
||||
sal_Int32 mnStructElementId;
|
||||
};
|
||||
struct SetLinkURL {
|
||||
OUString maLinkURL;
|
||||
sal_Int32 mnLinkId;
|
||||
@@ -144,6 +149,7 @@ typedef std::variant<CreateNamedDest,
|
||||
CreateLink,
|
||||
CreateScreen,
|
||||
SetLinkDest,
|
||||
SetDestStructureElement,
|
||||
SetLinkURL,
|
||||
SetScreenURL,
|
||||
SetScreenStream,
|
||||
@@ -274,6 +280,11 @@ void GlobalSyncData::PlayGlobalActions( pdf::PDFWriter& rWriter )
|
||||
sal_Int32 nDestId = GetMappedId(rSetLinkDest.mnDestId);
|
||||
rWriter.SetLinkDest( nLinkId, nDestId );
|
||||
}
|
||||
else if (std::holds_alternative<SetDestStructureElement>(action))
|
||||
{
|
||||
const vcl::SetDestStructureElement& rSet = std::get<SetDestStructureElement>(action);
|
||||
rWriter.SetDestStructureElement(GetMappedId(rSet.mnDestId), rSet.mnStructElementId);
|
||||
}
|
||||
else if (std::holds_alternative<SetLinkURL>(action)) {
|
||||
const vcl::SetLinkURL& rSetLinkURL = std::get<SetLinkURL>(action);
|
||||
sal_Int32 nLinkId = GetMappedId(rSetLinkURL.mnLinkId);
|
||||
@@ -703,6 +714,11 @@ sal_Int32 PDFExtOutDevData::CreateScreen(const tools::Rectangle& rRect,
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void PDFExtOutDevData::SetDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId)
|
||||
{
|
||||
mpGlobalSyncData->mActions.push_back(vcl::SetDestStructureElement{ nDestId, nStructElementId });
|
||||
}
|
||||
|
||||
void PDFExtOutDevData::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
|
||||
{
|
||||
mpGlobalSyncData->mActions.push_back( vcl::SetLinkDest{ nLinkId, nDestId } );
|
||||
|
||||
@@ -353,6 +353,11 @@ void PDFWriter::SetLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
|
||||
xImplementation->setLinkDest( nLinkId, nDestId );
|
||||
}
|
||||
|
||||
void PDFWriter::SetDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId)
|
||||
{
|
||||
xImplementation->setDestStructureElement(nDestId, nStructElementId);
|
||||
}
|
||||
|
||||
void PDFWriter::SetLinkURL( sal_Int32 nLinkId, const OUString& rURL )
|
||||
{
|
||||
xImplementation->setLinkURL( nLinkId, rURL );
|
||||
|
||||
@@ -2552,8 +2552,7 @@ sal_Int32 PDFWriterImpl::emitOutline()
|
||||
// Dest is not required
|
||||
if( rItem.m_nDestID >= 0 && o3tl::make_unsigned(rItem.m_nDestID) < m_aDests.size() )
|
||||
{
|
||||
aLine.append( "/Dest" );
|
||||
appendDest( rItem.m_nDestID, aLine );
|
||||
appendDestOrGoTo(rItem.m_nDestID, aLine);
|
||||
}
|
||||
aLine.append( "/Parent "
|
||||
+ OString::number( rItem.m_nParentObject )
|
||||
@@ -2579,6 +2578,47 @@ sal_Int32 PDFWriterImpl::emitOutline()
|
||||
return m_aOutline[0].m_nObject;
|
||||
}
|
||||
|
||||
bool PDFWriterImpl::appendStructureDest(sal_Int32 nDestID, OStringBuffer& rBuffer)
|
||||
{
|
||||
if (nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size())
|
||||
return false;
|
||||
|
||||
const PDFDest& rDest = m_aDests[nDestID];
|
||||
if (rDest.m_nStructElement < 0
|
||||
|| o3tl::make_unsigned(rDest.m_nStructElement) >= m_aStructure.size())
|
||||
return false;
|
||||
|
||||
const sal_Int32 nObject(m_aStructure[rDest.m_nStructElement].m_nObject);
|
||||
if (nObject <= 0) // not emitted: NonStruct, or no structure at all
|
||||
return false;
|
||||
|
||||
// same as the page destination, bar the first entry
|
||||
OStringBuffer aPageDest;
|
||||
if (!appendDest(nDestID, aPageDest))
|
||||
return false;
|
||||
const OString aPage(aPageDest.makeStringAndClear());
|
||||
const sal_Int32 nAfterPage(aPage.indexOf(" 0 R") + 4);
|
||||
|
||||
rBuffer.append("[" + OString::number(nObject) + " 0 R" + aPage.subView(nAfterPage));
|
||||
return true;
|
||||
}
|
||||
|
||||
void PDFWriterImpl::appendDestOrGoTo(sal_Int32 nDestID, OStringBuffer& rBuffer)
|
||||
{
|
||||
OStringBuffer aStructure;
|
||||
if (!appendStructureDest(nDestID, aStructure))
|
||||
{
|
||||
rBuffer.append("/Dest");
|
||||
appendDest(nDestID, rBuffer);
|
||||
return;
|
||||
}
|
||||
|
||||
// ISO 14289-2 8.8: a destination inside the document shall be a structure destination
|
||||
rBuffer.append("/A<</Type/Action/S/GoTo/D");
|
||||
appendDest(nDestID, rBuffer);
|
||||
rBuffer.append("/SD" + aStructure.makeStringAndClear() + ">>");
|
||||
}
|
||||
|
||||
bool PDFWriterImpl::appendDest( sal_Int32 nDestID, OStringBuffer& rBuffer )
|
||||
{
|
||||
if( nDestID < 0 || o3tl::make_unsigned(nDestID) >= m_aDests.size() )
|
||||
@@ -2826,8 +2866,7 @@ bool PDFWriterImpl::emitLinkAnnotations()
|
||||
}
|
||||
if( rLink.m_nDest >= 0 )
|
||||
{
|
||||
aLine.append( "/Dest" );
|
||||
appendDest( rLink.m_nDest, aLine );
|
||||
appendDestOrGoTo(rLink.m_nDest, aLine);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -9932,6 +9971,14 @@ sal_Int32 PDFWriterImpl::registerDestReference( sal_Int32 nDestId, const tools::
|
||||
return m_aDestinationIdTranslation[ nDestId ];
|
||||
}
|
||||
|
||||
void PDFWriterImpl::setDestStructureElement(sal_Int32 nDestId, sal_Int32 nStructElementId)
|
||||
{
|
||||
if (nDestId < 0 || o3tl::make_unsigned(nDestId) >= m_aDests.size())
|
||||
return;
|
||||
|
||||
m_aDests[nDestId].m_nStructElement = nStructElementId;
|
||||
}
|
||||
|
||||
void PDFWriterImpl::setLinkDest( sal_Int32 nLinkId, sal_Int32 nDestId )
|
||||
{
|
||||
if( nLinkId < 0 || o3tl::make_unsigned(nLinkId) >= m_aLinks.size() )
|
||||
|
||||
Reference in New Issue
Block a user