tdf#163240 vcl,sw: keep one Link tag for a wrapped link
Regression after commit a71da3b7a8
(tdf#156565 sw: PDF/UA export: split Link SE at line break,
2023-11-01), which closed the tag at each line break so that every
annotation had a tag to nest in.
A structure element could reference one annotation only, the
LinkAnnotation attribute being a value in a std::map; give it a vector
and emit an OBJR kid and a parent tree entry per annotation, so each
StructParent resolves to that tag. A page or column break still ends
the tag.
Language, baseline shift and decoration now come from the link's first
portion, at a line break as much as within a line - splitting the tag
at a break would only make the result depend on where the line ends.
LATER: subdivide the Link by Spans, which covers both.
testTdf157816Link and testTdf142806 expected a tag per annotation.
Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I493439a33b2cbc0eecf1af6b300859cd78ee87ba
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/11541
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit ade287e6a4edf7476e667f48082261deeddd96d5)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211077
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
committed by
Xisco Fauli
parent
692482b522
commit
79b06ca748
@@ -180,8 +180,6 @@ class SwTaggedPDFHelper
|
||||
SwTaggedPDFHelper( const Num_Info* pNumInfo, const Frame_Info* pFrameInfo, const Por_Info* pPorInfo,
|
||||
OutputDevice const & rOut );
|
||||
~SwTaggedPDFHelper();
|
||||
|
||||
static void EndCurrentLink(OutputDevice const&);
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include <EnhancedPDFExportHelper.hxx>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <com/sun/star/embed/XEmbeddedObject.hpp>
|
||||
#include <com/sun/star/i18n/ScriptType.hpp>
|
||||
#include <com/sun/star/drawing/XShape.hpp>
|
||||
@@ -164,8 +166,16 @@ struct SwEnhancedPDFState
|
||||
OUString StyleName;
|
||||
};
|
||||
|
||||
struct Link
|
||||
{
|
||||
const SwTextAttr* pAttr;
|
||||
sal_Int32 nStructElement;
|
||||
// the annotations already nested in it
|
||||
std::vector<sal_Int32> AnnotIds;
|
||||
};
|
||||
|
||||
::std::optional<Span> m_oCurrentSpan;
|
||||
::std::optional<SwTextAttr const*> m_oCurrentLink;
|
||||
std::optional<Link> m_oCurrentLink;
|
||||
|
||||
SwEnhancedPDFState(LanguageType const eLanguageDefault)
|
||||
: m_eLanguageDefault(eLanguageDefault)
|
||||
@@ -722,15 +732,34 @@ namespace {
|
||||
// link the link annotation to the link structured element
|
||||
void LinkLinkLink(vcl::PDFExtOutDevData & rPDFExtOutDevData, SwRect const& rRect)
|
||||
{
|
||||
const sal_Int32 nCurrentSE(rPDFExtOutDevData.GetCurrentStructureElement());
|
||||
auto& roCurrentLink(rPDFExtOutDevData.GetSwPDFState()->m_oCurrentLink);
|
||||
if (roCurrentLink && roCurrentLink->nStructElement == -1)
|
||||
{
|
||||
// the first call after opening a link is for the link's own SE
|
||||
roCurrentLink->nStructElement = nCurrentSE;
|
||||
}
|
||||
|
||||
const LinkIdMap& rLinkIdMap(rPDFExtOutDevData.GetSwPDFState()->m_LinkIdMap);
|
||||
const Point aCenter = rRect.Center();
|
||||
auto aIter = std::find_if(rLinkIdMap.begin(), rLinkIdMap.end(),
|
||||
[&aCenter](const IdMapEntry& rEntry) { return rEntry.first.Contains(aCenter); });
|
||||
if (aIter != rLinkIdMap.end())
|
||||
if (aIter == rLinkIdMap.end())
|
||||
return;
|
||||
|
||||
sal_Int32 nLinkId = (*aIter).second;
|
||||
if (roCurrentLink && roCurrentLink->nStructElement == nCurrentSE) // not a nested SE
|
||||
{
|
||||
sal_Int32 nLinkId = (*aIter).second;
|
||||
rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::pdf::PDFWriter::LinkAnnotation, nLinkId);
|
||||
// every portion of a line finds that line's annotation again
|
||||
if (std::find(roCurrentLink->AnnotIds.begin(), roCurrentLink->AnnotIds.end(), nLinkId)
|
||||
!= roCurrentLink->AnnotIds.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
roCurrentLink->AnnotIds.push_back(nLinkId);
|
||||
}
|
||||
rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::pdf::PDFWriter::LinkAnnotation,
|
||||
nLinkId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1900,20 +1929,6 @@ void SwTaggedPDFHelper::EndStructureElements()
|
||||
CheckRestoreTag();
|
||||
}
|
||||
|
||||
void SwTaggedPDFHelper::EndCurrentLink(OutputDevice const& rOut)
|
||||
{
|
||||
vcl::PDFExtOutDevData *const pPDFExtOutDevData(
|
||||
dynamic_cast<vcl::PDFExtOutDevData *>(rOut.GetExtOutDevData()));
|
||||
if (pPDFExtOutDevData && pPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
|
||||
{
|
||||
pPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.reset();
|
||||
pPDFExtOutDevData->EndStructureElement();
|
||||
#if OSL_DEBUG_LEVEL > 1
|
||||
aStructStack.pop_back();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void SwTaggedPDFHelper::EndCurrentAll()
|
||||
{
|
||||
if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
|
||||
@@ -1960,7 +1975,8 @@ bool SwTaggedPDFHelper::CheckContinueSpan(
|
||||
|| !mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
|
||||
if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
|
||||
{
|
||||
if (pInetFormatAttr && pInetFormatAttr == *mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink)
|
||||
if (pInetFormatAttr
|
||||
&& pInetFormatAttr == mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink->pAttr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -2050,17 +2066,6 @@ void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
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 :
|
||||
@@ -2077,10 +2082,18 @@ void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
nPDFType = sal_uInt16(vcl::pdf::StructElement::Link);
|
||||
aPDFType = aLinkString;
|
||||
assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
|
||||
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace(pInetFormatAttr);
|
||||
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace(
|
||||
SwEnhancedPDFState::Link{ pInetFormatAttr, -1, {} });
|
||||
// leave it open to let next portion decide to merge or close
|
||||
--m_nEndStructureElement;
|
||||
}
|
||||
else
|
||||
{
|
||||
// attach this portion's annotation
|
||||
SwRect aPorRect;
|
||||
rInf.CalcRect(*pPor, &aPorRect);
|
||||
LinkLinkLink(*mpPDFExtOutDevData, aPorRect);
|
||||
}
|
||||
}
|
||||
// Emphasis
|
||||
else if (sStyleName == constEmphasisStyleName)
|
||||
|
||||
@@ -263,8 +263,6 @@ void SwTextPainter::DrawTextLine( const SwRect &rPaint, SwSaveClip &rClip,
|
||||
roTaggedParagraph.emplace(nullptr, &aFrameInfo, nullptr, *GetInfo().GetOut());
|
||||
}
|
||||
|
||||
SwTaggedPDFHelper::EndCurrentLink(*GetInfo().GetOut());
|
||||
|
||||
// Optimization!
|
||||
SwTwips nMaxRight = std::min<SwTwips>( rPaint.Right(), Right() );
|
||||
const SwTwips nTmpLeft = GetInfo().X();
|
||||
|
||||
@@ -602,6 +602,8 @@ struct PDFStructureElement
|
||||
std::map<PDFWriter::StructAttribute, PDFStructureAttribute >
|
||||
m_aAttributes;
|
||||
::std::vector<sal_Int32> m_AnnotIds;
|
||||
// a Link has one annotation per line, and per fly gap
|
||||
std::vector<sal_Int32> m_LinkAnnotIds;
|
||||
tools::Rectangle m_aBBox;
|
||||
OUString m_aActualText;
|
||||
OUString m_aAltText;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
|
||||
<office:meta><dc:title>Link on several lines</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="Internet_20_link" style:display-name="Internet link" style:family="text">
|
||||
<style:text-properties fo:color="#000080" style:text-underline-style="solid" style:text-underline-width="auto" style:text-underline-color="font-color"/>
|
||||
</style:style>
|
||||
</office:styles>
|
||||
<office:automatic-styles>
|
||||
<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:p text:style-name="Standard"><text:a xlink:type="simple" xlink:href="https://www.example.com/" office:name="Example link" text:style-name="Internet_20_link">This is a hyperlink whose text is deliberately long enough that the Writer layout has to break it onto a second line of the same paragraph, which used to produce one Link structure element per line.</text:a></text:p>
|
||||
</office:text>
|
||||
</office:body>
|
||||
</office:document>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -940,30 +940,6 @@ OString PDFWriterImpl::emitStructureAttributes( PDFStructureElement& i_rEle )
|
||||
{
|
||||
appendStructureAttributeLine( attribute.first, attribute.second, aTable, false );
|
||||
}
|
||||
else if( attribute.first == PDFWriter::LinkAnnotation )
|
||||
{
|
||||
sal_Int32 nLink = attribute.second.nValue;
|
||||
std::map< sal_Int32, sal_Int32 >::const_iterator link_it =
|
||||
m_aLinkPropertyMap.find( nLink );
|
||||
if( link_it != m_aLinkPropertyMap.end() )
|
||||
nLink = link_it->second;
|
||||
if( nLink >= 0 && o3tl::make_unsigned(nLink) < m_aLinks.size() )
|
||||
{
|
||||
AppendAnnotKid(i_rEle, m_aLinks[nLink]);
|
||||
}
|
||||
else
|
||||
{
|
||||
OSL_FAIL( "unresolved link id for Link structure" );
|
||||
SAL_INFO("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
|
||||
if (g_bDebugDisableCompression)
|
||||
{
|
||||
OString aLine = "unresolved link id " +
|
||||
OString::number( nLink ) +
|
||||
" for Link structure";
|
||||
emitComment( aLine.getStr() );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (attribute.first == PDFWriter::NoteAnnotation)
|
||||
{
|
||||
sal_Int32 nNote = attribute.second.nValue;
|
||||
@@ -1250,6 +1226,27 @@ sal_Int32 PDFWriterImpl::emitStructure( PDFStructureElement& rEle )
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto id : rEle.m_LinkAnnotIds)
|
||||
{
|
||||
sal_Int32 nLink(id);
|
||||
const auto it(m_aLinkPropertyMap.find(nLink));
|
||||
if (it != m_aLinkPropertyMap.end())
|
||||
nLink = it->second;
|
||||
if (0 <= nLink && o3tl::make_unsigned(nLink) < m_aLinks.size())
|
||||
{
|
||||
AppendAnnotKid(rEle, m_aLinks[nLink]);
|
||||
}
|
||||
else
|
||||
{
|
||||
SAL_WARN("vcl.pdfwriter", "unresolved link id " << nLink << " for Link structure");
|
||||
if (g_bDebugDisableCompression)
|
||||
{
|
||||
const OString aComment
|
||||
= "unresolved link id " + OString::number(nLink) + " for Link structure";
|
||||
emitComment(aComment.getStr());
|
||||
}
|
||||
}
|
||||
}
|
||||
if( ! rEle.m_aKids.empty() )
|
||||
{
|
||||
unsigned int i = 0;
|
||||
@@ -10997,8 +10994,18 @@ bool PDFWriterImpl::setStructureAttributeNumerical( enum PDFWriter::StructAttrib
|
||||
}
|
||||
}
|
||||
|
||||
if( bInsert )
|
||||
m_aStructure[ m_nCurrentStructElement ].m_aAttributes[ eAttr ] = PDFStructureAttribute( nValue );
|
||||
if (bInsert)
|
||||
{
|
||||
if (eAttr == PDFWriter::LinkAnnotation)
|
||||
{
|
||||
m_aStructure[m_nCurrentStructElement].m_LinkAnnotIds.push_back(nValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_aStructure[m_nCurrentStructElement].m_aAttributes[eAttr]
|
||||
= PDFStructureAttribute(nValue);
|
||||
}
|
||||
}
|
||||
else if( m_nCurrentStructElement > 0 && m_bEmitStructure )
|
||||
SAL_INFO("vcl.pdfwriter",
|
||||
"rejecting setStructureAttributeNumerical( " << getAttributeTag( eAttr )
|
||||
|
||||
Reference in New Issue
Block a user