diff options
author | Marco Cecchetti <marco.cecchetti@collabora.com> | 2021-02-02 14:05:46 +0100 |
---|---|---|
committer | Andras Timar <andras.timar@collabora.com> | 2021-03-31 10:41:36 +0200 |
commit | 398c470cbe0b0b66a440e643b095615fb8c04471 (patch) | |
tree | b01bec9b805fbbd95a5cef2b9da170daf7bf76de | |
parent | 64b4673c35430197374ce8fb19a9127f2f8d1e31 (diff) |
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@gmail.com>
Reviewed-by: Ashod Nakashian <ash@collabora.com>
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/111844
Tested-by: Jenkins
Reviewed-by: Marco Cecchetti <marco.cecchetti@collabora.com>
-rw-r--r-- | filter/source/svg/svgexport.cxx | 184 | ||||
-rw-r--r-- | filter/source/svg/svgfilter.hxx | 13 | ||||
-rw-r--r-- | filter/source/svg/svgwriter.cxx | 12 | ||||
-rw-r--r-- | sd/qa/unit/SVGExportTests.cxx | 64 |
4 files changed, 255 insertions, 18 deletions
diff --git a/filter/source/svg/svgexport.cxx b/filter/source/svg/svgexport.cxx index 643f42bb3add..acd2b4ab2f4a 100644 --- a/filter/source/svg/svgexport.cxx +++ b/filter/source/svg/svgexport.cxx @@ -519,6 +519,32 @@ 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; + } +} + +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 ); @@ -545,6 +571,16 @@ void MetaBitmapActionGetSize( const MetaAction* pAction, Size& rSz ) rSz = OutputDevice::LogicToLogic( rSz, aSourceMode, aTargetMode ); } +OUString getPatternIdForTiledBackground( std::u16string_view sSlideId, BitmapChecksum nChecksum ) +{ + return OUString::Concat("bg-pattern.") + sSlideId + "." + OUString::number( nChecksum ); +} + +OUString getIdForTiledBackground( std::u16string_view sSlideId, BitmapChecksum nChecksum ) +{ + return OUString::Concat("bg-") + sSlideId + "." + OUString::number( nChecksum ); +} + } // end anonymous namespace size_t HashBitmap::operator()( const ObjectRepresentation& rObjRep ) const @@ -940,6 +976,7 @@ bool SVGFilter::implExportDocument() implExportTextEmbeddedBitmaps(); implExportBackgroundBitmaps(); mpSVGWriter->SetEmbeddedBitmapRefs( &maBitmapActionMap ); + implExportTiledBackground(); } // #i124608# export a given object selection, so no MasterPage export at all @@ -1520,6 +1557,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. @@ -2361,21 +2469,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 ) { @@ -2387,10 +2537,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 beb12f2add58..7bdaa9d60f09 100644 --- a/filter/source/svg/svgfilter.hxx +++ b/filter/source/svg/svgfilter.hxx @@ -55,6 +55,8 @@ using namespace ::com::sun::star::xml::sax; // Placeholder tag used into the ImplWriteActions method to filter text placeholder fields const OUString sPlaceholderTag( "<[:isPlaceholder:]>" ); +// This tag is used for exporting a slide background made of tiled bitmaps +const OString sTiledBackgroundTag( "SLIDE_BACKGROUND" ); class SVGExport : public SvXMLExport { @@ -155,6 +157,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; @@ -213,6 +224,7 @@ private: MetaBitmapActionSet mEmbeddedBitmapActionSet; ObjectMap mEmbeddedBitmapActionMap; MetaBitmapActionMap maBitmapActionMap; + PatternPropertySet maPatterProps; std::vector< Reference< css::drawing::XDrawPage > > mMasterPageTargets; Link<EditFieldInfo*,void> maOldFieldHdl; @@ -233,6 +245,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 23a8ce1c7a96..56eba1236a32 100644 --- a/filter/source/svg/svgwriter.cxx +++ b/filter/source/svg/svgwriter.cxx @@ -3633,6 +3633,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 60222d2dd263..bf0322235b20 100644 --- a/sd/qa/unit/SVGExportTests.cxx +++ b/sd/qa/unit/SVGExportTests.cxx @@ -26,6 +26,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; @@ -46,6 +48,19 @@ BitmapChecksum getBitmapChecksumFromId(const OUString& sId) OUString sChecksum = sId.copy( nStart, nCount ); return sChecksum.toUInt64(); } + +bool isValidBackgroundPatternId(const OUString& sId) +{ + std::regex aRegEx( R"(bg\-pattern\.id\d+\.\d+)" ); + return std::regex_match(sId.toUtf8().getStr(), aRegEx); +} + +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 @@ -218,31 +233,50 @@ public: xmlDocUniquePtr 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"); + bool bIsUrlFormat = sFillUrl.startsWith("url(#") && sFillUrl.endsWith(")"); + CPPUNIT_ASSERT_MESSAGE("The fill attribute for the <rectangle> element has not a url format .", bIsUrlFormat); + // 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); |