filter/source/svg/presentation_engine.js | 129 ++++++++++------ filter/source/svg/svgexport.cxx | 192 +++++++++++++++++++++++- filter/source/svg/svgfilter.hxx | 13 + filter/source/svg/svgwriter.cxx | 30 +++ filter/source/svg/svgwriter.hxx | 1 sd/qa/unit/SVGExportTests.cxx | 190 ++++++++++++++++++++++- sd/qa/unit/data/odp/slide-bitmap-background.odp |binary sd/qa/unit/data/odp/slide-tile-background.odp |binary 8 files changed, 493 insertions(+), 62 deletions(-)
New commits: commit 5ad541462aec381bb6a9d86db5ed20ecb6ddb496 Author: Marco Cecchetti <marco.cecche...@collabora.com> AuthorDate: Fri Feb 19 16:04:07 2021 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Mon Feb 22 20:04:48 2021 +0100 filter: svg: js engine: misplaced text: improving text field handling Change-Id: I8b5f9a39b3cd3fcfdae0d088eae0a875cf9404ee Reviewed-on: https://gerrit.libreoffice.org/c/core/+/111065 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Andras Timar <andras.ti...@collabora.com> diff --git a/filter/source/svg/presentation_engine.js b/filter/source/svg/presentation_engine.js index 0d4fc767c4ad..24fd4f53d2a7 100644 --- a/filter/source/svg/presentation_engine.js +++ b/filter/source/svg/presentation_engine.js @@ -5575,68 +5575,99 @@ PlaceholderShape.prototype.isValid = function() */ PlaceholderShape.prototype.init = function() { - var aTextFieldElement = getElementByClassName( this.masterPage.backgroundObjects, this.className ); if( aTextFieldElement ) { - var aPlaceholderElement = getElementByClassName( aTextFieldElement, 'PlaceholderText' ); - if( aPlaceholderElement ) + var aTextElem = getElementByClassName( aTextFieldElement, 'SVGTextShape' ); + if( aTextElem ) { - // Each text field element has an invisible rectangle that can be - // regarded as the text field bounding box. - // We exploit such a feature and the exported text adjust attribute - // value in order to set up correctly the position and text - // adjustment for the placeholder element. - var aSVGRectElem = getElementByClassName( aTextFieldElement, 'BoundingBox' ); - if( aSVGRectElem ) + var aPlaceholderElement = getElementByClassName(aTextElem, 'PlaceholderText'); + if( aPlaceholderElement ) { - var aRect = new Rectangle( aSVGRectElem ); - var sTextAdjust = getOOOAttribute( aTextFieldElement, aOOOAttrTextAdjust ) || 'left'; - var sTextAnchor, sX; - if( sTextAdjust == 'left' ) - { - sTextAnchor = 'start'; - sX = String( aRect.left ); - } - else if( sTextAdjust == 'right' ) - { - sTextAnchor = 'end'; - sX = String( aRect.right ); - } - else if( sTextAdjust == 'center' ) + // SVG 1.1 does not support text wrapping wrt a rectangle. + // When a text shape contains a placeholder, setting up the position + // of each text line doesn't work since the position is computed + // before replacing the placeholder text. + // Anyway each text shape has an invisible rectangle that can be + // regarded as the text shape bounding box. + // We exploit such a feature and the exported text adjust attribute + // value in order to set up correctly the position and text + // adjustment for the text shape content. + // We assume that once the real value has been substituted to + // the placeholder the resulting content is no more than a single line. + // So we remove from <tspan> elements used for setting up the + // position of text lines (class TextPosition) the 'x' and 'y' attribute. + // In the general case we would need to implement a function + // which is able to compute at which words the text shape content has + // to be wrapped. + var aSVGRectElem = getElementByClassName( aTextFieldElement, 'BoundingBox' ); + if( aSVGRectElem ) { - sTextAnchor = 'middle'; - var nMiddle = ( aRect.left + aRect.right ) / 2; - sX = String( parseInt( String( nMiddle ) ) ); + var aRect = new Rectangle( aSVGRectElem ); + var sTextAdjust = getOOOAttribute( aTextFieldElement, aOOOAttrTextAdjust ); + // the bbox of the text shape is indeed a bit larger, there is a bit of internal padding + var nMargin = 250; // 1000th mm + var sTextAnchor, sX; + if( sTextAdjust == 'left' ) + { + sTextAnchor = 'start'; + sX = String( Math.trunc( aRect.left + nMargin ) ); + } + else if( sTextAdjust == 'right' ) + { + sTextAnchor = 'end'; + sX = String( Math.trunc( aRect.right - nMargin ) ); + } + else if( sTextAdjust == 'center' ) + { + sTextAnchor = 'middle'; + var nMiddle = ( aRect.left + aRect.right ) / 2; + sX = String( parseInt( String( nMiddle ) ) ); + } + if( sTextAnchor ) + { + aTextElem.setAttribute( 'text-anchor', sTextAnchor ); + if( sX ) + aTextElem.setAttribute( 'x', sX ); + + var aTSpanElements = getElementsByClassName( aTextElem, 'TextPosition' ); + if( aTSpanElements ) + { + var i = 0; + for( ; i < aTSpanElements.length; ++i ) + { + var aTSpanElem = aTSpanElements[i]; + aTSpanElem.removeAttribute( 'x' ); + if( i !== 0 ) + aTSpanElem.removeAttribute( 'y' ); + } + } + } } - if( sTextAnchor ) - aPlaceholderElement.setAttribute( 'text-anchor', sTextAnchor ); - if( sX ) - aPlaceholderElement.setAttribute( 'x', sX ); - } - // date/time fields were not exported correctly when positioned chars are used - if( this.masterPage.metaSlide.theMetaDoc.bIsUsePositionedChars ) - { - // We remove all text lines but the first one used as placeholder. - var aTextLineGroupElem = aPlaceholderElement.parentNode.parentNode; - if( aTextLineGroupElem ) + // date/time fields were not exported correctly when positioned chars are used + if( this.masterPage.metaSlide.theMetaDoc.bIsUsePositionedChars ) { - // Just to be sure it is the element we are looking for. - var sFontFamilyAttr = aTextLineGroupElem.getAttribute( 'font-family' ); - if( sFontFamilyAttr ) + // We remove all text lines but the first one used as placeholder. + var aTextLineGroupElem = aPlaceholderElement.parentNode.parentNode; + if( aTextLineGroupElem ) { - var aChildSet = getElementChildren( aTextLineGroupElem ); - if( aChildSet.length > 1 ) - var i = 1; - for( ; i < aChildSet.length; ++i ) + // Just to be sure it is the element we are looking for. + var sFontFamilyAttr = aTextLineGroupElem.getAttribute( 'font-family' ); + if( sFontFamilyAttr ) { - aTextLineGroupElem.removeChild( aChildSet[i] ); + var aChildSet = getElementChildren( aTextLineGroupElem ); + if( aChildSet.length > 1 ) + var i = 1; + for( ; i < aChildSet.length; ++i ) + { + aTextLineGroupElem.removeChild( aChildSet[i] ); + } } } } + this.textElement = aPlaceholderElement; } - this.textElement = aPlaceholderElement; } this.element = aTextFieldElement; } diff --git a/filter/source/svg/svgexport.cxx b/filter/source/svg/svgexport.cxx index dec88345b43d..1f71feafe93a 100644 --- a/filter/source/svg/svgexport.cxx +++ b/filter/source/svg/svgexport.cxx @@ -2135,14 +2135,18 @@ bool SVGFilter::implExportShape( const Reference< css::drawing::XShape >& rxShap bool bIsPageNumber = ( aShapeClass == "Slide_Number" ); bool bIsFooter = ( aShapeClass == "Footer" ); bool bIsDateTime = ( aShapeClass == "Date/Time" ); - if( bIsPageNumber || bIsDateTime || bIsFooter ) + bool bTextField = bIsPageNumber || bIsFooter || bIsDateTime; + if( bTextField ) { // to notify to the SVGActionWriter::ImplWriteActions method // that we are dealing with a placeholder shape pElementId = &sPlaceholderTag; mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "visibility", "hidden" ); + } + if( bTextField || ( aShapeClass == "TextShape" ) ) + { sal_uInt16 nTextAdjust = sal_uInt16(ParagraphAdjust_LEFT); OUString sTextAdjust; xShapePropSet->getPropertyValue( "ParaAdjust" ) >>= nTextAdjust; commit aa03f345bd00334e8fdaaafba4e2ea69470e381d Author: Marco Cecchetti <marco.cecche...@collabora.com> AuthorDate: Wed Feb 17 23:46:23 2021 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Mon Feb 22 20:04:48 2021 +0100 filter: svg: unit test for placeholder locale We set the language to it-IT and check that the exported placeholder text is still <number> instead of <numero> Change-Id: I7ec7e25e53075da38cb87d81e9f8268b37121bfe Reviewed-on: https://gerrit.libreoffice.org/c/core/+/111115 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Andras Timar <andras.ti...@collabora.com> diff --git a/sd/qa/unit/SVGExportTests.cxx b/sd/qa/unit/SVGExportTests.cxx index 14b159e63529..269c75cedbde 100644 --- a/sd/qa/unit/SVGExportTests.cxx +++ b/sd/qa/unit/SVGExportTests.cxx @@ -14,7 +14,9 @@ #include <com/sun/star/frame/XStorable.hpp> #include <com/sun/star/frame/Desktop.hpp> #include <comphelper/processfactory.hxx> -#include <com/sun/star/packages/zip/ZipFileAccess.hpp> +#include <unotools/syslocaleoptions.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> #include <boost/preprocessor/stringize.hpp> @@ -66,6 +68,29 @@ static bool isValidTiledBackgroundId(const OUString& sId) class SdSVGFilterTest : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools { + class Resetter + { + private: + std::function<void ()> m_Func; + + public: + Resetter(std::function<void ()> const& rFunc) + : m_Func(rFunc) + { + } + ~Resetter() + { + try + { + m_Func(); + } + catch (...) // has to be reliable + { + CPPUNIT_FAIL("resetter failed with exception"); + } + } + }; + uno::Reference<lang::XComponent> mxComponent; utl::TempFile maTempFile; @@ -279,6 +304,37 @@ public: CPPUNIT_ASSERT_EQUAL_MESSAGE("The href attribute for <use> does not match the tiled background id attribute: ", sBackgroundId, sRef); } + void testSVGPlaceholderLocale() + { + static const OUString aLangISO("it-IT"); + SvtSysLocaleOptions aSysLocaleOptions; + aSysLocaleOptions.SetLocaleConfigString(aLangISO); + aSysLocaleOptions.SetUILocaleConfigString(aLangISO); + + auto aSavedSettings = Application::GetSettings(); + Resetter aResetter([&]() { Application::SetSettings(aSavedSettings); }); + AllSettings aSettings(aSavedSettings); + aSettings.SetLanguageTag(aLangISO, true); + Application::SetSettings(aSettings); + + executeExport("text-fields.odp"); + + xmlDocPtr svgDoc = parseXml(maTempFile); + CPPUNIT_ASSERT(svgDoc); + + assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2] ), "class", "Master_Slide"); + assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2] ), "class", "BackgroundObjects"); + + // Slide Name Field + assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[6] ), "class", "TextShape"); + assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[6]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "class", "PlaceholderText"); + assertXPathContent(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[6]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "<slide-name>"); + // Slide Number Field + assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[7] ), "class", "TextShape"); + assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[7]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "class", "PlaceholderText"); + assertXPathContent(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[7]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "<number>"); + } + CPPUNIT_TEST_SUITE(SdSVGFilterTest); CPPUNIT_TEST(testSVGExportTextDecorations); CPPUNIT_TEST(testSVGExportJavascriptURL); @@ -286,6 +342,7 @@ public: CPPUNIT_TEST(testSVGExportTextFieldsInMasterPage); CPPUNIT_TEST(testSVGExportSlideBitmapBackground); CPPUNIT_TEST(testSVGExportSlideTileBitmapBackground); + CPPUNIT_TEST(testSVGPlaceholderLocale); CPPUNIT_TEST_SUITE_END(); }; commit 27f43e1ee175806837a833a1495f2b4e59cd33c6 Author: Marco Cecchetti <marco.cecche...@collabora.com> AuthorDate: Wed Feb 17 13:21:07 2021 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Mon Feb 22 20:04:48 2021 +0100 filter: svg: export: renaming class attributes related to TextShape TextShape => SVGTextShape com.sun.star.drawing.TextShape => TextShape Change-Id: I4bbb465e0f65aa328527ac3022c0b68546fb5db6 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/111224 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Andras Timar <andras.ti...@collabora.com> diff --git a/filter/source/svg/presentation_engine.js b/filter/source/svg/presentation_engine.js index f6a42c4c0223..0d4fc767c4ad 100644 --- a/filter/source/svg/presentation_engine.js +++ b/filter/source/svg/presentation_engine.js @@ -5357,7 +5357,7 @@ function getTextFieldType ( elem ) { var sFieldType = null; var sClass = elem.getAttribute('class'); - if( sClass.endsWith( 'TextShape' ) ) + if( sClass == 'TextShape' ) { var aPlaceholderElement = getElementByClassName( elem, 'PlaceholderText' ); if (aPlaceholderElement) @@ -14711,7 +14711,7 @@ function AnimatedTextElement( aElement, aEventMultiplexer ) } var aTextShapeElement = aElement.parentNode; sTextType = aTextShapeElement.getAttribute( 'class' ); - if( sTextType !== 'TextShape' ) + if( sTextType !== 'SVGTextShape' ) { log( 'AnimatedTextElement: element parent is not a text shape.' ); return; diff --git a/filter/source/svg/svgexport.cxx b/filter/source/svg/svgexport.cxx index a7a08f486df9..dec88345b43d 100644 --- a/filter/source/svg/svgexport.cxx +++ b/filter/source/svg/svgexport.cxx @@ -2600,6 +2600,8 @@ OUString SVGFilter::implGetClassFromShape( const Reference< css::drawing::XShape aRet = "Graphic"; else if( aShapeType.lastIndexOf( "drawing.OLE2Shape" ) != -1 ) aRet = "OLE2"; + else if( aShapeType.lastIndexOf( "drawing.TextShape" ) != -1 ) + aRet = "TextShape"; else if( aShapeType.lastIndexOf( "presentation.HeaderShape" ) != -1 ) aRet = "Header"; else if( aShapeType.lastIndexOf( "presentation.FooterShape" ) != -1 ) diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx index f10906147777..07c234f036a1 100644 --- a/filter/source/svg/svgwriter.cxx +++ b/filter/source/svg/svgwriter.cxx @@ -1252,7 +1252,7 @@ void SVGTextWriter::startTextShape() { mbIsTextShapeStarted = true; maParentFont = vcl::Font(); - mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextShape" ); + mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "SVGTextShape" ); // if text is rotated, set transform matrix at text element const vcl::Font& rFont = mpVDev->GetFont(); diff --git a/sd/qa/unit/SVGExportTests.cxx b/sd/qa/unit/SVGExportTests.cxx index 4a45cc4edf29..14b159e63529 100644 --- a/sd/qa/unit/SVGExportTests.cxx +++ b/sd/qa/unit/SVGExportTests.cxx @@ -139,11 +139,11 @@ public: assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_G[2] ), "class", "SlideGroup"); assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_G[2]/SVG_G/SVG_G/SVG_G ), "class", "Slide"); assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_G[2]/SVG_G/SVG_G/SVG_G/SVG_G/SVG_G[1] ), "class", "TitleText"); - assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_G[2]/SVG_G/SVG_G/SVG_G/SVG_G/SVG_G[1]/SVG_G/SVG_TEXT ), "class", "TextShape"); + assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_G[2]/SVG_G/SVG_G/SVG_G/SVG_G/SVG_G[1]/SVG_G/SVG_TEXT ), "class", "SVGTextShape"); assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_G[2]/SVG_G/SVG_G/SVG_G/SVG_G/SVG_G[1]/SVG_G/SVG_TEXT/SVG_TSPAN ), "class", "TextParagraph"); assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_G[2]/SVG_G/SVG_G/SVG_G/SVG_G/SVG_G[1]/SVG_G/SVG_TEXT/SVG_TSPAN ), "text-decoration", "underline"); - assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_G[2]/SVG_G/SVG_G/SVG_G/SVG_G/SVG_G[2]/SVG_G/SVG_TEXT ), "class", "TextShape"); + assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_G[2]/SVG_G/SVG_G/SVG_G/SVG_G/SVG_G[2]/SVG_G/SVG_TEXT ), "class", "SVGTextShape"); assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_G[2]/SVG_G/SVG_G/SVG_G/SVG_G/SVG_G[2]/SVG_G/SVG_TEXT/SVG_TSPAN ), "class", "TextParagraph"); assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_G[2]/SVG_G/SVG_G/SVG_G/SVG_G/SVG_G[2]/SVG_G/SVG_TEXT/SVG_TSPAN ), "text-decoration", "line-through"); } @@ -182,19 +182,19 @@ public: assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2] ), "class", "Master_Slide"); assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2] ), "class", "BackgroundObjects"); // Current Date Field - assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[4] ), "class", "com.sun.star.drawing.TextShape"); + assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[4] ), "class", "TextShape"); assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[4]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "class", "PlaceholderText"); assertXPathContent(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[4]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "<date>"); // Current Time Field - assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[5] ), "class", "com.sun.star.drawing.TextShape"); + assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[5] ), "class", "TextShape"); assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[5]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "class", "PlaceholderText"); assertXPathContent(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[5]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "<time>"); // Slide Name Field - assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[6] ), "class", "com.sun.star.drawing.TextShape"); + assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[6] ), "class", "TextShape"); assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[6]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "class", "PlaceholderText"); assertXPathContent(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[6]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "<slide-name>"); // Slide Number Field - assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[7] ), "class", "com.sun.star.drawing.TextShape"); + assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[7] ), "class", "TextShape"); assertXPath(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[7]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "class", "PlaceholderText"); assertXPathContent(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[7]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "<number>"); } commit ecdbba7cf2326c77fd0ec3102554e3d1e8f97452 Author: Marco Cecchetti <marco.cecche...@collabora.com> AuthorDate: Mon Feb 15 17:57:00 2021 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Mon Feb 22 15:49:21 2021 +0100 filter: svg: text field: placeholder localization issue The text content for a placeholder is localized, so in case a French locale is used, the placeholder for a PageNumber text field is <numéro> instead of <number>. Change-Id: If1d31fee98d044775995b5b80567296f78d2a6c8 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/110944 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Ashod Nakashian <a...@collabora.com> Reviewed-by: Marco Cecchetti <marco.cecche...@collabora.com> diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx index a96efaaec878..f10906147777 100644 --- a/filter/source/svg/svgwriter.cxx +++ b/filter/source/svg/svgwriter.cxx @@ -1112,6 +1112,7 @@ bool SVGTextWriter::nextTextPortion() #endif msPageCount = ""; msDateTimeType = ""; + msTextFieldType = ""; if( xPortionTextRange.is() ) { #if OSL_DEBUG_LEVEL > 0 @@ -1155,6 +1156,7 @@ bool SVGTextWriter::nextTextPortion() ++pNames; } + msTextFieldType = sFieldName; #if OSL_DEBUG_LEVEL > 0 sInfo += "text field type: " + sFieldName + "; content: " + xTextField->getPresentation( /* show command: */ false ) + "; "; #endif @@ -1690,7 +1692,6 @@ void SVGTextWriter::implWriteTextPortion( const Point& rPos, if( mbIsPlaceholderShape ) { mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "PlaceholderText" ); - mbIsPlaceholderShape = false; } addFontAttributes( /* isTexTContainer: */ false ); @@ -1723,6 +1724,19 @@ void SVGTextWriter::implWriteTextPortion( const Point& rPos, SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ); mrExport.GetDocHandler()->characters( msDateTimeType ); } + else if( mbIsPlaceholderShape && rText.startsWith("<") && rText.endsWith(">") ) + { + OUString sContent; + if( msTextFieldType == "PageNumber" ) + sContent = "<number>"; + else if( msTextFieldType == "PageName" ) + sContent = "<slide-name>"; + else + sContent = rText; + + SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ); + mrExport.GetDocHandler()->characters( sContent ); + } else { SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ); diff --git a/filter/source/svg/svgwriter.hxx b/filter/source/svg/svgwriter.hxx index c5725d63e491..11df01208168 100644 --- a/filter/source/svg/svgwriter.hxx +++ b/filter/source/svg/svgwriter.hxx @@ -252,6 +252,7 @@ class SVGTextWriter final OUString msHyperlinkIdList; OUString msPageCount; OUString msDateTimeType; + OUString msTextFieldType; bool mbIsPlaceholderShape; static const bool mbIWS = false; vcl::Font maCurrentFont; commit 7ab136407252014273c9ba193f0bf9ea104c9db2 Author: Marco Cecchetti <marco.cecche...@collabora.com> AuthorDate: Tue Feb 2 14:05:46 2021 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Mon Feb 22 15:49:03 2021 +0100 filter: svg: export tiled background by exploiting svg:pattern element By exporting a tiled bitmap background by exploiting the <pattern> element we get performance improvement when the background is made of a big number of tiles. The unit test for the tiled background case has been updated. Change-Id: I80a4eebd081d2c59ec7d9906fc9c616692f7e0fa Reviewed-on: https://gerrit.libreoffice.org/c/core/+/110319 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Ashod Nakashian <a...@collabora.com> diff --git a/filter/source/svg/svgexport.cxx b/filter/source/svg/svgexport.cxx index a2357b605b18..a7a08f486df9 100644 --- a/filter/source/svg/svgexport.cxx +++ b/filter/source/svg/svgexport.cxx @@ -512,6 +512,32 @@ static void MetaBitmapActionGetSize( const MetaAction* pAction, Size& rSz ) OSL_FAIL( "MetaBitmapActionGetSize: passed MetaAction pointer is null." ); return; } + const MetaActionType nType = pAction->GetType(); + switch( nType ) + { + case MetaActionType::BMPSCALE: + { + const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction); + rSz = pA->GetSize(); + } + break; + case MetaActionType::BMPEXSCALE: + { + const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction); + rSz = pA->GetSize(); + } + break; + default: break; + } +} + +static void MetaBitmapActionGetOrigSize( const MetaAction* pAction, Size& rSz ) +{ + if( !pAction ) + { + OSL_FAIL( "MetaBitmapActionGetOrigSize: passed MetaAction pointer is null." ); + return; + } const MetaActionType nType = pAction->GetType(); MapMode aSourceMode( MapUnit::MapPixel ); @@ -538,6 +564,16 @@ static void MetaBitmapActionGetSize( const MetaAction* pAction, Size& rSz ) rSz = OutputDevice::LogicToLogic( rSz, aSourceMode, aTargetMode ); } +static OUString getPatternIdForTiledBackground( const OUString& sSlideId, BitmapChecksum nChecksum ) +{ + return "bg-pattern." + sSlideId + "." + OUString::number( nChecksum ); +} + +static OUString getIdForTiledBackground( const OUString& sSlideId, BitmapChecksum nChecksum ) +{ + return "bg-" + sSlideId + "." + OUString::number( nChecksum ); +} + } // end anonymous namespace size_t HashBitmap::operator()( const ObjectRepresentation& rObjRep ) const @@ -945,6 +981,7 @@ bool SVGFilter::implExportDocument() implExportTextEmbeddedBitmaps(); implExportBackgroundBitmaps(); mpSVGWriter->SetEmbeddedBitmapRefs( &maBitmapActionMap ); + implExportTiledBackground(); } // #i124608# export a given object selection, so no MasterPage export at all @@ -1530,6 +1567,77 @@ void SVGFilter::implExportBackgroundBitmaps() } } +void SVGFilter::implExportTiledBackground() +{ + if( maPatterProps.empty() ) + return; + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "class", "BackgroundPatterns" ); + SvXMLElementExport aDefsContainerElem( *mpSVGExport, XML_NAMESPACE_NONE, "defs", true, true ); + + for( const auto& [ rSlideId, rData ] : maPatterProps ) + { + auto aBitmapActionIt = maBitmapActionMap.find( rData.aBitmapChecksum ); + if( aBitmapActionIt != maBitmapActionMap.end() ) + { + // pattern element attributes + const OUString sPatternId = getPatternIdForTiledBackground( rSlideId, rData.aBitmapChecksum ); + // <pattern> <use> + { + // pattern element attributes + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sPatternId ); + + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", OUString::number( rData.aPos.X() ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", OUString::number( rData.aPos.Y() ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( rData.aSize.Width() ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( rData.aSize.Height() ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "patternUnits", "userSpaceOnUse" ); + + SvXMLElementExport aPatternElem( *mpSVGExport, XML_NAMESPACE_NONE, "pattern", true, true ); + + // use element attributes + const Size& aOrigSize = aBitmapActionIt->second->GetPrefSize(); + OUString sTransform; + Fraction aFractionX( rData.aSize.Width(), aOrigSize.Width() ); + Fraction aFractionY( rData.aSize.Height(), aOrigSize.Height() ); + double scaleX = rtl_math_round( double(aFractionX), 3, rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected ); + double scaleY = rtl_math_round( double(aFractionY), 3, rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected ); + if( !rtl_math_approxEqual( scaleX, 1.0 ) || !rtl_math_approxEqual( scaleY, 1.0 ) ) + sTransform += " scale(" + OUString::number( double(aFractionX) ) + ", " + OUString::number( double(aFractionY) ) + ")"; + + if( !sTransform.isEmpty() ) + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "transform", sTransform ); + + // referenced bitmap + OUString sRefId = "#bitmap(" + OUString::number( rData.aBitmapChecksum ) + ")"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "xlink:href", sRefId ); + + SvXMLElementExport aUseElem( *mpSVGExport, XML_NAMESPACE_NONE, "use", true, true ); + } // </use> </pattern> + + // <g> <rect> + { + // group + const OUString sBgId = getIdForTiledBackground( rSlideId, rData.aBitmapChecksum ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "id", sBgId ); + + SvXMLElementExport aGroupElem( *mpSVGExport, XML_NAMESPACE_NONE, "g", true, true ); + + // rectangle + const OUString sUrl = "url(#" + sPatternId + ")"; + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "x", "0" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "y", "0" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "width", OUString::number( rData.aSlideSize.Width() ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "height", OUString::number( rData.aSlideSize.Height() ) ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "stroke", "none" ); + mpSVGExport->AddAttribute( XML_NAMESPACE_NONE, "fill", sUrl ); + + SvXMLElementExport aRectElem( *mpSVGExport, XML_NAMESPACE_NONE, "rect", true, true ); + } // </g> </rect> + } + } +} + /** SVGFilter::implExportTextEmbeddedBitmaps We export bitmaps embedded into text shapes, such as those used by list items with image style, only once in a specific defs element. @@ -2379,21 +2487,63 @@ void SVGFilter::implCreateObjectsFromBackground( const Reference< css::drawing:: xExporter->filter( aDescriptor ); aMtf.Read( *aFile.GetStream( StreamMode::READ ) ); - MetaAction* pAction; + bool bIsBitmap = false; + bool bIsTiled = false; + + // look for background type + Reference< XPropertySet > xPropSet( rxDrawPage, UNO_QUERY ); + if( xPropSet.is() ) + { + Reference< XPropertySet > xBackground; + xPropSet->getPropertyValue( "Background" ) >>= xBackground; + if( xBackground.is() ) + { + drawing::FillStyle aFillStyle; + if( xBackground->getPropertyValue( "FillStyle" ) >>= aFillStyle ) + { + if( aFillStyle == drawing::FillStyle::FillStyle_BITMAP ) + { + bIsBitmap = true; + xBackground->getPropertyValue( "FillBitmapTile" ) >>= bIsTiled; + + // we do not handle tiled background with a row or column offset + sal_Int32 nFillBitmapOffsetX = 0, nFillBitmapOffsetY = 0; + xBackground->getPropertyValue( "FillBitmapOffsetX" ) >>= nFillBitmapOffsetX; + xBackground->getPropertyValue( "FillBitmapOffsetY" ) >>= nFillBitmapOffsetY; + bIsTiled = bIsTiled && ( nFillBitmapOffsetX == 0 && nFillBitmapOffsetY == 0 ); + } + } + } + } + + if( !bIsBitmap ) + { + (*mpObjects)[ rxDrawPage ] = ObjectRepresentation( rxDrawPage, aMtf ); + return; + } + + GDIMetaFile aTiledMtf; + bool bBitmapFound = false; + MetaAction* pAction; sal_uLong nCount = aMtf.GetActionSize(); for( sal_uLong nCurAction = 0; nCurAction < nCount; ++nCurAction ) { pAction = aMtf.GetAction( nCurAction ); const MetaActionType nType = pAction->GetType(); + // collect bitmap if( nType == MetaActionType::BMPSCALE || nType == MetaActionType::BMPEXSCALE ) { + if( bBitmapFound ) + continue; + bBitmapFound = true; // the subsequent bitmaps are still the same just translated + BitmapChecksum nChecksum = GetBitmapChecksum( pAction ); if( maBitmapActionMap.find( nChecksum ) == maBitmapActionMap.end() ) { Point aPos; // (0, 0) Size aSize; - MetaBitmapActionGetSize( pAction, aSize ); + MetaBitmapActionGetOrigSize( pAction, aSize ); MetaAction* pBitmapAction = CreateMetaBitmapAction( pAction, aPos, aSize ); if( pBitmapAction ) { @@ -2405,10 +2555,38 @@ void SVGFilter::implCreateObjectsFromBackground( const Reference< css::drawing:: maBitmapActionMap[ nChecksum ].reset( pEmbeddedBitmapMtf ); } } + + if( bIsTiled ) + { + // collect data for <pattern> and <rect> + const OUString & sPageId = implGetValidIDFromInterface( rxDrawPage ); + Point aPos; + MetaBitmapActionGetPoint( pAction, aPos ); + Size aSize; + MetaBitmapActionGetSize( pAction, aSize ); + + sal_Int32 nSlideWidth = 0, nSlideHeight = 0; + xPropSet->getPropertyValue( "Width" ) >>= nSlideWidth; + xPropSet->getPropertyValue( "Height" ) >>= nSlideHeight; + + maPatterProps[ sPageId ] = { nChecksum, aPos, aSize, { nSlideWidth, nSlideHeight } }; + + // create meta comment action that is used to exporting + // a <use> element which points to the group element representing the background + const OUString sBgId = getIdForTiledBackground( sPageId, nChecksum ); + OString sComment = sTiledBackgroundTag + " " + sBgId.toUtf8(); + MetaCommentAction* pCommentAction = new MetaCommentAction( sComment ); + if( pCommentAction ) + aTiledMtf.AddAction( pCommentAction ); + } + } + else if( bIsTiled && nType != MetaActionType::CLIPREGION ) + { + aTiledMtf.AddAction( pAction ); } } - (*mpObjects)[ rxDrawPage ] = ObjectRepresentation( rxDrawPage, aMtf ); + (*mpObjects)[ rxDrawPage ] = ObjectRepresentation( rxDrawPage, bIsTiled ? aTiledMtf : aMtf ); } OUString SVGFilter::implGetClassFromShape( const Reference< css::drawing::XShape >& rxShape ) diff --git a/filter/source/svg/svgfilter.hxx b/filter/source/svg/svgfilter.hxx index 64dc619dc739..3f7979ecf766 100644 --- a/filter/source/svg/svgfilter.hxx +++ b/filter/source/svg/svgfilter.hxx @@ -66,6 +66,8 @@ using namespace ::com::sun::star::xml::sax; // Placeholder tag used into the ImplWriteActions method to filter text placeholder fields static const OUString sPlaceholderTag( "<[:isPlaceholder:]>" ); +// This tag is used for exporting a slide background made of tiled bitmaps +static const OString sTiledBackgroundTag( "SLIDE_BACKGROUND" ); class SVGExport : public SvXMLExport { @@ -174,6 +176,15 @@ struct EqualityBitmap // This must match the same type definition in svgwriter.hxx typedef std::unordered_map< BitmapChecksum, std::unique_ptr< GDIMetaFile > > MetaBitmapActionMap; +struct PatternData +{ + BitmapChecksum aBitmapChecksum; + Point aPos; + Size aSize; + Size aSlideSize; +}; +typedef std::map<OUString, PatternData> PatternPropertySet; + class SVGFontExport; class SVGActionWriter; class EditFieldInfo; @@ -234,6 +245,7 @@ private: MetaBitmapActionSet mEmbeddedBitmapActionSet; ObjectMap mEmbeddedBitmapActionMap; MetaBitmapActionMap maBitmapActionMap; + PatternPropertySet maPatterProps; std::vector< Reference< css::drawing::XDrawPage > > mMasterPageTargets; Link<EditFieldInfo*,void> maOldFieldHdl; @@ -254,6 +266,7 @@ private: void implEmbedBulletGlyph( sal_Unicode cBullet, const OUString & sPathData ); void implExportTextEmbeddedBitmaps(); void implExportBackgroundBitmaps(); + void implExportTiledBackground(); void implGenerateScript(); bool implExportDocument(); diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx index 4f4bea2dc98b..a96efaaec878 100644 --- a/filter/source/svg/svgwriter.cxx +++ b/filter/source/svg/svgwriter.cxx @@ -3568,6 +3568,18 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, } } } + else if( pA->GetComment().startsWithIgnoreAsciiCase( sTiledBackgroundTag ) ) + { + // In the tile case the background is rendered through a rectangle + // filled by exploiting an exported pattern element. + // Both the pattern and the rectangle are embedded in a <defs> element. + // The comment content has the following format: "SLIDE_BACKGROUND <background-id>" + const OString& sComment = pA->GetComment(); + OUString sRefId = "#" + OUString::fromUtf8( sComment.getToken(1, ' ') ); + mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId ); + + SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true ); + } } break; diff --git a/sd/qa/unit/SVGExportTests.cxx b/sd/qa/unit/SVGExportTests.cxx index d14f7e146893..4a45cc4edf29 100644 --- a/sd/qa/unit/SVGExportTests.cxx +++ b/sd/qa/unit/SVGExportTests.cxx @@ -28,6 +28,8 @@ #define SVG_DEFS *[name()='defs'] #define SVG_IMAGE *[name()='image'] #define SVG_USE *[name()='use'] +#define SVG_PATTERN *[name()='pattern'] +#define SVG_RECT *[name()='rect'] using namespace css; @@ -47,6 +49,19 @@ static BitmapChecksum getBitmapChecksumFromId(const OUString& sId) OUString sChecksum = sId.copy( nStart, nCount ); return sChecksum.toUInt64(); } + +static bool isValidBackgroundPatternId(const OUString& sId) +{ + std::regex aRegEx( R"(bg\-pattern\.id\d+\.\d+)" ); + return std::regex_match(sId.toUtf8().getStr(), aRegEx); +} + +static bool isValidTiledBackgroundId(const OUString& sId) +{ + std::regex aRegEx( R"(bg\-id\d+\.\d+)" ); + return std::regex_match(sId.toUtf8().getStr(), aRegEx); +} + } class SdSVGFilterTest : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools @@ -219,31 +234,49 @@ public: xmlDocPtr svgDoc = parseXml(maTempFile); CPPUNIT_ASSERT(svgDoc); + // check the bitmap assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[9] ), "class", "BackgroundBitmaps"); assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[9]/SVG_IMAGE ), 1); + // check the pattern and background rectangle + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10] ), "class", "BackgroundPatterns"); + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_PATTERN ), 1); + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_PATTERN/SVG_USE ), 1); + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_G/SVG_RECT ), 1); + + + // check that <pattern><use> is pointing to the correct <image> OUString sImageId = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[9]/SVG_IMAGE ), "id"); CPPUNIT_ASSERT_MESSAGE(OString("The exported bitmap has not a valid id: " + sImageId.toUtf8()).getStr(), isValidBitmapId(sImageId)); BitmapChecksum nChecksum = getBitmapChecksumFromId(sImageId); CPPUNIT_ASSERT_MESSAGE(OString("The exported bitmap has not a valid checksum: " + sImageId.toUtf8()).getStr(), nChecksum != 0); - // tiles case - constexpr unsigned int nNumberOfTiles = 37; + OUString sRef = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_PATTERN/SVG_USE ), "href"); + CPPUNIT_ASSERT_MESSAGE("The <pattern><use> element has not a valid href attribute: starting '#' not present.", sRef.startsWith("#")); + sRef = sRef.copy(1); + CPPUNIT_ASSERT_EQUAL_MESSAGE("The href attribute for <pattern><use> does not match the <image> id attribute: ", sImageId, sRef); + + OUString sPatternId = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_PATTERN ), "id"); + CPPUNIT_ASSERT_MESSAGE(OString("The exported pattern has not a valid id: " + sPatternId.toUtf8()).getStr(), isValidBackgroundPatternId(sPatternId)); + + OUString sFillUrl = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_G/SVG_RECT ), "fill"); + CPPUNIT_ASSERT_MESSAGE("The fill attribute for the <rectangle> element has not a url format .", sFillUrl.startsWith("url(#") && sFillUrl.endsWith(")")); + // remove "url(#" and ")" + sFillUrl = sFillUrl.copy(5, sFillUrl.getLength() - 6); + CPPUNIT_ASSERT_EQUAL_MESSAGE("The fill url for <rectangle> does not match the <pattern> id attribute: ", sPatternId, sFillUrl); + + OUString sBackgroundId = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[10]/SVG_G ), "id"); + CPPUNIT_ASSERT_MESSAGE(OString("The exported tiled background has not a valid id: " + sBackgroundId.toUtf8()).getStr(), isValidTiledBackgroundId(sBackgroundId)); + + // check <use> element that point to the tiled background assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS ), "class", "SlideBackground"); - assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS/SVG_G/SVG_G/SVG_USE ), nNumberOfTiles); - - for (unsigned int i = 1; i <= nNumberOfTiles; ++i) - { - OString sIndex = OStringLiteral("[") + OString::number(i) + OStringLiteral("]"); - OUString sRef = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS/SVG_G/SVG_G/SVG_USE ) + sIndex, "href"); - CPPUNIT_ASSERT_MESSAGE("The <use> element has not a valid href attribute: starting '#' not present.", sRef.startsWith("#")); - sRef = sRef.copy(1); - CPPUNIT_ASSERT_MESSAGE(OString("The <use> element does not point to a valid bitmap id: " + sRef.toUtf8()).getStr(), isValidBitmapId(sRef)); - - BitmapChecksum nUseChecksum = getBitmapChecksumFromId(sRef); - CPPUNIT_ASSERT_EQUAL_MESSAGE("The bitmap checksum used in <use> does not match the expected one: ", nChecksum, nUseChecksum); - } + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS/SVG_G/SVG_USE ), 1); + + sRef = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS/SVG_G/SVG_USE ), "href"); + CPPUNIT_ASSERT_MESSAGE("The <use> element has not a valid href attribute: starting '#' not present.", sRef.startsWith("#")); + sRef = sRef.copy(1); + CPPUNIT_ASSERT_EQUAL_MESSAGE("The href attribute for <use> does not match the tiled background id attribute: ", sBackgroundId, sRef); } CPPUNIT_TEST_SUITE(SdSVGFilterTest); commit 2c58d033cd3c4b26e40dca292e1e806b8cfcdbe4 Author: Marco Cecchetti <marco.cecche...@collabora.com> AuthorDate: Tue Jan 26 09:36:44 2021 +0100 Commit: Andras Timar <andras.ti...@collabora.com> CommitDate: Mon Feb 22 15:45:00 2021 +0100 filter: svg: js engine: unit test: slide background: exporting bitmaps Two unit tests: 1 - a slide background with a single bitmap 2 - a slide background with bitmap tiles Change-Id: Iffdb9ea958ba07391dfbdcfd6e925a9461e2af84 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/109932 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Ashod Nakashian <a...@collabora.com> diff --git a/sd/qa/unit/SVGExportTests.cxx b/sd/qa/unit/SVGExportTests.cxx index 394a591ccbbf..d14f7e146893 100644 --- a/sd/qa/unit/SVGExportTests.cxx +++ b/sd/qa/unit/SVGExportTests.cxx @@ -18,15 +18,37 @@ #include <boost/preprocessor/stringize.hpp> +#include <regex> + #define MAKE_PATH_STRING( path ) BOOST_PP_STRINGIZE( path ) #define SVG_SVG *[name()='svg'] #define SVG_G *[name()='g'] #define SVG_TEXT *[name()='text'] #define SVG_TSPAN *[name()='tspan'] #define SVG_DEFS *[name()='defs'] +#define SVG_IMAGE *[name()='image'] +#define SVG_USE *[name()='use'] using namespace css; +namespace +{ +static bool isValidBitmapId(const OUString& sId) +{ + std::regex aRegEx("bitmap\\(\\d+\\)"); + return std::regex_match(sId.toUtf8().getStr(), aRegEx); +} + +static BitmapChecksum getBitmapChecksumFromId(const OUString& sId) +{ + sal_Int32 nStart = sId.indexOf("(") + 1; + sal_Int32 nCount = sId.indexOf(")") - nStart; + CPPUNIT_ASSERT(nStart > 0 && nCount > 0); + OUString sChecksum = sId.copy( nStart, nCount ); + return sChecksum.toUInt64(); +} +} + class SdSVGFilterTest : public test::BootstrapFixture, public unotest::MacrosTest, public XmlTestTools { uno::Reference<lang::XComponent> mxComponent; @@ -162,11 +184,75 @@ public: assertXPathContent(svgDoc, MAKE_PATH_STRING( /SVG_SVG/SVG_DEFS[9]/SVG_G[2]/SVG_G[2]/SVG_G[7]/SVG_G/SVG_TEXT/SVG_TSPAN/SVG_TSPAN/SVG_TSPAN ), "<number>"); } + void testSVGExportSlideBitmapBackground() + { + executeExport("slide-bitmap-background.odp"); + + xmlDocPtr svgDoc = parseXml(maTempFile); + CPPUNIT_ASSERT(svgDoc); + + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[9] ), "class", "BackgroundBitmaps"); + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[9]/SVG_IMAGE ), 1); + + OUString sImageId = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[9]/SVG_IMAGE ), "id"); + CPPUNIT_ASSERT_MESSAGE(OString("The exported bitmap has not a valid id: " + sImageId.toUtf8()).getStr(), isValidBitmapId(sImageId)); + + BitmapChecksum nChecksum = getBitmapChecksumFromId(sImageId); + CPPUNIT_ASSERT_MESSAGE(OString("The exported bitmap has not a valid checksum: " + sImageId.toUtf8()).getStr(), nChecksum != 0); + + // single image case + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS ), "class", "SlideBackground"); + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS/SVG_G/SVG_G/SVG_USE ), 1); + OUString sRef = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS/SVG_G/SVG_G/SVG_USE ), "href"); + CPPUNIT_ASSERT_MESSAGE("The <use> element has not a valid href attribute: starting '#' not present.", sRef.startsWith("#")); + sRef = sRef.copy(1); + CPPUNIT_ASSERT_MESSAGE(OString("The <use> element does not point to a valid bitmap id: " + sRef.toUtf8()).getStr(), isValidBitmapId(sRef)); + + BitmapChecksum nUseChecksum = getBitmapChecksumFromId(sRef); + CPPUNIT_ASSERT_EQUAL_MESSAGE("The bitmap checksum used in <use> does not match the expected one: ", nChecksum, nUseChecksum); + } + + void testSVGExportSlideTileBitmapBackground() + { + executeExport("slide-tile-background.odp"); + + xmlDocPtr svgDoc = parseXml(maTempFile); + CPPUNIT_ASSERT(svgDoc); + + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[9] ), "class", "BackgroundBitmaps"); + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[9]/SVG_IMAGE ), 1); + + OUString sImageId = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_DEFS[9]/SVG_IMAGE ), "id"); + CPPUNIT_ASSERT_MESSAGE(OString("The exported bitmap has not a valid id: " + sImageId.toUtf8()).getStr(), isValidBitmapId(sImageId)); + + BitmapChecksum nChecksum = getBitmapChecksumFromId(sImageId); + CPPUNIT_ASSERT_MESSAGE(OString("The exported bitmap has not a valid checksum: " + sImageId.toUtf8()).getStr(), nChecksum != 0); + + // tiles case + constexpr unsigned int nNumberOfTiles = 37; + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS ), "class", "SlideBackground"); + assertXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS/SVG_G/SVG_G/SVG_USE ), nNumberOfTiles); + + for (unsigned int i = 1; i <= nNumberOfTiles; ++i) + { + OString sIndex = OStringLiteral("[") + OString::number(i) + OStringLiteral("]"); + OUString sRef = getXPath(svgDoc, SAL_STRINGIFY( /SVG_SVG/SVG_G[2]/SVG_G[1]/SVG_G/SVG_G/SVG_G/SVG_DEFS/SVG_G/SVG_G/SVG_USE ) + sIndex, "href"); + CPPUNIT_ASSERT_MESSAGE("The <use> element has not a valid href attribute: starting '#' not present.", sRef.startsWith("#")); + sRef = sRef.copy(1); + CPPUNIT_ASSERT_MESSAGE(OString("The <use> element does not point to a valid bitmap id: " + sRef.toUtf8()).getStr(), isValidBitmapId(sRef)); + + BitmapChecksum nUseChecksum = getBitmapChecksumFromId(sRef); + CPPUNIT_ASSERT_EQUAL_MESSAGE("The bitmap checksum used in <use> does not match the expected one: ", nChecksum, nUseChecksum); + } + } + CPPUNIT_TEST_SUITE(SdSVGFilterTest); CPPUNIT_TEST(testSVGExportTextDecorations); CPPUNIT_TEST(testSVGExportJavascriptURL); CPPUNIT_TEST(testSVGExportSlideCustomBackground); CPPUNIT_TEST(testSVGExportTextFieldsInMasterPage); + CPPUNIT_TEST(testSVGExportSlideBitmapBackground); + CPPUNIT_TEST(testSVGExportSlideTileBitmapBackground); CPPUNIT_TEST_SUITE_END(); }; diff --git a/sd/qa/unit/data/odp/slide-bitmap-background.odp b/sd/qa/unit/data/odp/slide-bitmap-background.odp new file mode 100644 index 000000000000..46ea62be5a3a Binary files /dev/null and b/sd/qa/unit/data/odp/slide-bitmap-background.odp differ diff --git a/sd/qa/unit/data/odp/slide-tile-background.odp b/sd/qa/unit/data/odp/slide-tile-background.odp new file mode 100644 index 000000000000..d926b555f457 Binary files /dev/null and b/sd/qa/unit/data/odp/slide-tile-background.odp differ _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits