sw/qa/extras/htmlexport/data/embedded_formula.fodt | 30 +++ sw/qa/extras/htmlexport/htmlexport.cxx | 63 ++++--- sw/source/filter/html/htmlplug.cxx | 184 +++++++++++++-------- sw/source/filter/html/wrthtml.cxx | 6 sw/source/filter/html/wrthtml.hxx | 3 5 files changed, 201 insertions(+), 85 deletions(-)
New commits: commit 2ecd5da533f8fc4229bb8a38167eb147c1213fa8 Author: Mike Kaganski <mike.kagan...@collabora.com> AuthorDate: Mon Oct 30 17:14:47 2023 +0300 Commit: Mike Kaganski <mike.kagan...@collabora.com> CommitDate: Tue Oct 31 05:54:03 2023 +0100 ReqIF: introduce ExportFormulasAsPDF HTML export filter option Change-Id: Id400bd5571d0a192d854620abe83d862e0512434 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/158663 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> diff --git a/sw/qa/extras/htmlexport/data/embedded_formula.fodt b/sw/qa/extras/htmlexport/data/embedded_formula.fodt new file mode 100644 index 000000000000..46e5391223c0 --- /dev/null +++ b/sw/qa/extras/htmlexport/data/embedded_formula.fodt @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office: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:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:body> + <office:text> + <text:p>Formula:</text:p> + <text:p><draw:frame draw:name="Formula1" text:anchor-type="as-char" svg:width="1.549cm" svg:height="0.531cm"><draw:object> + <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> + <semantics> + <mrow> + <msup> + <mi mathvariant="normal">e</mi> + <mrow> + <mi>i</mi> + <mi>π</mi> + </mrow> + </msup> + <mo stretchy="false">+</mo> + <mn>1</mn> + <mo stretchy="false">=</mo> + <mn>0</mn> + </mrow> + <annotation encoding="StarMath 5.0">{ func e ^ { i %pi } + 1 } = 0</annotation> + </semantics> + </math> + </draw:object> + </draw:frame></text:p> + </office:text> + </office:body> +</office:document> \ No newline at end of file diff --git a/sw/qa/extras/htmlexport/htmlexport.cxx b/sw/qa/extras/htmlexport/htmlexport.cxx index a106dd53bf14..b4d3bfaaf10a 100644 --- a/sw/qa/extras/htmlexport/htmlexport.cxx +++ b/sw/qa/extras/htmlexport/htmlexport.cxx @@ -13,6 +13,7 @@ #include <string_view> #include <com/sun/star/document/XEmbeddedObjectSupplier2.hpp> +#include <com/sun/star/document/XTypeDetection.hpp> #include <com/sun/star/embed/ElementModes.hpp> #include <com/sun/star/io/XActiveDataStreamer.hpp> #include <com/sun/star/io/XSeekable.hpp> @@ -215,9 +216,10 @@ public: { } + OUString GetObjectPath(const OUString& ext); /// Get the .ole path, assuming maTempFile is an XHTML export result. - OUString GetOlePath(); - OUString GetPngPath(); + OUString GetOlePath() { return GetObjectPath(u".ole"_ustr); } + OUString GetPngPath() { return GetObjectPath(u".png"_ustr); } /// Parse the ole1 data out of an RTF fragment URL. void ParseOle1FromRtfUrl(const OUString& rRtfUrl, SvMemoryStream& rOle1); /// Export using the C++ HTML export filter, with xhtmlns=reqif-xhtml. @@ -228,29 +230,16 @@ public: void ExportToHTML(); }; -OUString SwHtmlDomExportTest::GetOlePath() +OUString SwHtmlDomExportTest::GetObjectPath(const OUString& ext) { + assert(ext.startsWith(".")); xmlDocUniquePtr pDoc = WrapReqifFromTempFile(); OUString aOlePath = getXPath( pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p/reqif-xhtml:object", "data"); - OUString aOleSuffix(".ole"); - CPPUNIT_ASSERT(aOlePath.endsWith(aOleSuffix)); + CPPUNIT_ASSERT(aOlePath.endsWith(ext)); INetURLObject aUrl(maTempFile.GetURL()); - aUrl.setBase(aOlePath.subView(0, aOlePath.getLength() - aOleSuffix.getLength())); - aUrl.setExtension(u"ole"); - return aUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE); -} - -OUString SwHtmlDomExportTest::GetPngPath() -{ - xmlDocUniquePtr pDoc = WrapReqifFromTempFile(); - OUString aPngPath = getXPath( - pDoc, "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p/reqif-xhtml:object", "data"); - OUString aPngSuffix(".png"); - CPPUNIT_ASSERT(aPngPath.endsWith(aPngSuffix)); - INetURLObject aUrl(maTempFile.GetURL()); - aUrl.setBase(aPngPath.subView(0, aPngPath.getLength() - aPngSuffix.getLength())); - aUrl.setExtension(u"png"); + aUrl.setBase(aOlePath.subView(0, aOlePath.getLength() - ext.getLength())); + aUrl.setExtension(ext.subView(1)); return aUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE); } @@ -2823,6 +2812,40 @@ CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_PreserveSpaces) CPPUNIT_ASSERT_EQUAL(paraText, getParagraph(1)->getString()); } +CPPUNIT_TEST_FIXTURE(SwHtmlDomExportTest, testReqIF_ExportFormulasAsPDF) +{ + // Given a document with a formula: + createSwDoc("embedded_formula.fodt"); + + // When exporting to reqif with ExportFormulasAsPDF=true: + uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY_THROW); + uno::Sequence<beans::PropertyValue> aStoreProperties = { + comphelper::makePropertyValue(u"FilterName"_ustr, u"HTML (StarWriter)"_ustr), + comphelper::makePropertyValue(u"FilterOptions"_ustr, u"xhtmlns=reqif-xhtml"_ustr), + comphelper::makePropertyValue(u"ExportFormulasAsPDF"_ustr, true), + }; + xStorable->storeToURL(maTempFile.GetURL(), aStoreProperties); + + // Make sure that the formula is exported as PDF: + xmlDocUniquePtr pXmlDoc = WrapReqifFromTempFile(); + assertXPath(pXmlDoc, + "/reqif-xhtml:html/reqif-xhtml:div/reqif-xhtml:p[2]/reqif-xhtml:object"_ostr, + "type"_ostr, u"application/pdf"_ustr); + + css::uno::Sequence<css::beans::PropertyValue> descr{ + comphelper::makePropertyValue(u"URL"_ustr, GetObjectPath(u".pdf"_ustr)), + }; + + uno::Reference<lang::XMultiServiceFactory> xFactory( + comphelper::getProcessComponentContext()->getServiceManager(), uno::UNO_QUERY_THROW); + uno::Reference<document::XTypeDetection> xTypeDetection( + xFactory->createInstance(u"com.sun.star.document.TypeDetection"_ustr), + uno::UNO_QUERY_THROW); + + CPPUNIT_ASSERT_EQUAL(u"pdf_Portable_Document_Format"_ustr, + xTypeDetection->queryTypeByDescriptor(descr, true)); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/filter/html/htmlplug.cxx b/sw/source/filter/html/htmlplug.cxx index 86d5cc0ffe7b..8331751fe431 100644 --- a/sw/source/filter/html/htmlplug.cxx +++ b/sw/source/filter/html/htmlplug.cxx @@ -1477,6 +1477,119 @@ SwHTMLWriter& OutHTML_FrameFormatOLENode( SwHTMLWriter& rWrt, const SwFrameForma return rWrt; } +static void OutHTMLGraphic(SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, SwOLENode* pOLENd, + const Graphic& rGraphic, bool bObjectOpened, bool bInCntnr) +{ + OUString aGraphicURL; + OUString aMimeType; + if (!rWrt.mbEmbedImages) + { + const OUString* pTempFileName = rWrt.GetOrigFileName(); + if (pTempFileName) + aGraphicURL = *pTempFileName; + + OUString aFilterName(u"JPG"_ustr); + XOutFlags nFlags = XOutFlags::UseGifIfPossible | XOutFlags::UseNativeIfPossible; + + if (bObjectOpened) + { + aFilterName = u"PNG"_ustr; + nFlags = XOutFlags::NONE; + aMimeType = u"image/png"_ustr; + + if (rGraphic.GetType() == GraphicType::NONE) + { + // The OLE Object has no replacement image, write a stub. + aGraphicURL = lcl_CalculateFileName(rWrt.GetOrigFileName(), rGraphic, u"png"); + osl::File aFile(aGraphicURL); + aFile.open(osl_File_OpenFlag_Create); + aFile.close(); + } + } + + ErrCode nErr = XOutBitmap::WriteGraphic(rGraphic, aGraphicURL, aFilterName, nFlags); + if (nErr) // error, don't write anything + { + rWrt.m_nWarn = WARN_SWG_POOR_LOAD; + if (bObjectOpened) // Still at least close the tag. + rWrt.Strm().WriteOString( + Concat2View("</" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object ">")); + return; + } + aGraphicURL = URIHelper::SmartRel2Abs(INetURLObject(rWrt.GetBaseURL()), aGraphicURL, + URIHelper::GetMaybeFileHdl()); + } + HtmlFrmOpts nFlags = bInCntnr ? HtmlFrmOpts::GenImgAllMask : HtmlFrmOpts::GenImgMask; + if (bObjectOpened) + nFlags |= HtmlFrmOpts::Replacement; + HtmlWriter aHtml(rWrt.Strm(), rWrt.maNamespace); + OutHTML_ImageStart(aHtml, rWrt, rFrameFormat, aGraphicURL, rGraphic, pOLENd->GetTitle(), + pOLENd->GetTwipSize(), nFlags, "ole", nullptr, aMimeType); + OutHTML_ImageEnd(aHtml, rWrt); +} + +static void OutHTMLStartObject(SwHTMLWriter& rWrt, const OUString& rFileName, const OUString& rFileType) +{ + OUString aFileName = URIHelper::simpleNormalizedMakeRelative(rWrt.GetBaseURL(), rFileName); + + if (rWrt.IsLFPossible()) + rWrt.OutNewLine(); + rWrt.Strm().WriteOString( + Concat2View("<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object)); + rWrt.Strm().WriteOString(Concat2View(" data=\"" + aFileName.toUtf8() + "\"")); + if (!rFileType.isEmpty()) + rWrt.Strm().WriteOString(Concat2View(" type=\"" + rFileType.toUtf8() + "\"")); + rWrt.Strm().WriteOString(">"); + rWrt.SetLFPossible(true); +} + +static void OutHTMLEndObject(SwHTMLWriter& rWrt) +{ + rWrt.Strm().WriteOString( + Concat2View("</" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object ">")); +} + +static bool TrySaveFormulaAsPDF(SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, + SwOLENode* pOLENd, bool bWriteReplacementGraphic, bool bInCntnr) +{ + if (!rWrt.mbReqIF) + return false; + if (!rWrt.m_bExportFormulasAsPDF) + return false; + + auto xTextContent = SwXTextEmbeddedObject::CreateXTextEmbeddedObject( + *rWrt.m_pDoc, const_cast<SwFrameFormat*>(&rFrameFormat)); + uno::Reference<frame::XStorable> xStorable(xTextContent->getEmbeddedObject(), uno::UNO_QUERY); + uno::Reference<lang::XServiceInfo> xServiceInfo(xStorable, uno::UNO_QUERY); + if (!xServiceInfo) + return false; + if (!xServiceInfo->supportsService(u"com.sun.star.formula.FormulaProperties"_ustr)) + return false; + + Graphic aGraphic(xTextContent->getReplacementGraphic()); + OUString aFileName = lcl_CalculateFileName(rWrt.GetOrigFileName(), aGraphic, u"pdf"_ustr); + + utl::MediaDescriptor aDescr; + aDescr[u"FilterName"_ustr] <<= u"math_pdf_Export"_ustr; + // Properties from starmath/inc/unomodel.hxx + aDescr[u"FilterData"_ustr] <<= comphelper::InitPropertySequence({ + { u"TitleRow"_ustr, css::uno::Any(false) }, + { u"FormulaText"_ustr, css::uno::Any(false) }, + { u"Border"_ustr, css::uno::Any(false) }, + { u"PrintFormat"_ustr, css::uno::Any(sal_Int32(1)) }, // PRINT_SIZE_SCALED + }); + xStorable->storeToURL(aFileName, aDescr.getAsConstPropertyValueList()); + + OutHTMLStartObject(rWrt, aFileName, u"application/pdf"_ustr); + + if (bWriteReplacementGraphic) + OutHTMLGraphic(rWrt, rFrameFormat, pOLENd, aGraphic, true, bInCntnr); + + OutHTMLEndObject(rWrt); + + return true; +} + SwHTMLWriter& OutHTML_FrameFormatOLENodeGrf( SwHTMLWriter& rWrt, const SwFrameFormat& rFrameFormat, bool bInCntnr, bool bWriteReplacementGraphic ) { @@ -1533,6 +1646,9 @@ SwHTMLWriter& OutHTML_FrameFormatOLENodeGrf( SwHTMLWriter& rWrt, const SwFrameFo return rWrt; } + if (TrySaveFormulaAsPDF(rWrt, rFrameFormat, pOLENd, bWriteReplacementGraphic, bInCntnr)) + return rWrt; + if ( !pOLENd->GetGraphic() ) { SAL_WARN("sw.html", "Unexpected missing OLE fallback graphic"); @@ -1630,80 +1746,18 @@ SwHTMLWriter& OutHTML_FrameFormatOLENodeGrf( SwHTMLWriter& rWrt, const SwFrameFo aFileType = aRTFType; } } - aFileName = URIHelper::simpleNormalizedMakeRelative(rWrt.GetBaseURL(), aFileName); // Refer to this data. - if (rWrt.IsLFPossible()) - rWrt.OutNewLine(); - rWrt.Strm().WriteOString(Concat2View("<" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object)); - rWrt.Strm().WriteOString(Concat2View(" data=\"" + aFileName.toUtf8() + "\"")); - if (!aFileType.isEmpty()) - rWrt.Strm().WriteOString(Concat2View(" type=\"" + aFileType.toUtf8() + "\"")); - rWrt.Strm().WriteOString(">"); + OutHTMLStartObject(rWrt, aFileName, aFileType); bObjectOpened = true; - rWrt.SetLFPossible(true); } if (!bObjectOpened || bWriteReplacementGraphic) - { - OUString aGraphicURL; - OUString aMimeType; - if(!rWrt.mbEmbedImages) - { - const OUString* pTempFileName = rWrt.GetOrigFileName(); - if(pTempFileName) - aGraphicURL = *pTempFileName; - - OUString aFilterName("JPG"); - XOutFlags nFlags = XOutFlags::UseGifIfPossible | XOutFlags::UseNativeIfPossible; - - if (bObjectOpened) - { - aFilterName = "PNG"; - nFlags = XOutFlags::NONE; - aMimeType = "image/png"; - - if (aGraphic.GetType() == GraphicType::NONE) - { - // The OLE Object has no replacement image, write a stub. - aGraphicURL = lcl_CalculateFileName(rWrt.GetOrigFileName(), aGraphic, u"png"); - osl::File aFile(aGraphicURL); - aFile.open(osl_File_OpenFlag_Create); - aFile.close(); - } - } - - ErrCode nErr = XOutBitmap::WriteGraphic( aGraphic, aGraphicURL, - aFilterName, - nFlags ); - if( nErr ) // error, don't write anything - { - rWrt.m_nWarn = WARN_SWG_POOR_LOAD; - if (bObjectOpened) // Still at least close the tag. - rWrt.Strm().WriteOString(Concat2View("</" + rWrt.GetNamespace() - + OOO_STRING_SVTOOLS_HTML_object ">")); - return rWrt; - } - aGraphicURL = URIHelper::SmartRel2Abs( - INetURLObject(rWrt.GetBaseURL()), aGraphicURL, - URIHelper::GetMaybeFileHdl() ); - - } - HtmlFrmOpts nFlags = bInCntnr ? HtmlFrmOpts::GenImgAllMask - : HtmlFrmOpts::GenImgMask; - if (bObjectOpened) - nFlags |= HtmlFrmOpts::Replacement; - HtmlWriter aHtml(rWrt.Strm(), rWrt.maNamespace); - OutHTML_ImageStart( aHtml, rWrt, rFrameFormat, aGraphicURL, aGraphic, - pOLENd->GetTitle(), pOLENd->GetTwipSize(), - nFlags, "ole", nullptr, aMimeType ); - OutHTML_ImageEnd(aHtml, rWrt); - } + OutHTMLGraphic(rWrt, rFrameFormat, pOLENd, aGraphic, bObjectOpened, bInCntnr); if (bObjectOpened) // Close native data. - rWrt.Strm().WriteOString(Concat2View("</" + rWrt.GetNamespace() + OOO_STRING_SVTOOLS_HTML_object - ">")); + OutHTMLEndObject(rWrt); return rWrt; } diff --git a/sw/source/filter/html/wrthtml.cxx b/sw/source/filter/html/wrthtml.cxx index 0578bbbf6682..e4980b311f5e 100644 --- a/sw/source/filter/html/wrthtml.cxx +++ b/sw/source/filter/html/wrthtml.cxx @@ -264,6 +264,12 @@ void SwHTMLWriter::SetupFilterFromPropertyValues( it->second >>= m_bExportImagesAsOLE; } + it = aStoreMap.find("ExportFormulasAsPDF"); + if (it != aStoreMap.end()) + { + it->second >>= m_bExportFormulasAsPDF; + } + it = aStoreMap.find("ShapeDPI"); if (it != aStoreMap.end()) { diff --git a/sw/source/filter/html/wrthtml.hxx b/sw/source/filter/html/wrthtml.hxx index 6b49fe984119..4f37296cf687 100644 --- a/sw/source/filter/html/wrthtml.hxx +++ b/sw/source/filter/html/wrthtml.hxx @@ -430,6 +430,9 @@ public: /// ReqIF mode: export images as OLE objects. bool m_bExportImagesAsOLE = false; + /// ReqIF mode: export formulas as PDFs. + bool m_bExportFormulasAsPDF = false; + /// DPI used when exporting a vector shape as a bitmap. std::optional<sal_Int32> m_nShapeDPI;