From c3bcf7d5f8d7d6ac3d2bdcda369f32cafa922ca2 Mon Sep 17 00:00:00 2001 From: Mike Kaganski Date: Thu, 17 Sep 2026 01:04:43 +0500 Subject: [PATCH] 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: <> 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 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 Change-Id: I09a68eff3d15981a7e112f79409b3448bceb4bc0 Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/12987 Tested-by: Jenkins CPCI Reviewed-by: Miklos Vajna (cherry picked from commit 5b84c1873533bcdd98d917e8b6fb6d149a238047) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211348 Reviewed-by: Xisco Fauli Tested-by: Xisco Fauli --- sw/qa/core/text/EnhancedPDFExportHelper.cxx | 36 +++++++++++++ sw/qa/core/text/data/formula-alt.fodt | 23 ++++++++ .../core/text/EnhancedPDFExportHelper.cxx | 52 ++++++++++++++++++- 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 sw/qa/core/text/data/formula-alt.fodt diff --git a/sw/qa/core/text/EnhancedPDFExportHelper.cxx b/sw/qa/core/text/EnhancedPDFExportHelper.cxx index c5effb0056df..e57d18436dd4 100644 --- a/sw/qa/core/text/EnhancedPDFExportHelper.cxx +++ b/sw/qa/core/text/EnhancedPDFExportHelper.cxx @@ -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(rDocElement.get()); + if (!pObject) + continue; + + auto pType = dynamic_cast(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(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: */ diff --git a/sw/qa/core/text/data/formula-alt.fodt b/sw/qa/core/text/data/formula-alt.fodt new file mode 100644 index 000000000000..d040e95b3c55 --- /dev/null +++ b/sw/qa/core/text/data/formula-alt.fodt @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + The mass energy equivalence is E=mc2E=mc^2 in a paragraph. + A described one is a2a over 2Half of a here. + + + diff --git a/sw/source/core/text/EnhancedPDFExportHelper.cxx b/sw/source/core/text/EnhancedPDFExportHelper.cxx index 288476cf1b10..492dc4242bb1 100644 --- a/sw/source/core/text/EnhancedPDFExportHelper.cxx +++ b/sw/source/core/text/EnhancedPDFExportHelper.cxx @@ -22,13 +22,16 @@ #include #include +#include #include #include #include #include #include +#include #include +#include #include #include #include @@ -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(rFly.Lower())->GetNode(); + SwOLENode* pOLENd = pNode ? const_cast(pNode->GetOLENode()) : nullptr; + if (!pOLENd) + return OUString(); + + const uno::Reference 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()); + 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(pFrame)->GetFormat()); + SwFlyFrame const& rFlyFrame(*static_cast(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);