[Libreoffice-commits] core.git: Branch 'distro/collabora/cp-6.2' - 6 commits - filter/source sd/qa

Marco Cecchetti (via logerrit) logerrit at kemper.freedesktop.org
Mon Feb 22 19:05:30 UTC 2021


 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.cecchetti at collabora.com>
AuthorDate: Fri Feb 19 16:04:07 2021 +0100
Commit:     Andras Timar <andras.timar at 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 <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Andras Timar <andras.timar at 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.cecchetti at collabora.com>
AuthorDate: Wed Feb 17 23:46:23 2021 +0100
Commit:     Andras Timar <andras.timar at 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 <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Andras Timar <andras.timar at 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.cecchetti at collabora.com>
AuthorDate: Wed Feb 17 13:21:07 2021 +0100
Commit:     Andras Timar <andras.timar at 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 <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Andras Timar <andras.timar at 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.cecchetti at collabora.com>
AuthorDate: Mon Feb 15 17:57:00 2021 +0100
Commit:     Andras Timar <andras.timar at 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 <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Ashod Nakashian <ash at collabora.com>
    Reviewed-by: Marco Cecchetti <marco.cecchetti at 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.cecchetti at collabora.com>
AuthorDate: Tue Feb 2 14:05:46 2021 +0100
Commit:     Andras Timar <andras.timar at 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 <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Ashod Nakashian <ash at 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.cecchetti at collabora.com>
AuthorDate: Tue Jan 26 09:36:44 2021 +0100
Commit:     Andras Timar <andras.timar at 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 <jenkinscollaboraoffice at gmail.com>
    Reviewed-by: Ashod Nakashian <ash at 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


More information about the Libreoffice-commits mailing list