tdf#168720 sw: an undescribed formula's /Alt is its source

Insert - OLE Object - Formula Object, write E=mc^2, save and reopen,
then export with File - Export As - Export as PDF and Universal
Accessibility (PDF/UA) on. PAC 2026 fails it under Alternative
Descriptions, the element saying nothing about what it holds:

 <</Type/StructElem/S/Formula /P 5 0 R /Pg 1 0 R ... /K[1 ]>>

Writer takes a frame's Alt from the title and description a user
typed; a formula has neither unless asked. A tagged export falls back
to the formula's own source now, so the element says E=mc^2 and PAC
passes that checkpoint:

 /Alt <FEFF0045003D006D0063005E0032>

A title or description still wins. The report asks for an option; a
formula with neither said nothing at all, so the source goes in with
none to switch it off. Reading the source needs the object running,
so the export runs each undescribed formula and puts it back. PAC's
separate Structure tree warning still stands.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I09a68eff3d15981a7e112f79409b3448bceb4bc0
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12987
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 5b84c1873533bcdd98d917e8b6fb6d149a238047)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211348
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Mike Kaganski
2026-09-23 12:21:47 +02:00
committed by Xisco Fauli
parent 722410514b
commit c3bcf7d5f8
3 changed files with 109 additions and 2 deletions
@@ -204,6 +204,42 @@ CPPUNIT_TEST_FIXTURE(Test, testTOCItemRef)
// one item per heading, each naming its own
CPPUNIT_ASSERT_EQUAL("H1 H2 "_ostr, aTargets.makeStringAndClear());
}
CPPUNIT_TEST_FIXTURE(Test, testFormulaAltFromSource)
{
createSwDoc("formula-alt.fodt");
// a formula reaches the export loaded, not running, in every document but a freshly typed one
saveAndReload(TestFilter::ODT);
uno::Sequence aFilterData{ comphelper::makePropertyValue(u"UseTaggedPDF"_ustr, true) };
save(TestFilter::PDF_WRITER,
{ comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData) });
vcl::filter::PDFDocument aDocument;
maTempFile.CloseStream();
CPPUNIT_ASSERT(aDocument.Read(*maTempFile.GetStream(StreamMode::READ)));
OUStringBuffer aAlts;
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("S"_ostr));
if (!pType || pType->GetValue() != "Formula")
continue;
// Without the fix a formula nobody had described carried no Alt, which the PDF/UA
// checkers count against the file
auto pAlt = dynamic_cast<vcl::filter::PDFHexStringElement*>(pObject->Lookup("Alt"_ostr));
CPPUNIT_ASSERT(pAlt);
aAlts.append(vcl::filter::PDFDocument::DecodeHexStringUTF16BE(*pAlt) + "|");
}
// the source where nobody described the formula, and the title where somebody did
CPPUNIT_ASSERT_EQUAL(u"E=mc^2|Half of a|"_ustr, aAlts.makeStringAndClear());
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
+23
View File
@@ -0,0 +1,23 @@
<?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: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:math="http://www.w3.org/1998/Math/MathML" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
<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:default-style>
<style:style style:name="Standard" style:family="paragraph" style:class="text"/>
</office:styles>
<office:automatic-styles>
<style:style style:name="fr1" style:family="graphic">
<style:graphic-properties style:vertical-pos="middle" style:vertical-rel="text" fo:padding="0in" fo:border="none"/>
</style:style>
</office:automatic-styles>
<office:body>
<office:text>
<text:p text:style-name="Standard">The mass energy equivalence is <draw:frame draw:style-name="fr1" draw:name="Object1" text:anchor-type="as-char" svg:width="0.9in" svg:height="0.3in" draw:z-index="0"><draw:object><math:math><math:semantics><math:mrow><math:mi math:mathvariant="normal">E</math:mi><math:mo math:stretchy="false">=</math:mo><math:msup><math:mi math:mathvariant="italic">mc</math:mi><math:mn>2</math:mn></math:msup></math:mrow><math:annotation math:encoding="StarMath 5.0">E=mc^2</math:annotation></math:semantics></math:math></draw:object></draw:frame> in a paragraph.</text:p>
<text:p text:style-name="Standard">A described one is <draw:frame draw:style-name="fr1" draw:name="Object2" text:anchor-type="as-char" svg:width="0.6in" svg:height="0.4in" draw:z-index="1"><draw:object><math:math><math:semantics><math:mrow><math:mfrac><math:mi math:mathvariant="italic">a</math:mi><math:mn>2</math:mn></math:mfrac></math:mrow><math:annotation math:encoding="StarMath 5.0">a over 2</math:annotation></math:semantics></math:math></draw:object><svg:title>Half of a</svg:title></draw:frame> here.</text:p>
</office:text>
</office:body>
</office:document>
@@ -22,13 +22,16 @@
#include <algorithm>
#include <unordered_map>
#include <com/sun/star/embed/EmbedStates.hpp>
#include <com/sun/star/embed/XEmbeddedObject.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <hintids.hxx>
#include <comphelper/scopeguard.hxx>
#include <sot/exchange.hxx>
#include <svtools/embedhlp.hxx>
#include <vcl/outdev.hxx>
#include <vcl/pdfextoutdevdata.hxx>
#include <vcl/pdf/PDFNote.hxx>
@@ -453,6 +456,45 @@ const SwTextNode* lcl_JumpedToNode(const SwEditShell& rSh, const SwPosition& rBe
return rPoint == rBeforeJump ? nullptr : rPoint.GetNode().GetTextNode();
}
// the StarMath source of the formula a frame holds, empty for anything else
OUString lcl_GetFormulaSource(const SwFlyFrame& rFly)
{
if (!rFly.Lower() || !rFly.Lower()->IsNoTextFrame())
return OUString();
const SwContentNode* pNode = static_cast<const SwNoTextFrame*>(rFly.Lower())->GetNode();
SwOLENode* pOLENd = pNode ? const_cast<SwOLENode*>(pNode->GetOLENode()) : nullptr;
if (!pOLENd)
return OUString();
const uno::Reference<embed::XEmbeddedObject> xObj(pOLENd->GetOLEObj().GetOleRef());
if (!xObj.is() || !SotExchange::IsMath(SvGlobalName(xObj->getClassID())))
return OUString();
OUString aSource;
try
{
// a loaded object has no component to read the source from
const bool bWasLoaded(xObj->getCurrentState() == embed::EmbedStates::LOADED);
if (!svt::EmbeddedObjectRef::TryRunningState(xObj))
return OUString();
// reading it ran the object, and an export leaves the document as it found it
const comphelper::ScopeGuard aRestore([&xObj, bWasLoaded] {
if (bWasLoaded)
xObj->changeState(embed::EmbedStates::LOADED);
});
const auto xProps(xObj->getComponent().query<beans::XPropertySet>());
if (xProps.is())
xProps->getPropertyValue(u"Formula"_ustr) >>= aSource;
}
catch (const uno::Exception&)
{
}
return aSource;
}
// the node a table of contents entry links to, which the mark in its URL names
const SwTextNode* lcl_GetLinkedNode(const SwDoc& rDoc, const SwTextNode& rEntry)
{
@@ -1113,11 +1155,17 @@ void SwTaggedPDFHelper::SetAttributes(vcl::pdf::StructElement eType)
// text here again.
if (bAltText)
{
SwFlyFrameFormat const& rFly(*static_cast<SwFlyFrame const*>(pFrame)->GetFormat());
SwFlyFrame const& rFlyFrame(*static_cast<SwFlyFrame const*>(pFrame));
SwFlyFrameFormat const& rFly(*rFlyFrame.GetFormat());
OUString const sep(
(rFly.GetObjTitle().isEmpty() || rFly.GetObjDescription().isEmpty())
? OUString() : u" - "_ustr);
OUString const altText(rFly.GetObjTitle() + sep + rFly.GetObjDescription());
OUString altText(rFly.GetObjTitle() + sep + rFly.GetObjDescription());
// a formula nobody described names its own source
if (altText.isEmpty() && eType == vcl::pdf::StructElement::Formula)
{
altText = lcl_GetFormulaSource(rFlyFrame);
}
if (!altText.isEmpty())
{
mpPDFExtOutDevData->SetAlternateText(altText);