tdf#168462 svx: mark the slide background as an artifact

A slide with a background, its own or its master's, exported to
tagged PDF with that background as untagged content. ISO 14289-1 7.1
leaves no third state, and veraPDF says so once per background:

 Content is neither marked as Artifact nor tagged as real content

Page decoration is wrapped as an artifact in
ViewObjectContact::getPrimitive2DSequence, which the background never
reached: the master page descriptor builds its own hierarchy and
emitted the fill straight into the visitor. It now takes the same
wrapper as the page decoration beside it - the machinery of commit
81ef846485 ("(related: tdf#135192) svx:
PDF/UA export: tag background as Artifact", 2022-12-01), whose author
hoped there that "anything that's not SdrObject can be tagged as
Artifact".

The wrapper has to lie to be heard: VclMetafileProcessor2D drops a
tag that is a NonStructElement, and drops a background tag that is
not an image, so page decoration goes out as a Division claiming to
be an image. That second refusal is also why a shape on a master
slide, unless it is a graphic, is untagged to this day. LATER: let the
processor emit an artifact for a plain background and stop dropping
one, and both go.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I9431c460cade3d432cb7abf76930e23f8106a7f1
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12311
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
(cherry picked from commit b11283cd97e1170688a75f4229fd43bd8298e276)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211166
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Mike Kaganski
2026-09-18 22:42:53 +02:00
committed by Xisco Fauli
parent becf60124d
commit f1e4a0f0ab
3 changed files with 117 additions and 1 deletions
@@ -89,7 +89,11 @@ namespace sdr::contact
if (!bHideBackground && !GetObjectContact().isDrawModeGray() && !GetObjectContact().isDrawModeHighContrast())
{
// if visible, create the default background primitive sequence
static_cast< ViewContactOfMasterPageDescriptor& >(GetViewContact()).getViewIndependentPrimitive2DContainer(rVisitor);
drawinglayer::primitive2d::Primitive2DContainer aBackground;
static_cast<ViewContactOfMasterPageDescriptor&>(GetViewContact())
.getViewIndependentPrimitive2DContainer(aBackground);
createStructureTag(aBackground); // decoration, not content
rVisitor.visit(std::move(aBackground));
}
}
@@ -0,0 +1,38 @@
<?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:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation: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" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.presentation">
<office:font-face-decls>
<style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/>
</office:font-face-decls>
<office:styles>
<style:default-style style:family="paragraph">
<style:text-properties style:font-name="Liberation Sans" fo:font-size="18pt" fo:language="en" fo:country="US"/>
</style:default-style>
</office:styles>
<office:automatic-styles>
<style:page-layout style:name="pm1">
<style:page-layout-properties fo:page-width="28cm" fo:page-height="15.75cm" style:print-orientation="landscape" fo:margin-top="0cm" fo:margin-bottom="0cm" fo:margin-left="0cm" fo:margin-right="0cm"/>
</style:page-layout>
<style:style style:name="dp1" style:family="drawing-page">
<style:drawing-page-properties draw:fill="solid" draw:fill-color="#ffcc00"/>
</style:style>
<style:style style:name="dp2" style:family="drawing-page">
<style:drawing-page-properties draw:fill="none" presentation:background-visible="true"/>
</style:style>
<style:style style:name="gr1" style:family="graphic">
<style:graphic-properties draw:fill="none" draw:stroke="none"/>
<style:text-properties style:font-name="Liberation Sans" fo:font-size="18pt"/>
</style:style>
</office:automatic-styles>
<office:master-styles>
<style:master-page style:name="Default" style:page-layout-name="pm1" draw:style-name="dp1"/>
</office:master-styles>
<office:body>
<office:presentation>
<draw:page draw:name="page1" draw:style-name="dp2" draw:master-page-name="Default">
<draw:frame draw:style-name="gr1" svg:width="10cm" svg:height="2cm" svg:x="2cm" svg:y="2cm">
<draw:text-box><text:p>Slide text</text:p></draw:text-box>
</draw:frame>
</draw:page>
</office:presentation>
</office:body>
</office:document>
+74
View File
@@ -3812,6 +3812,80 @@ CPPUNIT_TEST_FIXTURE(PdfExportTest2, cool16122BadPantoneElement)
CPPUNIT_ASSERT(bFound);
}
CPPUNIT_TEST_FIXTURE(PdfExportTest2, testTdf168462)
{
uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true) };
loadFromFile(u"master-background-artifact.fodp");
save(TestFilter::PDF_WRITER,
{ comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) });
vcl::filter::PDFDocument aDocument;
CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ)));
std::vector<vcl::filter::PDFObjectElement*> aPages = aDocument.GetPages();
CPPUNIT_ASSERT_EQUAL(size_t(1), aPages.size());
vcl::filter::PDFObjectElement* pContents = aPages[0]->LookupObject("Contents"_ostr);
CPPUNIT_ASSERT(pContents);
vcl::filter::PDFStreamElement* pStream = pContents->GetStream();
CPPUNIT_ASSERT(pStream);
SvMemoryStream& rObjectStream = pStream->GetMemory();
SvMemoryStream aUncompressed;
ZCodec aZCodec;
aZCodec.BeginCompression();
rObjectStream.Seek(0);
aZCodec.Decompress(rObjectStream, aUncompressed);
CPPUNIT_ASSERT(aZCodec.EndCompression());
// an operator shares its line with its operands, and sometimes with another operator
auto isPaintingOperator = [](const std::string_view line) {
for (size_t nPos = 0; nPos != std::string_view::npos;)
{
const size_t nStart = line.find_first_not_of(' ', nPos);
if (nStart == std::string_view::npos)
break;
nPos = line.find(' ', nStart);
const std::string_view aToken(
line.substr(nStart, nPos == std::string_view::npos ? nPos : nPos - nStart));
for (const auto op :
{ "f", "f*", "F", "S", "s", "B", "B*", "b", "b*", "Do", "TJ", "Tj" })
{
if (aToken == op)
return true;
}
}
return false;
};
auto pStart = static_cast<const char*>(aUncompressed.GetData());
const char* const pEnd = pStart + aUncompressed.GetSize();
int nOpenMarks(0);
int nArtifacts(0);
OStringBuffer aUntagged;
// ISO 14289-1 7.1: content is either tagged or marked as an artifact
while (pStart != pEnd)
{
const auto pLineEnd = std::find(pStart, pEnd, '\n');
const std::string_view line(pStart, pLineEnd - pStart);
pStart = pLineEnd == pEnd ? pEnd : pLineEnd + 1;
if (line == "EMC")
--nOpenMarks;
else if (o3tl::ends_with(line, "BMC") || o3tl::ends_with(line, "BDC"))
{
++nOpenMarks;
if (o3tl::starts_with(line, "/Artifact"))
++nArtifacts;
}
else if (nOpenMarks == 0 && isPaintingOperator(line))
aUntagged.append(OString::Concat(line) + " ");
}
// without the fix the background was painted outside every marked-content section
CPPUNIT_ASSERT_MESSAGE(aUntagged.toString().getStr(), aUntagged.isEmpty());
// the page decoration, and the background the slide takes from its master page
CPPUNIT_ASSERT_EQUAL(2, nArtifacts);
CPPUNIT_ASSERT_EQUAL(0, nOpenMarks);
}
} // end anonymous namespace
CPPUNIT_PLUGIN_IMPLEMENT();