tdf#163240 sw: subdivide a Link by Spans
The commit "tdf#163240 vcl,sw: keep one Link tag for a wrapped link" made language, baseline shift and decoration come from the link's first portion for the whole link, and noted subdividing by Spans as the fix: those three are attributes of the Link tag, and there is now one tag per hyperlink, so a change inside it had nowhere to go. Open a Span in the Link instead. A span waits inside its link, so two tags can be open at once, and an annotation goes to the link's own tag - vcl takes it on a Link only, and a styled run crossing a line break would otherwise leave that line's annotation without a StructParent. Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com> Change-Id: I302d32f66fcb4e3b1eaf63b136bdcf96aaea17b7 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/11543 Tested-by: Jenkins CPCI <releng@collaboraoffice.com> Reviewed-by: Miklos Vajna <vmiklos@collabora.com> (cherry picked from commit 8f52d63f897aaf710dfe4cd9771f39689471abfd) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211101 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
committed by
Xisco Fauli
parent
1d901c0707
commit
6e785d7b0b
@@ -167,10 +167,19 @@ class SwTaggedPDFHelper
|
||||
void BeginInlineStructureElements();
|
||||
void EndStructureElements();
|
||||
|
||||
// what an open link or span leaves for this portion to do
|
||||
enum class Continuation
|
||||
{
|
||||
None, // nothing is open that covers it
|
||||
Whole, // the open tag covers it, so no new tag
|
||||
SpanInLink // the link goes on, but its properties do not
|
||||
};
|
||||
|
||||
void EndCurrentAll();
|
||||
void EndCurrentSpan();
|
||||
void CreateCurrentSpan(SwTextPaintInfo const& rInf, OUString const& rStyleName);
|
||||
bool CheckContinueSpan(SwTextPaintInfo const& rInf, std::u16string_view rStyleName, SwTextAttr const* pInetFormatAttr);
|
||||
Continuation CheckContinuation(SwTextPaintInfo const& rInf, OUString const& rStyleName,
|
||||
SwTextAttr const* pInetFormatAttr);
|
||||
|
||||
bool CheckReopenTag();
|
||||
void CheckRestoreTag() const;
|
||||
|
||||
@@ -164,6 +164,8 @@ struct SwEnhancedPDFState
|
||||
SwFontScript nScript;
|
||||
LanguageType nLang;
|
||||
OUString StyleName;
|
||||
|
||||
bool operator==(const Span&) const = default;
|
||||
};
|
||||
|
||||
struct Link
|
||||
@@ -172,6 +174,8 @@ struct SwEnhancedPDFState
|
||||
sal_Int32 nStructElement;
|
||||
// the annotations already nested in it
|
||||
std::vector<sal_Int32> AnnotIds;
|
||||
// the properties the Link's own tag carries
|
||||
Span aSpan;
|
||||
};
|
||||
|
||||
::std::optional<Span> m_oCurrentSpan;
|
||||
@@ -732,7 +736,7 @@ void SwTaggedPDFHelper::EndTag()
|
||||
void SwTaggedPDFHelper::DeferTag()
|
||||
{
|
||||
assert(!m_aOpenedTags.empty());
|
||||
assert(mpPDFExtOutDevData->GetSwPDFState()->m_DeferredTags.empty());
|
||||
assert(mpPDFExtOutDevData->GetSwPDFState()->m_DeferredTags.size() <= 1); // a span in a link
|
||||
assert(mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan
|
||||
|| mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
|
||||
mpPDFExtOutDevData->GetSwPDFState()->m_DeferredTags.push_back(m_aOpenedTags.back());
|
||||
@@ -750,9 +754,18 @@ void SwTaggedPDFHelper::EndDeferredTag()
|
||||
{
|
||||
assert(IsDeferredTagCurrent());
|
||||
auto& rState(*mpPDFExtOutDevData->GetSwPDFState());
|
||||
assert(!rState.m_oCurrentSpan || !rState.m_oCurrentLink); // one reset covers both
|
||||
rState.m_oCurrentSpan.reset();
|
||||
rState.m_oCurrentLink.reset();
|
||||
// one deferred tag per open span or link
|
||||
assert(rState.m_DeferredTags.size()
|
||||
== (rState.m_oCurrentSpan ? 1u : 0u) + (rState.m_oCurrentLink ? 1u : 0u));
|
||||
// a span is inside the link, so it is the innermost of the two
|
||||
if (rState.m_oCurrentSpan)
|
||||
{
|
||||
rState.m_oCurrentSpan.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
rState.m_oCurrentLink.reset();
|
||||
}
|
||||
rState.m_DeferredTags.pop_back();
|
||||
EndTag();
|
||||
}
|
||||
@@ -787,7 +800,11 @@ namespace {
|
||||
return;
|
||||
|
||||
sal_Int32 nLinkId = (*aIter).second;
|
||||
if (roCurrentLink && roCurrentLink->nStructElement == nCurrentSE) // not a nested SE
|
||||
// a Link of its own, for a footnote or a fly, is not the link being subdivided
|
||||
const auto& rDeferredTags(rPDFExtOutDevData.GetSwPDFState()->m_DeferredTags);
|
||||
const bool bInSpanOfLink(roCurrentLink && rPDFExtOutDevData.GetSwPDFState()->m_oCurrentSpan
|
||||
&& !rDeferredTags.empty() && rDeferredTags.back() == nCurrentSE);
|
||||
if (roCurrentLink && (roCurrentLink->nStructElement == nCurrentSE || bInSpanOfLink))
|
||||
{
|
||||
// every portion of a line finds that line's annotation again
|
||||
if (std::find(roCurrentLink->AnnotIds.begin(), roCurrentLink->AnnotIds.end(), nLinkId)
|
||||
@@ -796,6 +813,15 @@ namespace {
|
||||
return;
|
||||
}
|
||||
roCurrentLink->AnnotIds.push_back(nLinkId);
|
||||
if (bInSpanOfLink)
|
||||
{
|
||||
// vcl accepts the attribute on a Link, so set it on the link
|
||||
rPDFExtOutDevData.SetCurrentStructureElement(roCurrentLink->nStructElement);
|
||||
rPDFExtOutDevData.SetStructureAttributeNumerical(
|
||||
vcl::pdf::PDFWriter::LinkAnnotation, nLinkId);
|
||||
rPDFExtOutDevData.SetCurrentStructureElement(nCurrentSE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
rPDFExtOutDevData.SetStructureAttributeNumerical(vcl::pdf::PDFWriter::LinkAnnotation,
|
||||
nLinkId);
|
||||
@@ -1975,73 +2001,78 @@ void SwTaggedPDFHelper::EndCurrentSpan()
|
||||
EndDeferredTag();
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
// the properties a Span tag would carry for this portion
|
||||
SwEnhancedPDFState::Span MakeSpan(const SwTextPaintInfo& rInf, const OUString& rStyleName)
|
||||
{
|
||||
return { .eUnderline = rInf.GetFont()->GetUnderline(),
|
||||
.eOverline = rInf.GetFont()->GetOverline(),
|
||||
.eStrikeout = rInf.GetFont()->GetStrikeout(),
|
||||
.eFontEmphasis = rInf.GetFont()->GetEmphasisMark(),
|
||||
.nEscapement = rInf.GetFont()->GetEscapement(),
|
||||
.nScript = rInf.GetFont()->GetActual(),
|
||||
.nLang = rInf.GetFont()->GetLanguage(),
|
||||
.StyleName = rStyleName };
|
||||
}
|
||||
}
|
||||
|
||||
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});
|
||||
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan.emplace(MakeSpan(rInf, rStyleName));
|
||||
}
|
||||
|
||||
bool SwTaggedPDFHelper::CheckContinueSpan(
|
||||
SwTextPaintInfo const& rInf, std::u16string_view const rStyleName,
|
||||
SwTextAttr const*const pInetFormatAttr)
|
||||
SwTaggedPDFHelper::Continuation
|
||||
SwTaggedPDFHelper::CheckContinuation(SwTextPaintInfo const& rInf, OUString 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)
|
||||
auto& rState(*mpPDFExtOutDevData->GetSwPDFState());
|
||||
if (rState.m_oCurrentLink)
|
||||
{
|
||||
if (pInetFormatAttr
|
||||
&& pInetFormatAttr == mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink->pAttr)
|
||||
if (!pInetFormatAttr || pInetFormatAttr != rState.m_oCurrentLink->pAttr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsDeferredTagCurrent()) // a ruby leaves it to the paragraph
|
||||
// the span is inside the link, so it closes first
|
||||
if (rState.m_oCurrentSpan && IsDeferredTagCurrent())
|
||||
{
|
||||
EndCurrentSpan();
|
||||
}
|
||||
if (IsDeferredTagCurrent())
|
||||
{
|
||||
EndDeferredTag();
|
||||
}
|
||||
return false;
|
||||
return Continuation::None;
|
||||
}
|
||||
}
|
||||
if (mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan && pInetFormatAttr)
|
||||
{
|
||||
if (IsDeferredTagCurrent()) // a fly leaves it to the paragraph
|
||||
// the link goes on; a property change subdivides it instead of splitting it
|
||||
const SwEnhancedPDFState::Span aSpan(MakeSpan(rInf, rStyleName));
|
||||
if (rState.m_oCurrentSpan)
|
||||
{
|
||||
EndCurrentSpan();
|
||||
if (aSpan == *rState.m_oCurrentSpan)
|
||||
{
|
||||
return Continuation::Whole;
|
||||
}
|
||||
if (IsDeferredTagCurrent())
|
||||
{
|
||||
EndCurrentSpan();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return aSpan == rState.m_oCurrentLink->aSpan ? Continuation::Whole
|
||||
: Continuation::SpanInLink;
|
||||
}
|
||||
|
||||
if (!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan)
|
||||
return false;
|
||||
|
||||
SwEnhancedPDFState::Span const& rCurrent(*mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentSpan);
|
||||
|
||||
bool const ret(rCurrent.eUnderline == rInf.GetFont()->GetUnderline()
|
||||
&& rCurrent.eOverline == rInf.GetFont()->GetOverline()
|
||||
&& rCurrent.eStrikeout == rInf.GetFont()->GetStrikeout()
|
||||
&& rCurrent.eFontEmphasis == rInf.GetFont()->GetEmphasisMark()
|
||||
&& rCurrent.nEscapement == rInf.GetFont()->GetEscapement()
|
||||
&& rCurrent.nScript == rInf.GetFont()->GetActual()
|
||||
&& rCurrent.nLang == rInf.GetFont()->GetLanguage()
|
||||
&& rCurrent.StyleName == rStyleName);
|
||||
if (!ret && IsDeferredTagCurrent()) // a fly leaves it to the paragraph
|
||||
if (!rState.m_oCurrentSpan)
|
||||
{
|
||||
return Continuation::None;
|
||||
}
|
||||
if (!pInetFormatAttr && MakeSpan(rInf, rStyleName) == *rState.m_oCurrentSpan)
|
||||
{
|
||||
return Continuation::Whole;
|
||||
}
|
||||
if (IsDeferredTagCurrent()) // a fly in the span leaves it to the paragraph
|
||||
{
|
||||
EndCurrentSpan();
|
||||
}
|
||||
return ret;
|
||||
return Continuation::None;
|
||||
}
|
||||
|
||||
void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
@@ -2072,12 +2103,14 @@ void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
SwStyleNameMapper::FillProgName( pCharFormat->GetName(), sStyleName, SwGetPoolIdFromName::TxtColl );
|
||||
}
|
||||
|
||||
const OUString aStyleName(sStyleName.toString());
|
||||
// note: ILSE may be nested, so only end the span if needed to start new one
|
||||
bool const isContinueSpan(CheckContinueSpan(rInf, sStyleName.toString(), pInetFormatAttr));
|
||||
const Continuation eContinuation(CheckContinuation(rInf, aStyleName, pInetFormatAttr));
|
||||
|
||||
const sal_Int32 nParentSE(mpPDFExtOutDevData->GetCurrentStructureElement());
|
||||
const auto& rDeferredTags(mpPDFExtOutDevData->GetSwPDFState()->m_DeferredTags);
|
||||
// only one tag waits for the next portion; a ruby closes the rest with itself
|
||||
const bool bCanDefer(rDeferredTags.empty());
|
||||
// a span may be deferred inside its link, but not inside a ruby
|
||||
const bool bCanDefer(rDeferredTags.empty() || rDeferredTags.back() == nParentSE);
|
||||
sal_uInt16 nPDFType = USHRT_MAX;
|
||||
OUString aPDFType;
|
||||
bool bDeferTag(false);
|
||||
@@ -2113,7 +2146,7 @@ void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
// Check for Link:
|
||||
if( pInetFormatAttr )
|
||||
{
|
||||
if (!isContinueSpan)
|
||||
if (eContinuation == Continuation::None)
|
||||
{
|
||||
nPDFType = sal_uInt16(vcl::pdf::StructElement::Link);
|
||||
aPDFType = aLinkString;
|
||||
@@ -2121,7 +2154,8 @@ void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
{
|
||||
assert(!mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink);
|
||||
mpPDFExtOutDevData->GetSwPDFState()->m_oCurrentLink.emplace(
|
||||
SwEnhancedPDFState::Link{ pInetFormatAttr, -1, {} });
|
||||
SwEnhancedPDFState::Link{
|
||||
pInetFormatAttr, -1, {}, MakeSpan(rInf, aStyleName) });
|
||||
bDeferTag = true;
|
||||
}
|
||||
}
|
||||
@@ -2131,12 +2165,18 @@ void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
SwRect aPorRect;
|
||||
rInf.CalcRect(*pPor, &aPorRect);
|
||||
LinkLinkLink(*mpPDFExtOutDevData, aPorRect);
|
||||
if (eContinuation == Continuation::SpanInLink)
|
||||
{
|
||||
nPDFType = sal_uInt16(vcl::pdf::StructElement::Span);
|
||||
aPDFType = aSpanString;
|
||||
bDeferSpan = bCanDefer;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Emphasis
|
||||
else if (sStyleName == constEmphasisStyleName)
|
||||
{
|
||||
if (!isContinueSpan)
|
||||
if (eContinuation == Continuation::None)
|
||||
{
|
||||
nPDFType = sal_uInt16(vcl::pdf::StructElement::Emphasis);
|
||||
aPDFType = constEmphasisStyleName;
|
||||
@@ -2146,7 +2186,7 @@ void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
// Strong
|
||||
else if (sStyleName == constStrongEmphasisStyleName)
|
||||
{
|
||||
if (!isContinueSpan)
|
||||
if (eContinuation == Continuation::None)
|
||||
{
|
||||
nPDFType = sal_uInt16(vcl::pdf::StructElement::Strong);
|
||||
aPDFType = constStrongEmphasisStyleName;
|
||||
@@ -2156,7 +2196,7 @@ void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
// Check for Quote/Code character style:
|
||||
else if (sStyleName == aQuotation)
|
||||
{
|
||||
if (!isContinueSpan)
|
||||
if (eContinuation == Continuation::None)
|
||||
{
|
||||
nPDFType = sal_uInt16(vcl::pdf::StructElement::Quote);
|
||||
aPDFType = aQuoteString;
|
||||
@@ -2165,14 +2205,14 @@ void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
}
|
||||
else if (sStyleName == aSourceText)
|
||||
{
|
||||
if (!isContinueSpan)
|
||||
if (eContinuation == Continuation::None)
|
||||
{
|
||||
nPDFType = sal_uInt16(vcl::pdf::StructElement::Code);
|
||||
aPDFType = aCodeString;
|
||||
bDeferSpan = bCanDefer;
|
||||
}
|
||||
}
|
||||
else if (!isContinueSpan)
|
||||
else if (eContinuation == Continuation::None)
|
||||
{
|
||||
const LanguageType nCurrentLanguage = rInf.GetFont()->GetLanguage();
|
||||
const SwFontScript nFont = rInf.GetFont()->GetActual();
|
||||
@@ -2274,7 +2314,7 @@ void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
|
||||
// for FootnoteNum, is called twice: outer generates Lbl, inner Link
|
||||
case PortionType::FootnoteNum:
|
||||
assert(!isContinueSpan); // is at start
|
||||
assert(eContinuation == Continuation::None); // is at start
|
||||
if (mpPorInfo->m_Mode == 0)
|
||||
{ // tdf#152218 link both directions
|
||||
nPDFType = sal_uInt16(vcl::pdf::StructElement::Link);
|
||||
@@ -2285,7 +2325,7 @@ void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
case PortionType::Number:
|
||||
case PortionType::Bullet:
|
||||
case PortionType::GrfNum:
|
||||
assert(!isContinueSpan); // is at start
|
||||
assert(eContinuation == Continuation::None); // is at start
|
||||
if (mpPorInfo->m_Mode == 1)
|
||||
{ // only works for multiple lines via wrapper from PaintSwFrame
|
||||
nPDFType = sal_uInt16(vcl::pdf::StructElement::LILabel);
|
||||
@@ -2307,7 +2347,7 @@ void SwTaggedPDFHelper::BeginInlineStructureElements()
|
||||
BeginTag( static_cast<vcl::pdf::StructElement>(nPDFType), aPDFType );
|
||||
if (bDeferSpan)
|
||||
{
|
||||
CreateCurrentSpan(rInf, sStyleName.toString());
|
||||
CreateCurrentSpan(rInf, aStyleName);
|
||||
bDeferTag = true;
|
||||
}
|
||||
if (bDeferTag)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" 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 with a span and an as-character frame</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:style style:name="T1" style:family="text">
|
||||
<style:text-properties fo:language="de" fo:country="DE"/>
|
||||
</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:p text:style-name="Standard">plain <text:a xlink:type="simple" xlink:href="https://www.example.com/" office:name="Fly link" text:style-name="Internet_20_link">before <text:span text:style-name="T1">deutsch <draw:frame text:anchor-type="as-char" svg:width="2cm" svg:height="1cm" draw:z-index="0"><draw:text-box><text:p text:style-name="Standard">inner</text:p></draw:text-box></draw:frame> weiter</text:span> after</text:a></text:p>
|
||||
</office:text>
|
||||
</office:body>
|
||||
</office:document>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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 with a wrapped language change</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:style style:name="T1" style:family="text">
|
||||
<style:text-properties fo:language="de" fo:country="DE"/>
|
||||
</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:p text:style-name="Standard"><text:a xlink:type="simple" xlink:href="https://www.example.com/" office:name="Span link" text:style-name="Internet_20_link">This hyperlink starts in the document language and then <text:span text:style-name="T1">continues in German for long enough that the run itself has to break onto a second line, and the link ends with it</text:span></text:a></text:p>
|
||||
</office:text>
|
||||
</office:body>
|
||||
</office:document>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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 with a language change</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:style style:name="T1" style:family="text">
|
||||
<style:text-properties fo:language="de" fo:country="DE"/>
|
||||
</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:p text:style-name="Standard"><text:a xlink:type="simple" xlink:href="https://www.example.com/" office:name="Span link" text:style-name="Internet_20_link">before <text:span text:style-name="T1">deutsch</text:span> after</text:a></text:p>
|
||||
</office:text>
|
||||
</office:body>
|
||||
</office:document>
|
||||
@@ -3183,6 +3183,139 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testLinkWithRuby)
|
||||
CPPUNIT_ASSERT(std::find(aRubyKids.begin(), aRubyKids.end(), "RT"_ostr) != aRubyKids.end());
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(PdfExportTest, testLinkWithSpan)
|
||||
{
|
||||
uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) };
|
||||
|
||||
vcl::filter::PDFDocument aDocument;
|
||||
loadFromFile(u"LinkWithSpan.fodt");
|
||||
save(TestFilter::PDF_WRITER,
|
||||
{ comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) });
|
||||
|
||||
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
|
||||
CPPUNIT_ASSERT(aDocument.Read(aStream));
|
||||
|
||||
vcl::filter::PDFObjectElement* pLinkSE(nullptr);
|
||||
for (const auto& rDocElement : aDocument.GetElements())
|
||||
{
|
||||
auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
|
||||
if (!pObject)
|
||||
continue;
|
||||
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
|
||||
auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
|
||||
if (pType && pType->GetValue() == "StructElem" && pS && pS->GetValue() == "Link")
|
||||
{
|
||||
CPPUNIT_ASSERT_MESSAGE("a language change must not split the link", !pLinkSE);
|
||||
pLinkSE = pObject;
|
||||
}
|
||||
}
|
||||
CPPUNIT_ASSERT(pLinkSE);
|
||||
|
||||
// the link keeps the language of its first portion, so the German must not be on it
|
||||
auto pLinkLang
|
||||
= dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pLinkSE->Lookup("Lang"_ostr));
|
||||
if (pLinkLang)
|
||||
{
|
||||
CPPUNIT_ASSERT(pLinkLang->GetValue() != "de-DE"_ostr);
|
||||
}
|
||||
|
||||
// the run that differs is a Span inside it, not a second Link
|
||||
auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pLinkSE->Lookup("K"_ostr));
|
||||
CPPUNIT_ASSERT(pKids);
|
||||
size_t nMCID(0);
|
||||
size_t nMCIDBeforeSpan(0);
|
||||
vcl::filter::PDFObjectElement* pSpanSE(nullptr);
|
||||
for (const auto pElement : pKids->GetElements())
|
||||
{
|
||||
if (dynamic_cast<vcl::filter::PDFNumberElement*>(pElement))
|
||||
{
|
||||
++nMCID;
|
||||
continue;
|
||||
}
|
||||
auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pElement);
|
||||
if (!pRef)
|
||||
continue; // the OBJR for the annotation
|
||||
auto pKid = pRef->LookupObject();
|
||||
CPPUNIT_ASSERT(pKid);
|
||||
auto pKidS = dynamic_cast<vcl::filter::PDFNameElement*>(pKid->Lookup("S"_ostr));
|
||||
CPPUNIT_ASSERT(pKidS);
|
||||
CPPUNIT_ASSERT_EQUAL("Span"_ostr, pKidS->GetValue());
|
||||
CPPUNIT_ASSERT(!pSpanSE);
|
||||
pSpanSE = pKid;
|
||||
nMCIDBeforeSpan = nMCID;
|
||||
}
|
||||
CPPUNIT_ASSERT(pSpanSE);
|
||||
CPPUNIT_ASSERT_EQUAL(size_t(2), nMCID);
|
||||
// it reads between the text before it and the text after it
|
||||
CPPUNIT_ASSERT_EQUAL(size_t(1), nMCIDBeforeSpan);
|
||||
|
||||
auto pSpanLang
|
||||
= dynamic_cast<vcl::filter::PDFLiteralStringElement*>(pSpanSE->Lookup("Lang"_ostr));
|
||||
CPPUNIT_ASSERT(pSpanLang);
|
||||
CPPUNIT_ASSERT_EQUAL("de-DE"_ostr, pSpanLang->GetValue());
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(PdfExportTest, testLinkSpanWithFly)
|
||||
{
|
||||
uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) };
|
||||
|
||||
vcl::filter::PDFDocument aDocument;
|
||||
loadFromFile(u"LinkSpanWithFly.fodt");
|
||||
save(TestFilter::PDF_WRITER,
|
||||
{ comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) });
|
||||
|
||||
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
|
||||
CPPUNIT_ASSERT(aDocument.Read(aStream));
|
||||
|
||||
// the frame's own paragraph is current when the link ends, so neither the span nor
|
||||
// the link closes there - one link, one span, and the frame nested in the span
|
||||
vcl::filter::PDFObjectElement* pLinkSE(nullptr);
|
||||
vcl::filter::PDFObjectElement* pSpanSE(nullptr);
|
||||
for (const auto& rDocElement : aDocument.GetElements())
|
||||
{
|
||||
auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
|
||||
if (!pObject)
|
||||
continue;
|
||||
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
|
||||
auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
|
||||
if (!pType || pType->GetValue() != "StructElem" || !pS)
|
||||
continue;
|
||||
if (pS->GetValue() == "Link")
|
||||
{
|
||||
CPPUNIT_ASSERT_MESSAGE("a frame must not split the link", !pLinkSE);
|
||||
pLinkSE = pObject;
|
||||
}
|
||||
else if (pS->GetValue() == "Span")
|
||||
{
|
||||
CPPUNIT_ASSERT_MESSAGE("a frame must not split the span", !pSpanSE);
|
||||
pSpanSE = pObject;
|
||||
}
|
||||
}
|
||||
CPPUNIT_ASSERT(pLinkSE);
|
||||
CPPUNIT_ASSERT(pSpanSE);
|
||||
|
||||
// the span is a kid of the link, and the frame a kid of the span
|
||||
auto pSpanParent = dynamic_cast<vcl::filter::PDFReferenceElement*>(pSpanSE->Lookup("P"_ostr));
|
||||
CPPUNIT_ASSERT(pSpanParent);
|
||||
CPPUNIT_ASSERT_EQUAL(pLinkSE, pSpanParent->LookupObject());
|
||||
auto pSpanKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pSpanSE->Lookup("K"_ostr));
|
||||
CPPUNIT_ASSERT(pSpanKids);
|
||||
size_t nDiv(0);
|
||||
for (const auto pElement : pSpanKids->GetElements())
|
||||
{
|
||||
auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pElement);
|
||||
if (!pRef)
|
||||
continue;
|
||||
auto pKid = pRef->LookupObject();
|
||||
CPPUNIT_ASSERT(pKid);
|
||||
auto pKidS = dynamic_cast<vcl::filter::PDFNameElement*>(pKid->Lookup("S"_ostr));
|
||||
CPPUNIT_ASSERT(pKidS);
|
||||
CPPUNIT_ASSERT_EQUAL("Div"_ostr, pKidS->GetValue());
|
||||
++nDiv;
|
||||
}
|
||||
CPPUNIT_ASSERT_EQUAL(size_t(1), nDiv);
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(PdfExportTest, testSpanWithFly)
|
||||
{
|
||||
uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) };
|
||||
@@ -3238,6 +3371,95 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest, testSpanWithFly)
|
||||
CPPUNIT_ASSERT_GREATER(size_t(1), nMCID);
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(PdfExportTest, testLinkSpanWraps)
|
||||
{
|
||||
uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) };
|
||||
|
||||
vcl::filter::PDFDocument aDocument;
|
||||
loadFromFile(u"LinkSpanWraps.fodt");
|
||||
save(TestFilter::PDF_WRITER,
|
||||
{ comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) });
|
||||
|
||||
SvFileStream aStream(maTempFile.GetURL(), StreamMode::READ);
|
||||
CPPUNIT_ASSERT(aDocument.Read(aStream));
|
||||
|
||||
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
|
||||
CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size());
|
||||
auto pAnnots = dynamic_cast<vcl::filter::PDFArrayElement*>(aPages[0]->Lookup("Annots"_ostr));
|
||||
CPPUNIT_ASSERT(pAnnots);
|
||||
// how many lines the link takes does not matter, only that the styled run breaks
|
||||
CPPUNIT_ASSERT_GREATER(size_t(1), pAnnots->GetElements().size());
|
||||
|
||||
vcl::filter::PDFObjectElement* pLinkSE(nullptr);
|
||||
for (const auto& rDocElement : aDocument.GetElements())
|
||||
{
|
||||
auto pObject = dynamic_cast<vcl::filter::PDFObjectElement*>(rDocElement.get());
|
||||
if (!pObject)
|
||||
continue;
|
||||
auto pType = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("Type"_ostr));
|
||||
auto pS = dynamic_cast<vcl::filter::PDFNameElement*>(pObject->Lookup("S"_ostr));
|
||||
if (pType && pType->GetValue() == "StructElem" && pS && pS->GetValue() == "Link")
|
||||
{
|
||||
CPPUNIT_ASSERT_MESSAGE("a wrapped span must not split the link", !pLinkSE);
|
||||
pLinkSE = pObject;
|
||||
}
|
||||
}
|
||||
CPPUNIT_ASSERT(pLinkSE);
|
||||
|
||||
// both lines' annotations reach the Link, although the span is what is open
|
||||
// across the break
|
||||
auto pKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pLinkSE->Lookup("K"_ostr));
|
||||
CPPUNIT_ASSERT(pKids);
|
||||
std::vector<vcl::filter::PDFObjectElement*> aKidAnnots;
|
||||
for (const auto pElement : pKids->GetElements())
|
||||
{
|
||||
auto pObjR = dynamic_cast<vcl::filter::PDFDictionaryElement*>(pElement);
|
||||
if (!pObjR)
|
||||
continue;
|
||||
auto pOType = dynamic_cast<vcl::filter::PDFNameElement*>(pObjR->LookupElement("Type"_ostr));
|
||||
CPPUNIT_ASSERT(pOType);
|
||||
CPPUNIT_ASSERT_EQUAL("OBJR"_ostr, pOType->GetValue());
|
||||
auto pRef
|
||||
= dynamic_cast<vcl::filter::PDFReferenceElement*>(pObjR->LookupElement("Obj"_ostr));
|
||||
CPPUNIT_ASSERT(pRef);
|
||||
aKidAnnots.push_back(pRef->LookupObject());
|
||||
}
|
||||
CPPUNIT_ASSERT_EQUAL(pAnnots->GetElements().size(), aKidAnnots.size());
|
||||
CPPUNIT_ASSERT_EQUAL(aKidAnnots.size(), std::set(aKidAnnots.begin(), aKidAnnots.end()).size());
|
||||
|
||||
// the span is what straddles the break, so it holds a run of text on each line
|
||||
vcl::filter::PDFObjectElement* pSpanSE(nullptr);
|
||||
for (const auto pElement : pKids->GetElements())
|
||||
{
|
||||
auto pRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pElement);
|
||||
if (!pRef)
|
||||
continue;
|
||||
auto pKid = pRef->LookupObject();
|
||||
CPPUNIT_ASSERT(pKid);
|
||||
auto pKidS = dynamic_cast<vcl::filter::PDFNameElement*>(pKid->Lookup("S"_ostr));
|
||||
CPPUNIT_ASSERT(pKidS);
|
||||
CPPUNIT_ASSERT_EQUAL("Span"_ostr, pKidS->GetValue());
|
||||
CPPUNIT_ASSERT(!pSpanSE);
|
||||
pSpanSE = pKid;
|
||||
}
|
||||
CPPUNIT_ASSERT(pSpanSE);
|
||||
auto pSpanKids = dynamic_cast<vcl::filter::PDFArrayElement*>(pSpanSE->Lookup("K"_ostr));
|
||||
CPPUNIT_ASSERT(pSpanKids);
|
||||
CPPUNIT_ASSERT_GREATER(size_t(1), pSpanKids->GetElements().size());
|
||||
|
||||
// and every annotation carries a StructParent and is one of the Link's OBJR kids
|
||||
for (const auto pElement : pAnnots->GetElements())
|
||||
{
|
||||
auto pAnnotRef = dynamic_cast<vcl::filter::PDFReferenceElement*>(pElement);
|
||||
CPPUNIT_ASSERT(pAnnotRef);
|
||||
auto pAnnot = pAnnotRef->LookupObject();
|
||||
CPPUNIT_ASSERT(pAnnot);
|
||||
CPPUNIT_ASSERT_MESSAGE("every annotation needs a StructParent",
|
||||
pAnnot->Lookup("StructParent"_ostr));
|
||||
CPPUNIT_ASSERT(std::find(aKidAnnots.begin(), aKidAnnots.end(), pAnnot) != aKidAnnots.end());
|
||||
}
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(PdfExportTest, testRubyWithSpan)
|
||||
{
|
||||
uno::Sequence aFilterData{ comphelper::makePropertyValue(u"PDFUACompliance"_ustr, true) };
|
||||
|
||||
Reference in New Issue
Block a user