diff options
Diffstat (limited to 'filter/source/svg/svgwriter.cxx')
-rw-r--r-- | filter/source/svg/svgwriter.cxx | 510 |
1 files changed, 316 insertions, 194 deletions
diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx index e066806cea9a..e2079304abbc 100644 --- a/filter/source/svg/svgwriter.cxx +++ b/filter/source/svg/svgwriter.cxx @@ -37,6 +37,10 @@ #include <xmloff/namespacemap.hxx> #include <xmloff/unointerfacetouniqueidentifiermapper.hxx> #include <i18nlangtag/languagetag.hxx> +#include <o3tl/string_view.hxx> +#include <svx/svdomedia.hxx> +#include <basegfx/utils/bgradient.hxx> +#include <tools/vcompat.hxx> #include <com/sun/star/container/XEnumerationAccess.hpp> #include <com/sun/star/container/XIndexReplace.hpp> @@ -49,43 +53,43 @@ #include <memory> -constexpr OUStringLiteral aPrefixClipPathId = u"clip_path_"; - -constexpr OUStringLiteral aXMLElemG = u"g"; -constexpr OUStringLiteral aXMLElemDefs = u"defs"; -constexpr OUStringLiteral aXMLElemText = u"text"; -constexpr OUStringLiteral aXMLElemTspan = u"tspan"; -constexpr OUStringLiteral aXMLElemLinearGradient = u"linearGradient"; -constexpr OUStringLiteral aXMLElemStop = u"stop"; - -constexpr OUStringLiteral aXMLAttrTransform = u"transform"; -constexpr OUStringLiteral aXMLAttrStyle = u"style"; -constexpr OUStringLiteral aXMLAttrId = u"id"; -constexpr OUStringLiteral aXMLAttrX = u"x"; -constexpr OUStringLiteral aXMLAttrY = u"y"; -constexpr OUStringLiteral aXMLAttrX1 = u"x1"; -constexpr OUStringLiteral aXMLAttrY1 = u"y1"; -constexpr OUStringLiteral aXMLAttrX2 = u"x2"; -constexpr OUStringLiteral aXMLAttrY2 = u"y2"; -constexpr OUStringLiteral aXMLAttrCX = u"cx"; -constexpr OUStringLiteral aXMLAttrCY = u"cy"; -constexpr OUStringLiteral aXMLAttrRX = u"rx"; -constexpr OUStringLiteral aXMLAttrRY = u"ry"; -constexpr OUStringLiteral aXMLAttrWidth = u"width"; -constexpr OUStringLiteral aXMLAttrHeight = u"height"; -constexpr OUStringLiteral aXMLAttrStrokeWidth = u"stroke-width"; -constexpr OUStringLiteral aXMLAttrFill = u"fill"; -constexpr OUStringLiteral aXMLAttrFontFamily = u"font-family"; -constexpr OUStringLiteral aXMLAttrFontSize = u"font-size"; -constexpr OUStringLiteral aXMLAttrFontStyle = u"font-style"; -constexpr OUStringLiteral aXMLAttrFontWeight = u"font-weight"; -constexpr OUStringLiteral aXMLAttrTextDecoration = u"text-decoration"; -constexpr OUStringLiteral aXMLAttrXLinkHRef = u"xlink:href"; -constexpr OUStringLiteral aXMLAttrGradientUnits = u"gradientUnits"; -constexpr OUStringLiteral aXMLAttrOffset = u"offset"; -constexpr OUStringLiteral aXMLAttrStopColor = u"stop-color"; -constexpr OUStringLiteral aXMLAttrStrokeLinejoin = u"stroke-linejoin"; -constexpr OUStringLiteral aXMLAttrStrokeLinecap = u"stroke-linecap"; +constexpr OUString aPrefixClipPathId = u"clip_path_"_ustr; + +constexpr OUString aXMLElemG = u"g"_ustr; +constexpr OUString aXMLElemDefs = u"defs"_ustr; +constexpr OUString aXMLElemText = u"text"_ustr; +constexpr OUString aXMLElemTspan = u"tspan"_ustr; +constexpr OUString aXMLElemLinearGradient = u"linearGradient"_ustr; +constexpr OUString aXMLElemStop = u"stop"_ustr; + +constexpr OUString aXMLAttrTransform = u"transform"_ustr; +constexpr OUString aXMLAttrStyle = u"style"_ustr; +constexpr OUString aXMLAttrId = u"id"_ustr; +constexpr OUString aXMLAttrX = u"x"_ustr; +constexpr OUString aXMLAttrY = u"y"_ustr; +constexpr OUString aXMLAttrX1 = u"x1"_ustr; +constexpr OUString aXMLAttrY1 = u"y1"_ustr; +constexpr OUString aXMLAttrX2 = u"x2"_ustr; +constexpr OUString aXMLAttrY2 = u"y2"_ustr; +constexpr OUString aXMLAttrCX = u"cx"_ustr; +constexpr OUString aXMLAttrCY = u"cy"_ustr; +constexpr OUString aXMLAttrRX = u"rx"_ustr; +constexpr OUString aXMLAttrRY = u"ry"_ustr; +constexpr OUString aXMLAttrWidth = u"width"_ustr; +constexpr OUString aXMLAttrHeight = u"height"_ustr; +constexpr OUString aXMLAttrStrokeWidth = u"stroke-width"_ustr; +constexpr OUString aXMLAttrFill = u"fill"_ustr; +constexpr OUString aXMLAttrFontFamily = u"font-family"_ustr; +constexpr OUString aXMLAttrFontSize = u"font-size"_ustr; +constexpr OUString aXMLAttrFontStyle = u"font-style"_ustr; +constexpr OUString aXMLAttrFontWeight = u"font-weight"_ustr; +constexpr OUString aXMLAttrTextDecoration = u"text-decoration"_ustr; +constexpr OUString aXMLAttrXLinkHRef = u"xlink:href"_ustr; +constexpr OUString aXMLAttrGradientUnits = u"gradientUnits"_ustr; +constexpr OUString aXMLAttrOffset = u"offset"_ustr; +constexpr OUString aXMLAttrStopColor = u"stop-color"_ustr; +constexpr OUString aXMLAttrStrokeLinejoin = u"stroke-linejoin"_ustr; +constexpr OUString aXMLAttrStrokeLinecap = u"stroke-linecap"_ustr; vcl::PushFlags SVGContextHandler::getPushFlags() const @@ -217,8 +221,8 @@ void SVGAttributeWriter::AddPaintAttr( const Color& rLineColor, const Color& rFi void SVGAttributeWriter::AddGradientDef( const tools::Rectangle& rObjRect, const Gradient& rGradient, OUString& rGradientId ) { if( rObjRect.GetWidth() && rObjRect.GetHeight() && - ( rGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial || - rGradient.GetStyle() == GradientStyle::Radial || rGradient.GetStyle() == GradientStyle::Elliptical ) ) + ( rGradient.GetStyle() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::GradientStyle_AXIAL || + rGradient.GetStyle() == css::awt::GradientStyle_RADIAL || rGradient.GetStyle() == css::awt::GradientStyle_ELLIPTICAL ) ) { SvXMLElementExport aDesc( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true ); Color aStartColor( rGradient.GetStartColor() ), aEndColor( rGradient.GetEndColor() ); @@ -246,7 +250,7 @@ void SVGAttributeWriter::AddGradientDef( const tools::Rectangle& rObjRect, const std::unique_ptr< SvXMLElementExport > apGradient; OUString aColorStr; - if( rGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial ) + if( rGradient.GetStyle() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ) { tools::Polygon aLinePoly( 2 ); @@ -265,9 +269,9 @@ void SVGAttributeWriter::AddGradientDef( const tools::Rectangle& rObjRect, const // write stop values double fBorder = static_cast< double >( rGradient.GetBorder() ) * - ( ( rGradient.GetStyle() == GradientStyle::Axial ) ? 0.005 : 0.01 ); + ( ( rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ) ? 0.005 : 0.01 ); - ImplGetColorStr( ( rGradient.GetStyle() == GradientStyle::Axial ) ? aEndColor : aStartColor, aColorStr ); + ImplGetColorStr( ( rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ) ? aEndColor : aStartColor, aColorStr ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( fBorder ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr ); @@ -275,7 +279,7 @@ void SVGAttributeWriter::AddGradientDef( const tools::Rectangle& rObjRect, const SvXMLElementExport aDesc2( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true ); } - if( rGradient.GetStyle() == GradientStyle::Axial ) + if( rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ) { ImplGetColorStr( aStartColor, aColorStr ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( 0.5 ) ); @@ -286,7 +290,7 @@ void SVGAttributeWriter::AddGradientDef( const tools::Rectangle& rObjRect, const } } - if( rGradient.GetStyle() != GradientStyle::Axial ) + if( rGradient.GetStyle() != css::awt::GradientStyle_AXIAL ) fBorder = 0.0; ImplGetColorStr( aEndColor, aColorStr ); @@ -301,8 +305,7 @@ void SVGAttributeWriter::AddGradientDef( const tools::Rectangle& rObjRect, const { const double fCenterX = rObjRect.Left() + rObjRect.GetWidth() * rGradient.GetOfsX() * 0.01; const double fCenterY = rObjRect.Top() + rObjRect.GetHeight() * rGradient.GetOfsY() * 0.01; - const double fRadius = sqrt( static_cast< double >( rObjRect.GetWidth() ) * rObjRect.GetWidth() + - rObjRect.GetHeight() * rObjRect.GetHeight() ) * 0.5; + const double fRadius = std::hypot(rObjRect.GetWidth(), rObjRect.GetHeight()) * 0.5; mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrGradientUnits, "userSpaceOnUse" ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrCX, OUString::number( ImplRound( fCenterX ) ) ); @@ -633,8 +636,13 @@ sal_Int32 SVGTextWriter::setTextPosition(const GDIMetaFile& rMtf, size_t& nCurAc { // Text is found in the inner metafile. bConfigured = true; + + // nTextFound == 1 is only possible if the inner setTextPosition() had bEmpty == + // false, adjust our bEmpty accordingly. + bEmpty = false; + mrActionWriter.StartMask(pA->GetPoint(), pA->GetSize(), pA->GetGradient(), - nWriteFlags, &maTextOpacity); + nWriteFlags, pA->getSVGTransparencyColorStops(), &maTextOpacity); } } break; @@ -1099,7 +1107,19 @@ bool SVGTextWriter::nextParagraph() const OUString& rParagraphId = implGetValidIDFromInterface( Reference<XInterface>(xTextContent, UNO_QUERY) ); if( !rParagraphId.isEmpty() ) { - mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", rParagraphId ); + // if there is id for empty paragraph we need to create a empty text paragraph + Reference < XTextRange > xRange( xTextContent, UNO_QUERY_THROW ); + if ( xRange.is() && xRange->getString().isEmpty() ) + { + endTextParagraph(); + mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextParagraph" ); + mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", rParagraphId ); + mpTextParagraphElem.reset(new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS )); + } + else + { + mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", rParagraphId ); + } } return true; } @@ -1144,8 +1164,8 @@ bool SVGTextWriter::nextTextPortion() Reference < XTextField > xTextField( xRangePropSet->getPropertyValue( "TextField" ), UNO_QUERY ); if( xTextField.is() ) { - static const OUStringLiteral sServicePrefix(u"com.sun.star.text.textfield."); - static const OUStringLiteral sPresentationServicePrefix(u"com.sun.star.presentation.TextField."); + static constexpr OUString sServicePrefix(u"com.sun.star.text.textfield."_ustr); + static constexpr OUString sPresentationServicePrefix(u"com.sun.star.presentation.TextField."_ustr); Reference< XServiceInfo > xService( xTextField, UNO_QUERY ); const Sequence< OUString > aServices = xService->getSupportedServiceNames(); @@ -1178,8 +1198,8 @@ bool SVGTextWriter::nextTextPortion() #if OSL_DEBUG_LEVEL > 0 sInfo += "text field type: " + sFieldName + "; content: " + xTextField->getPresentation( /* show command: */ false ) + "; "; #endif - // This case handle Date or Time text field inserted by the user - // on both page/master page. It doesn't handle the standard Date/Time field. + // This case handles Date or Time text field inserted by the user + // on both page/master page. It doesn't handle the standard DateTime field. if( sFieldName == "DateTime" ) { Reference<XPropertySet> xTextFieldPropSet(xTextField, UNO_QUERY); @@ -1188,7 +1208,7 @@ bool SVGTextWriter::nextTextPortion() Reference<XPropertySetInfo> xPropSetInfo = xTextFieldPropSet->getPropertySetInfo(); if( xPropSetInfo.is() ) { - // The standard Date/Time field has no property. + // The standard DateTime field has no property. // Trying to get a property value on such field would cause a runtime exception. // So the hasPropertyByName check is needed. bool bIsFixed = true; @@ -1197,7 +1217,7 @@ bool SVGTextWriter::nextTextPortion() bool bIsDate = true; if( xPropSetInfo->hasPropertyByName("IsDate") && ( ( xTextFieldPropSet->getPropertyValue( "IsDate" ) ) >>= bIsDate ) ) { - msDateTimeType = OUString::createFromAscii( bIsDate ? "<date>" : "<time>" ); + msDateTimeType = OUString::createFromAscii( bIsDate ? "Date" : "Time" ); } } } @@ -1329,7 +1349,6 @@ void SVGTextWriter::startTextParagraph() mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextParagraph" ); } maParentFont = vcl::Font(); - addFontAttributes( /* isTexTContainer: */ true ); mpTextParagraphElem.reset(new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS )); if( !mbIsListLevelStyleImage ) @@ -1612,6 +1631,12 @@ void SVGTextWriter::writeTextPortion( const Point& rPos, continue; if( sContent == "\n" ) mbLineBreak = true; + else if (sContent == "\t") + { + // Need to emit position for the next text portion after a tab, otherwise the tab + // would appear as if it has 0 width. + mbPositioningNeeded = true; + } if( sContent.match( rText, nStartPos ) ) bNotSync = false; } @@ -1722,7 +1747,18 @@ void SVGTextWriter::implWriteTextPortion( const Point& rPos, if( mbIsPlaceholderShape ) { - mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "PlaceholderText" ); + OUString sClass = "PlaceholderText"; + // This case handles Date or Time text field inserted by the user + // on both page/master page. It doesn't handle the standard DateTime field. + if( !msDateTimeType.isEmpty() ) + { + sClass += " " + msDateTimeType; + } + else if( !msTextFieldType.isEmpty() ) + { + sClass += " " + msTextFieldType; + } + mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", sClass ); } addFontAttributes( /* isTexTContainer: */ false ); @@ -1753,28 +1789,11 @@ void SVGTextWriter::implWriteTextPortion( const Point& rPos, SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ); mrExport.GetDocHandler()->characters( msPageCount ); } - // This case handle Date or Time text field inserted by the user - // on both page/master page. It doesn't handle the standard Date/Time field. - else if ( !msDateTimeType.isEmpty() ) - { - 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 { + // Without the following attribute Google Chrome does not render leading spaces + mrExport.AddAttribute( XML_NAMESPACE_NONE, "style", "white-space: pre" ); + SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ); mrExport.GetDocHandler()->characters( rText ); } @@ -1796,7 +1815,8 @@ SVGActionWriter::SVGActionWriter( SVGExport& rExport, SVGFontExport& rFontExport mpVDev(VclPtr<VirtualDevice>::Create()), mbClipAttrChanged( false ), mbIsPlaceholderShape( false ), - mpEmbeddedBitmapsMap( nullptr ) + mpEmbeddedBitmapsMap( nullptr ), + mbIsPreview( false ) { mpVDev->EnableOutput( false ); maTargetMapMode = MapMode(MapUnit::Map100thMM); @@ -1873,8 +1893,8 @@ tools::PolyPolygon& SVGActionWriter::ImplMap( const tools::PolyPolygon& rPolyPol OUString SVGActionWriter::GetPathString( const tools::PolyPolygon& rPolyPoly, bool bLine ) { OUStringBuffer aPathData; - static const OUStringLiteral aBlank( u" " ); - static const OUStringLiteral aComma( u"," ); + static constexpr OUString aBlank( u" "_ustr ); + static constexpr OUString aComma( u","_ustr ); Point aPolyPoint; for( tools::Long i = 0, nCount = rPolyPoly.Count(); i < nCount; i++ ) @@ -2100,10 +2120,11 @@ void SVGActionWriter::ImplWriteShape( const SVGShapeDescriptor& rShape ) ImplMap( rShape.maShapePolyPoly, aPolyPoly ); const bool bLineOnly - = (rShape.maShapeFillColor == COL_TRANSPARENT) && (!rShape.mapShapeGradient); + = (rShape.maShapeFillColor == COL_TRANSPARENT) && (!rShape.moShapeGradient); tools::Rectangle aBoundRect( aPolyPoly.GetBoundRect() ); - maAttributeWriter.AddPaintAttr( rShape.maShapeLineColor, rShape.maShapeFillColor, &aBoundRect, rShape.mapShapeGradient.get() ); + maAttributeWriter.AddPaintAttr( rShape.maShapeLineColor, rShape.maShapeFillColor, &aBoundRect, + rShape.moShapeGradient ? &*rShape.moShapeGradient : nullptr ); if( !rShape.maId.isEmpty() ) mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, rShape.maId ); @@ -2293,12 +2314,12 @@ void SVGActionWriter::ImplWritePattern( const tools::PolyPolygon& rPolyPoly, void SVGActionWriter::ImplWriteGradientEx( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, - sal_uInt32 nWriteFlags) + sal_uInt32 nWriteFlags, const basegfx::BColorStops* pColorStops) { - if ( rGradient.GetStyle() == GradientStyle::Linear || - rGradient.GetStyle() == GradientStyle::Axial ) + if ( rGradient.GetStyle() == css::awt::GradientStyle_LINEAR || + rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ) { - ImplWriteGradientLinear( rPolyPoly, rGradient ); + ImplWriteGradientLinear( rPolyPoly, rGradient, pColorStops ); } else { @@ -2308,7 +2329,7 @@ void SVGActionWriter::ImplWriteGradientEx( const tools::PolyPolygon& rPolyPoly, void SVGActionWriter::ImplWriteGradientLinear( const tools::PolyPolygon& rPolyPoly, - const Gradient& rGradient ) + const Gradient& rGradient, const basegfx::BColorStops* pColorStops ) { if( !rPolyPoly.Count() ) return; @@ -2351,60 +2372,53 @@ void SVGActionWriter::ImplWriteGradientLinear( const tools::PolyPolygon& rPolyPo { SvXMLElementExport aElemLinearGradient( mrExport, XML_NAMESPACE_NONE, aXMLElemLinearGradient, true, true ); + basegfx::BColorStops aColorStops; - const Color aStartColor = ImplGetColorWithIntensity( rGradient.GetStartColor(), rGradient.GetStartIntensity() ); - const Color aEndColor = ImplGetColorWithIntensity( rGradient.GetEndColor(), rGradient.GetEndIntensity() ); - double fBorderOffset = rGradient.GetBorder() / 100.0; - const sal_uInt16 nSteps = rGradient.GetSteps(); - if( rGradient.GetStyle() == GradientStyle::Linear ) + if (nullptr != pColorStops && pColorStops->size() > 1) { - // Emulate non-smooth gradient - if( 0 < nSteps && nSteps < 100 ) - { - double fOffsetStep = ( 1.0 - fBorderOffset ) / static_cast<double>(nSteps); - for( sal_uInt16 i = 0; i < nSteps; i++ ) { - Color aColor = ImplGetGradientColor( aStartColor, aEndColor, i / static_cast<double>(nSteps) ); - ImplWriteGradientStop( aColor, fBorderOffset + ( i + 1 ) * fOffsetStep ); - aColor = ImplGetGradientColor( aStartColor, aEndColor, ( i + 1 ) / static_cast<double>(nSteps) ); - ImplWriteGradientStop( aColor, fBorderOffset + ( i + 1 ) * fOffsetStep ); - } - } - else - { - ImplWriteGradientStop( aStartColor, fBorderOffset ); - ImplWriteGradientStop( aEndColor, 1.0 ); - } + // if we got the real colr stops, use them. That way we are + // now capable in the SVG export to export real multi color gradients + aColorStops = *pColorStops; } else { - fBorderOffset /= 2; - // Emulate non-smooth gradient - if( 0 < nSteps && nSteps < 100 ) - { - double fOffsetStep = ( 0.5 - fBorderOffset ) / static_cast<double>(nSteps); - // Upper half - for( sal_uInt16 i = 0; i < nSteps; i++ ) - { - Color aColor = ImplGetGradientColor( aEndColor, aStartColor, i / static_cast<double>(nSteps) ); - ImplWriteGradientStop( aColor, fBorderOffset + i * fOffsetStep ); - aColor = ImplGetGradientColor( aEndColor, aStartColor, (i + 1 ) / static_cast<double>(nSteps) ); - ImplWriteGradientStop( aColor, fBorderOffset + i * fOffsetStep ); - } - // Lower half - for( sal_uInt16 i = 0; i < nSteps; i++ ) - { - Color aColor = ImplGetGradientColor( aStartColor, aEndColor, i / static_cast<double>(nSteps) ); - ImplWriteGradientStop( aColor, 0.5 + (i + 1) * fOffsetStep ); - aColor = ImplGetGradientColor( aStartColor, aEndColor, (i + 1 ) / static_cast<double>(nSteps) ); - ImplWriteGradientStop( aColor, 0.5 + (i + 1) * fOffsetStep ); - } - } - else - { - ImplWriteGradientStop( aEndColor, fBorderOffset ); - ImplWriteGradientStop( aStartColor, 0.5 ); - ImplWriteGradientStop( aEndColor, 1.0 - fBorderOffset ); - } + // else create color stops with 'old' start/endColor + aColorStops.emplace_back(0.0, rGradient.GetStartColor().getBColor()); + aColorStops.emplace_back(1.0, rGradient.GetEndColor().getBColor()); + } + + // create a basegfx::BGradient with the info to be able to directly + // use the tooling it offers + basegfx::BGradient aGradient( + aColorStops, + rGradient.GetStyle(), + rGradient.GetAngle(), + rGradient.GetOfsX(), + rGradient.GetOfsY(), + rGradient.GetBorder(), + rGradient.GetStartIntensity(), + rGradient.GetEndIntensity(), + rGradient.GetSteps()); + + // apply Start/EndIntensity to the whole color stops - if used + aGradient.tryToApplyStartEndIntensity(); + + // apply border to color stops - if used + aGradient.tryToApplyBorder(); + + // convert from 'axial' to linear - if needed and used + aGradient.tryToApplyAxial(); + + // apply 'Steps' as hard gradient stops - if used + aGradient.tryToApplySteps(); + + // write prepared gradient stops + for (const auto& rCand : aGradient.GetColorStops()) + { + ImplWriteGradientStop( + Color(rCand.getStopColor()), + rCand.getStopOffset()); + // aStartColor, fBorderOffset ); } } } @@ -2442,28 +2456,9 @@ Color SVGActionWriter::ImplGetColorWithIntensity( const Color& rColor, } -Color SVGActionWriter::ImplGetGradientColor( const Color& rStartColor, - const Color& rEndColor, - double fOffset ) -{ - tools::Long nRedStep = rEndColor.GetRed() - rStartColor.GetRed(); - tools::Long nNewRed = rStartColor.GetRed() + static_cast<tools::Long>( nRedStep * fOffset ); - nNewRed = ( nNewRed < 0 ) ? 0 : ( nNewRed > 0xFF) ? 0xFF : nNewRed; - - tools::Long nGreenStep = rEndColor.GetGreen() - rStartColor.GetGreen(); - tools::Long nNewGreen = rStartColor.GetGreen() + static_cast<tools::Long>( nGreenStep * fOffset ); - nNewGreen = ( nNewGreen < 0 ) ? 0 : ( nNewGreen > 0xFF) ? 0xFF : nNewGreen; - - tools::Long nBlueStep = rEndColor.GetBlue() - rStartColor.GetBlue(); - tools::Long nNewBlue = rStartColor.GetBlue() + static_cast<tools::Long>( nBlueStep * fOffset ); - nNewBlue = ( nNewBlue < 0 ) ? 0 : ( nNewBlue > 0xFF) ? 0xFF : nNewBlue; - - return Color( static_cast<sal_uInt8>(nNewRed), static_cast<sal_uInt8>(nNewGreen), static_cast<sal_uInt8>(nNewBlue) ); -} - void SVGActionWriter::StartMask(const Point& rDestPt, const Size& rDestSize, const Gradient& rGradient, sal_uInt32 nWriteFlags, - OUString* pTextFillOpacity) + const basegfx::BColorStops* pColorStops, OUString* pTextFillOpacity) { OUString aStyle; if (rGradient.GetStartColor() == rGradient.GetEndColor()) @@ -2504,7 +2499,19 @@ void SVGActionWriter::StartMask(const Point& rDestPt, const Size& rDestSize, aGradient.SetEndColor(aTmpColor); aGradient.SetEndIntensity(nTmpIntensity); - ImplWriteGradientEx(aPolyPolygon, aGradient, nWriteFlags); + // tdf#155479 prep local ColorStops. The code above + // implies that the ColorStops need to be reversed, + // so do so & use change of local ptr to represent this + basegfx::BColorStops aLocalColorStops; + + if (nullptr != pColorStops) + { + aLocalColorStops = *pColorStops; + aLocalColorStops.reverseColorStops(); + pColorStops = &aLocalColorStops; + } + + ImplWriteGradientEx(aPolyPolygon, aGradient, nWriteFlags, pColorStops); } } @@ -2514,7 +2521,7 @@ void SVGActionWriter::StartMask(const Point& rDestPt, const Size& rDestSize, } void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, const Size& rDestSize, - const Gradient& rGradient, sal_uInt32 nWriteFlags) + const Gradient& rGradient, sal_uInt32 nWriteFlags, const basegfx::BColorStops* pColorStops) { Point aSrcPt(rMtf.GetPrefMapMode().GetOrigin()); const Size aSrcSize(rMtf.GetPrefSize()); @@ -2541,7 +2548,7 @@ void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, con std::unique_ptr<SvXMLElementExport> pElemG; if (!maTextWriter.hasTextOpacity()) { - StartMask(rDestPt, rDestSize, rGradient, nWriteFlags); + StartMask(rDestPt, rDestSize, rGradient, nWriteFlags, pColorStops); pElemG.reset( new SvXMLElementExport(mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true)); } @@ -2554,7 +2561,7 @@ void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, con void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText, - o3tl::span<const sal_Int32> pDXArray, tools::Long nWidth ) + KernArraySpan pDXArray, tools::Long nWidth ) { const FontMetric aMetric( mpVDev->GetFontMetric() ); @@ -2645,7 +2652,7 @@ void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText, void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText, - o3tl::span<const sal_Int32> pDXArray, tools::Long nWidth, + KernArraySpan pDXArray, tools::Long nWidth, Color aTextColor ) { sal_Int32 nLen = rText.getLength(); @@ -2662,18 +2669,17 @@ void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText, ImplMap( rPos, aPos ); - std::vector<sal_Int32> aTmpArray(nLen); + KernArray aTmpArray; // get text sizes if( !pDXArray.empty() ) { aNormSize = Size( mpVDev->GetTextWidth( rText ), 0 ); - memcpy(aTmpArray.data(), pDXArray.data(), nLen * sizeof(sal_Int32)); + aTmpArray.assign(pDXArray); } else { aNormSize = Size( mpVDev->GetTextArray( rText, &aTmpArray ), 0 ); } - sal_Int32* pDX = aTmpArray.data(); // if text is rotated, set transform matrix at new g element if( rFont.GetOrientation() ) @@ -2721,7 +2727,7 @@ void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText, { if( nLen > 1 ) { - aNormSize.setWidth( pDX[ nLen - 2 ] + mpVDev->GetTextWidth( OUString(rText[nLen - 1]) ) ); + aNormSize.setWidth( aTmpArray[ nLen - 2 ] + mpVDev->GetTextWidth( OUString(rText[nLen - 1]) ) ); if( nWidth && aNormSize.Width() && ( nWidth != aNormSize.Width() ) ) { @@ -2729,7 +2735,7 @@ void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText, const double fFactor = static_cast<double>(nWidth) / aNormSize.Width(); for( i = 0; i < ( nLen - 1 ); i++ ) - pDX[ i ] = FRound( pDX[ i ] * fFactor ); + aTmpArray.set(i, FRound(aTmpArray[i] * fFactor)); } else { @@ -2765,7 +2771,7 @@ void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText, if( bCont ) { // #118796# do NOT access pDXArray, it may be zero (!) - sal_Int32 nDXWidth = pDX[ nCurPos - 1 ]; + sal_Int32 nDXWidth = aTmpArray[ nCurPos - 1 ]; nDXWidth = ImplMap( nDXWidth ); nX = aPos.X() + nDXWidth; } @@ -2804,7 +2810,7 @@ void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText, aPoly[ 2 ].setX( aPoly[ 1 ].X() ); aPoly[ 2 ].setY( aPoly[ 0 ].Y() + nLineHeight - 1 ); aPoly[ 3 ].setX( aPoly[ 0 ].X() ); aPoly[ 3 ].setY( aPoly[ 2 ].Y() ); - ImplWritePolyPolygon( aPoly, false ); + ImplWritePolyPolygon( tools::PolyPolygon(aPoly), false ); } if( rFont.GetUnderline() ) @@ -2816,7 +2822,7 @@ void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText, aPoly[ 2 ].setX( aPoly[ 1 ].X() ); aPoly[ 2 ].setY( aPoly[ 0 ].Y() + nLineHeight - 1 ); aPoly[ 3 ].setX( aPoly[ 0 ].X() ); aPoly[ 3 ].setY( aPoly[ 2 ].Y() ); - ImplWritePolyPolygon( aPoly, false ); + ImplWritePolyPolygon( tools::PolyPolygon(aPoly), false ); } } @@ -2909,7 +2915,7 @@ void SVGActionWriter::ImplWriteBmp( const BitmapEx& rBmpEx, if (aGraphic.GetType() == GraphicType::Bitmap) { const BitmapEx& rGraphicBitmap = aGraphic.GetBitmapExRef(); - if (rGraphicBitmap.GetChecksum() == rBmpEx.GetChecksum()) + if (rGraphicBitmap == rBmpEx) { bool bPNG = false; GfxLink aGfxLink = aGraphic.GetGfxLink(); @@ -2930,7 +2936,27 @@ void SVGActionWriter::ImplWriteBmp( const BitmapEx& rBmpEx, } } - if( !(bCached || GraphicConverter::Export( aOStm, rBmpEx, ConvertDataFormat::PNG ) == ERRCODE_NONE) ) + const BitmapEx* pBitmap = &rBmpEx; + std::unique_ptr<BitmapEx> pNewBitmap; + + // for preview we generate downscaled images (1280x720 max) + if (mbIsPreview) + { + Size aSize = rBmpEx.GetSizePixel(); + double fX = static_cast<double>(aSize.getWidth()) / 1280; + double fY = static_cast<double>(aSize.getHeight()) / 720; + double fFactor = fX > fY ? fX : fY; + if (fFactor > 1.0) + { + aSize.setWidth(aSize.getWidth() / fFactor); + aSize.setHeight(aSize.getHeight() / fFactor); + pNewBitmap = std::make_unique<BitmapEx>(rBmpEx); + pNewBitmap->Scale(aSize); + pBitmap = pNewBitmap.get(); + } + } + + if( !(bCached || GraphicConverter::Export( aOStm, *pBitmap, ConvertDataFormat::PNG ) == ERRCODE_NONE) ) return; Point aPt; @@ -2955,12 +2981,49 @@ void SVGActionWriter::ImplWriteBmp( const BitmapEx& rBmpEx, mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrWidth, OUString::number( aSz.Width() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrHeight, OUString::number( aSz.Height() ) ); - // the image must be scaled to aSz in a non-uniform way - mrExport.AddAttribute( XML_NAMESPACE_NONE, "preserveAspectRatio", "none" ); + // If we have a media object (a video), export the video. + // Also, use the image generated above as the video poster (thumbnail). + SdrMediaObj* pMediaObj + = pShape ? dynamic_cast<SdrMediaObj*>(SdrObject::getSdrObjectFromXShape(*pShape)) : nullptr; + const bool embedVideo = (pMediaObj && !pMediaObj->getTempURL().isEmpty()); + + if (!embedVideo) + { + // the image must be scaled to aSz in a non-uniform way + mrExport.AddAttribute(XML_NAMESPACE_NONE, "preserveAspectRatio", "none"); + + mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, aBuffer.makeStringAndClear()); - mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, aBuffer.makeStringAndClear() ); + SvXMLElementExport aElem(mrExport, XML_NAMESPACE_NONE, "image", true, true); + } + else { - SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "image", true, true ); + // <foreignObject xmlns="http://www.w3.org/2000/svg" overflow="visible" width="499.6" height="374.33333333333337" x="705" y="333"> + // <body xmlns="http://www.w3.org/1999/xhtml"> + // <video controls="controls" width="499.6" height="374.33333333333337"> + // <source src="file:///tmp/abcdef.mp4" type="video/mp4"> + // </video> + // </body> + // </foreignObject> + mrExport.AddAttribute(XML_NAMESPACE_NONE, "xmlns", "http://www.w3.org/2000/svg"); + mrExport.AddAttribute(XML_NAMESPACE_NONE, "overflow", "visible"); + SvXMLElementExport aForeignObject(mrExport, XML_NAMESPACE_NONE, "foreignObject", true, + true); + mrExport.AddAttribute(XML_NAMESPACE_NONE, "xmlns", "http://www.w3.org/1999/xhtml"); + SvXMLElementExport aBody(mrExport, XML_NAMESPACE_NONE, "body", true, true); + + mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrWidth, OUString::number(aSz.Width())); + mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrHeight, OUString::number(aSz.Height())); + mrExport.AddAttribute(XML_NAMESPACE_NONE, "autoplay", "autoplay"); + mrExport.AddAttribute(XML_NAMESPACE_NONE, "controls", "controls"); + mrExport.AddAttribute(XML_NAMESPACE_NONE, "loop", "loop"); + mrExport.AddAttribute(XML_NAMESPACE_NONE, "preload", "auto"); + mrExport.AddAttribute(XML_NAMESPACE_NONE, "poster", aBuffer.makeStringAndClear()); + SvXMLElementExport aVideo(mrExport, XML_NAMESPACE_NONE, "video", true, true); + + mrExport.AddAttribute(XML_NAMESPACE_NONE, "src", pMediaObj->getTempURL()); + mrExport.AddAttribute(XML_NAMESPACE_NONE, "type", "video/mp4"); //FIXME: set mime type. + SvXMLElementExport aSource(mrExport, XML_NAMESPACE_NONE, "source", true, true); } } @@ -3025,10 +3088,10 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, sal_uInt16 sz = static_cast<sal_uInt16>((pA->GetDataSize()) / 2); if (sz) { - sType.append("; "); - sType.append( - reinterpret_cast<sal_Unicode const*>(pData), - sz); + sType.append(OUString::Concat("; ") + + std::u16string_view( + reinterpret_cast<sal_Unicode const*>(pData), + sz)); } } } @@ -3161,7 +3224,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, if( aPoly.GetSize() ) { maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() ); - ImplWritePolyPolygon( aPoly, false ); + ImplWritePolyPolygon( tools::PolyPolygon(aPoly), false ); } } } @@ -3178,7 +3241,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, { maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), COL_TRANSPARENT ); ImplAddLineAttr( pA->GetLineInfo() ); - ImplWritePolyPolygon( rPoly, true ); + ImplWritePolyPolygon( tools::PolyPolygon(rPoly), true ); } } } @@ -3208,7 +3271,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, const tools::Polygon aRectPoly( pA->GetRect() ); const tools::PolyPolygon aRectPolyPoly( aRectPoly ); - ImplWriteGradientEx( aRectPolyPoly, pA->GetGradient(), nWriteFlags ); + ImplWriteGradientEx( aRectPolyPoly, pA->GetGradient(), nWriteFlags, nullptr ); } } break; @@ -3218,7 +3281,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaGradientExAction* pA = static_cast<const MetaGradientExAction*>(pAction); - ImplWriteGradientEx( pA->GetPolyPolygon(), pA->GetGradient(), nWriteFlags ); + ImplWriteGradientEx( pA->GetPolyPolygon(), pA->GetGradient(), nWriteFlags, nullptr ); } } break; @@ -3244,7 +3307,11 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, { Color aNewLineColor( mpVDev->GetLineColor() ), aNewFillColor( mpVDev->GetFillColor() ); - aNewLineColor.SetAlpha( 255 - sal::static_int_cast<sal_uInt8>( FRound( pA->GetTransparence() * 2.55 ) ) ); + // tdf#149800 do not change transparency of fully transparent + // i.e. invisible line, because it makes it visible, + // resulting an extra line behind the normal shape line + if ( aNewLineColor.GetAlpha() > 0 ) + aNewLineColor.SetAlpha( 255 - sal::static_int_cast<sal_uInt8>( FRound( pA->GetTransparence() * 2.55 ) ) ); aNewFillColor.SetAlpha( 255 - sal::static_int_cast<sal_uInt8>( FRound( pA->GetTransparence() * 2.55 ) ) ); maAttributeWriter.AddPaintAttr( aNewLineColor, aNewFillColor ); @@ -3261,7 +3328,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction); GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() ); ImplWriteMask( aTmpMtf, pA->GetPoint(), pA->GetSize(), - pA->GetGradient(), nWriteFlags ); + pA->GetGradient(), nWriteFlags, pA->getSVGTransparencyColorStops() ); } } break; @@ -3295,7 +3362,62 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, { const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction); - if( ( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN") ) && + if (pA->GetComment().equalsIgnoreAsciiCase("BGRAD_SEQ_BEGIN")) + { + // detect and use the new BGRAD_SEQ_* metafile comment actions + const MetaGradientExAction* pGradAction(nullptr); + bool bDone(false); + + while (!bDone && (++nCurAction < nCount)) + { + pAction = rMtf.GetAction(nCurAction); + + if (MetaActionType::GRADIENTEX == pAction->GetType()) + { + // remember the 'paint' data action + pGradAction = static_cast<const MetaGradientExAction*>(pAction); + } + else if (MetaActionType::COMMENT == pAction->GetType() + && static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("BGRAD_SEQ_END")) + { + // end action found + bDone = true; + } + } + + if (nullptr != pGradAction) + { + // we have a complete actions sequence of BGRAD_SEQ_*, so we can now + // read the correct color stops here + basegfx::BColorStops aColorStops; + SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ); + VersionCompatRead aCompat(aMemStm); + sal_uInt16 nTmp(0); + double fOff, fR, fG, fB; + aMemStm.ReadUInt16( nTmp ); + + const size_t nMaxPossibleEntries = aMemStm.remainingSize() / 4 * sizeof(double); + if (nTmp > nMaxPossibleEntries) + { + SAL_WARN("filter.svg", "gradient record claims to have: " << nTmp << " entries, but only " << nMaxPossibleEntries << " possible, clamping"); + nTmp = nMaxPossibleEntries; + } + + for (sal_uInt16 a(0); a < nTmp; a++) + { + aMemStm.ReadDouble(fOff); + aMemStm.ReadDouble(fR); + aMemStm.ReadDouble(fG); + aMemStm.ReadDouble(fB); + + aColorStops.emplace_back(fOff, basegfx::BColor(fR, fG, fB)); + } + + // export with real Color Stops + ImplWriteGradientEx(pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), nWriteFlags, &aColorStops); + } + } + else if( ( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN") ) && ( nWriteFlags & SVGWRITER_WRITE_FILL ) ) { const MetaGradientExAction* pGradAction = nullptr; @@ -3316,7 +3438,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, } if( pGradAction ) - ImplWriteGradientEx( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), nWriteFlags ); + ImplWriteGradientEx( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), nWriteFlags, nullptr ); } else if( ( pA->GetComment().equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN") ) && ( nWriteFlags & SVGWRITER_WRITE_FILL ) && !( nWriteFlags & SVGWRITER_NO_SHAPE_COMMENTS ) && @@ -3361,7 +3483,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, if( bGradient ) { // step through following actions until the first Gradient/GradientEx action is found - while (!mapCurShape->mapShapeGradient && bSkip + while (!mapCurShape->moShapeGradient && bSkip && (++nCurAction < nCount)) { pAction = rMtf.GetAction( nCurAction ); @@ -3374,13 +3496,13 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, } else if( pAction->GetType() == MetaActionType::GRADIENTEX ) { - mapCurShape->mapShapeGradient.reset( new Gradient( - static_cast< const MetaGradientExAction* >( pAction )->GetGradient() ) ); + mapCurShape->moShapeGradient.emplace( + static_cast< const MetaGradientExAction* >( pAction )->GetGradient() ); } else if( pAction->GetType() == MetaActionType::GRADIENT ) { - mapCurShape->mapShapeGradient.reset( new Gradient( - static_cast< const MetaGradientAction* >( pAction )->GetGradient() ) ); + mapCurShape->moShapeGradient.emplace( + static_cast< const MetaGradientAction* >( pAction )->GetGradient() ); } } } @@ -3442,7 +3564,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++); } - mapCurShape->maShapePolyPoly = aPoly; + mapCurShape->maShapePolyPoly = tools::PolyPolygon(aPoly); } mapCurShape->maShapeLineColor = mpVDev->GetLineColor(); @@ -3490,7 +3612,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, } } - if(mapCurShape && mapCurShape->maShapePolyPoly.Count() && (aStartArrow.Count() || aEndArrow.Count())) + if (mapCurShape->maShapePolyPoly.Count() && (aStartArrow.Count() || aEndArrow.Count())) { ImplWriteShape( *mapCurShape ); @@ -3666,7 +3788,7 @@ void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, // 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, ' ') ); + OUString sRefId = "#" + OUString::fromUtf8( o3tl::getToken(sComment, 1, ' ') ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId ); SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true ); |