/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include "svgfilter.hxx" #include "svgfontexport.hxx" #include "svgwriter.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 { if (maStateStack.empty()) return vcl::PushFlags::NONE; const PartialState& rPartialState = maStateStack.top(); return rPartialState.meFlags; } SVGState& SVGContextHandler::getCurrentState() { return maCurrentState; } void SVGContextHandler::pushState( vcl::PushFlags eFlags ) { PartialState aPartialState; aPartialState.meFlags = eFlags; if (eFlags & vcl::PushFlags::FONT) { aPartialState.setFont( maCurrentState.aFont ); } if (eFlags & vcl::PushFlags::CLIPREGION) { aPartialState.mnRegionClipPathId = maCurrentState.nRegionClipPathId; } maStateStack.push( std::move(aPartialState) ); } void SVGContextHandler::popState() { if (maStateStack.empty()) return; const PartialState& rPartialState = maStateStack.top(); vcl::PushFlags eFlags = rPartialState.meFlags; if (eFlags & vcl::PushFlags::FONT) { maCurrentState.aFont = rPartialState.getFont( vcl::Font() ); } if (eFlags & vcl::PushFlags::CLIPREGION) { maCurrentState.nRegionClipPathId = rPartialState.mnRegionClipPathId; } maStateStack.pop(); } SVGAttributeWriter::SVGAttributeWriter( SVGExport& rExport, SVGFontExport& rFontExport, SVGState& rCurState ) : mrExport( rExport ) , mrFontExport( rFontExport ) , mrCurrentState( rCurState ) { } SVGAttributeWriter::~SVGAttributeWriter() { } double SVGAttributeWriter::ImplRound( double fValue ) { return floor( fValue * pow( 10.0, 3 ) + 0.5 ) / pow( 10.0, 3 ); } void SVGAttributeWriter::ImplGetColorStr( const Color& rColor, OUString& rColorStr ) { if( rColor.GetAlpha() == 0 ) rColorStr = "none"; else { rColorStr = "rgb(" + OUString::number(rColor.GetRed()) + "," + OUString::number(rColor.GetGreen()) + "," + OUString::number(rColor.GetBlue()) + ")"; } } void SVGAttributeWriter::AddColorAttr( const OUString& pColorAttrName, const OUString& pColorOpacityAttrName, const Color& rColor ) { OUString aColor, aColorOpacity; ImplGetColorStr( rColor, aColor ); if( rColor.GetAlpha() < 255 && rColor.GetAlpha() > 0 ) aColorOpacity = OUString::number( ImplRound( rColor.GetAlpha() / 255.0 ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, pColorAttrName, aColor ); if( !aColorOpacity.isEmpty() && mrExport.IsUseOpacity() ) mrExport.AddAttribute( XML_NAMESPACE_NONE, pColorOpacityAttrName, aColorOpacity ); } void SVGAttributeWriter::AddPaintAttr( const Color& rLineColor, const Color& rFillColor, const tools::Rectangle* pObjBoundRect, const Gradient* pFillGradient ) { // Fill if( pObjBoundRect && pFillGradient ) { OUString aGradientId; AddGradientDef( *pObjBoundRect, *pFillGradient, aGradientId ); if( !aGradientId.isEmpty() ) { OUString aGradientURL = "url(#" + aGradientId + ")"; mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFill, aGradientURL ); } } else AddColorAttr( aXMLAttrFill, "fill-opacity", rFillColor ); // Stroke AddColorAttr( "stroke", "stroke-opacity", rLineColor ); } void SVGAttributeWriter::AddGradientDef( const tools::Rectangle& rObjRect, const Gradient& rGradient, OUString& rGradientId ) { if( rObjRect.GetWidth() && rObjRect.GetHeight() && ( 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() ); Degree10 nAngle = rGradient.GetAngle() % 3600_deg10; Point aObjRectCenter( rObjRect.Center() ); tools::Polygon aPoly( rObjRect ); static sal_Int32 nCurGradientId = 1; aPoly.Rotate( aObjRectCenter, nAngle ); tools::Rectangle aRect( aPoly.GetBoundRect() ); // adjust start/end colors with intensities aStartColor.SetRed( static_cast( ( aStartColor.GetRed() * rGradient.GetStartIntensity() ) / 100 ) ); aStartColor.SetGreen( static_cast( ( aStartColor.GetGreen() * rGradient.GetStartIntensity() ) / 100 ) ); aStartColor.SetBlue( static_cast( ( aStartColor.GetBlue() * rGradient.GetStartIntensity() ) / 100 ) ); aEndColor.SetRed( static_cast( ( aEndColor.GetRed() * rGradient.GetEndIntensity() ) / 100 ) ); aEndColor.SetGreen( static_cast( ( aEndColor.GetGreen() * rGradient.GetEndIntensity() ) / 100 ) ); aEndColor.SetBlue( static_cast( ( aEndColor.GetBlue() * rGradient.GetEndIntensity() ) / 100 ) ); rGradientId = "Gradient_" + OUString::number( nCurGradientId++ ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, rGradientId ); { std::unique_ptr< SvXMLElementExport > apGradient; OUString aColorStr; if( rGradient.GetStyle() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ) { tools::Polygon aLinePoly( 2 ); aLinePoly[ 0 ] = Point( aObjRectCenter.X(), aRect.Top() ); aLinePoly[ 1 ] = Point( aObjRectCenter.X(), aRect.Bottom() ); aLinePoly.Rotate( aObjRectCenter, nAngle ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrGradientUnits, "userSpaceOnUse" ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX1, OUString::number( aLinePoly[ 0 ].X() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY1, OUString::number( aLinePoly[ 0 ].Y() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX2, OUString::number( aLinePoly[ 1 ].X() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY2, OUString::number( aLinePoly[ 1 ].Y() ) ); apGradient.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemLinearGradient, true, true ) ); // write stop values double fBorder = static_cast< double >( rGradient.GetBorder() ) * ( ( rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ) ? 0.005 : 0.01 ); 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 ); { SvXMLElementExport aDesc2( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true ); } if( rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ) { ImplGetColorStr( aStartColor, aColorStr ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( 0.5 ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr ); { SvXMLElementExport aDesc3( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true ); } } if( rGradient.GetStyle() != css::awt::GradientStyle_AXIAL ) fBorder = 0.0; ImplGetColorStr( aEndColor, aColorStr ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( ImplRound( 1.0 - fBorder ) ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr ); { SvXMLElementExport aDesc4( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true ); } } else { 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 = 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 ) ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrCY, OUString::number( ImplRound( fCenterY ) ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, "r", OUString::number( ImplRound( fRadius ) ) ); apGradient.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, "radialGradient", true, true ) ); // write stop values ImplGetColorStr( aEndColor, aColorStr ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( 0.0 ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr ); { SvXMLElementExport aDesc5( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true ); } ImplGetColorStr( aStartColor, aColorStr ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( ImplRound( 1.0 - rGradient.GetBorder() * 0.01 ) ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr ); { SvXMLElementExport aDesc6( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true ); } } } } else rGradientId.clear(); } void SVGAttributeWriter::SetFontAttr( const vcl::Font& rFont ) { vcl::Font& rCurFont = mrCurrentState.aFont; if( rFont == rCurFont ) return; OUString aFontStyle; sal_Int32 nFontWeight; rCurFont = rFont; // Font Family setFontFamily(); // Font Size mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontSize, OUString::number( rFont.GetFontHeight() ) + "px" ); // Font Style if( rFont.GetItalic() != ITALIC_NONE ) { if( rFont.GetItalic() == ITALIC_OBLIQUE ) aFontStyle = "oblique"; else aFontStyle = "italic"; } else aFontStyle = "normal"; mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontStyle, aFontStyle ); // Font Weight switch( rFont.GetWeight() ) { case WEIGHT_THIN: nFontWeight = 100; break; case WEIGHT_ULTRALIGHT: nFontWeight = 200; break; case WEIGHT_LIGHT: nFontWeight = 300; break; case WEIGHT_SEMILIGHT: nFontWeight = 400; break; case WEIGHT_NORMAL: nFontWeight = 400; break; case WEIGHT_MEDIUM: nFontWeight = 500; break; case WEIGHT_SEMIBOLD: nFontWeight = 600; break; case WEIGHT_BOLD: nFontWeight = 700; break; case WEIGHT_ULTRABOLD: nFontWeight = 800; break; case WEIGHT_BLACK: nFontWeight = 900; break; default: nFontWeight = 400; break; } mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontWeight, OUString::number( nFontWeight ) ); if( mrExport.IsUseNativeTextDecoration() ) { OUString aTextDecoration; if( rFont.GetUnderline() != LINESTYLE_NONE || rFont.GetStrikeout() != STRIKEOUT_NONE ) { if( rFont.GetUnderline() != LINESTYLE_NONE ) aTextDecoration = "underline "; if( rFont.GetStrikeout() != STRIKEOUT_NONE ) aTextDecoration += "line-through "; } else aTextDecoration = "none"; mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTextDecoration, aTextDecoration ); } startFontSettings(); } void SVGAttributeWriter::startFontSettings() { endFontSettings(); if( mrExport.IsUsePositionedCharacters() ) { mpElemFont.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ) ); } else { mpElemFont.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, true, true ) ); } } void SVGAttributeWriter::endFontSettings() { mpElemFont.reset(); } void SVGAttributeWriter::setFontFamily() { vcl::Font& rCurFont = mrCurrentState.aFont; if( mrExport.IsUsePositionedCharacters() ) { mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontFamily, mrFontExport.GetMappedFontName( rCurFont.GetFamilyName() ) ); } else { const OUString& rsFontName = rCurFont.GetFamilyName(); OUString sFontFamily( rsFontName.getToken( 0, ';' ) ); FontPitch ePitch = rCurFont.GetPitch(); if( ePitch == PITCH_FIXED ) { sFontFamily += ", monospace"; } else { FontFamily eFamily = rCurFont.GetFamilyType(); if( eFamily == FAMILY_ROMAN ) sFontFamily += ", serif"; else if( eFamily == FAMILY_SWISS ) sFontFamily += ", sans-serif"; } mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontFamily, sFontFamily ); } } SVGTextWriter::SVGTextWriter(SVGExport& rExport, SVGAttributeWriter& rAttributeWriter, SVGActionWriter& rActionWriter) : mrExport( rExport ), mrAttributeWriter( rAttributeWriter ), mrActionWriter(rActionWriter), mpVDev( nullptr ), mbIsTextShapeStarted( false ), mpTextEmbeddedBitmapMtf( nullptr ), mpTargetMapMode( nullptr ), mnLeftTextPortionLength( 0 ), maTextPos(0,0), mnTextWidth(0), mbPositioningNeeded( false ), mbIsNewListItem( false ), meNumberingType(0), mcBulletChar(0), mbIsListLevelStyleImage( false ), mbLineBreak( false ), mbIsURLField( false ), mbIsPlaceholderShape( false ) { } SVGTextWriter::~SVGTextWriter() { endTextParagraph(); } void SVGTextWriter::implRegisterInterface( const Reference< XInterface >& rxIf ) { if( rxIf.is() ) mrExport.getInterfaceToIdentifierMapper().registerReference( rxIf ); } const OUString & SVGTextWriter::implGetValidIDFromInterface( const Reference< XInterface >& rxIf ) { return mrExport.getInterfaceToIdentifierMapper().getIdentifier( rxIf ); } void SVGTextWriter::implMap( const Size& rSz, Size& rDstSz ) const { if( mpVDev && mpTargetMapMode ) rDstSz = OutputDevice::LogicToLogic( rSz, mpVDev->GetMapMode(), *mpTargetMapMode ); else OSL_FAIL( "SVGTextWriter::implMap: invalid virtual device or map mode." ); } void SVGTextWriter::implMap( const Point& rPt, Point& rDstPt ) const { if( mpVDev && mpTargetMapMode ) rDstPt = OutputDevice::LogicToLogic( rPt, mpVDev->GetMapMode(), *mpTargetMapMode ); else OSL_FAIL( "SVGTextWriter::implMap: invalid virtual device or map mode." ); } void SVGTextWriter::implSetCurrentFont() { if( mpVDev ) { maCurrentFont = mpVDev->GetFont(); Size aSz; implMap( Size( 0, maCurrentFont.GetFontHeight() ), aSz ); maCurrentFont.SetFontHeight( aSz.Height() ); } else { OSL_FAIL( "SVGTextWriter::implSetCorrectFontHeight: invalid virtual device." ); } } template< typename SubType > bool SVGTextWriter::implGetTextPosition( const MetaAction* pAction, Point& raPos, bool& rbEmpty ) { const SubType* pA = static_cast(pAction); sal_uInt16 nLength = pA->GetLen(); rbEmpty = ( nLength == 0 ); if( !rbEmpty ) { raPos = pA->GetPoint(); return true; } return false; } template<> bool SVGTextWriter::implGetTextPosition( const MetaAction* pAction, Point& raPos, bool& rbEmpty ) { const MetaTextRectAction* pA = static_cast(pAction); sal_uInt16 nLength = pA->GetText().getLength(); rbEmpty = ( nLength == 0 ); if( !rbEmpty ) { raPos = pA->GetRect().TopLeft(); return true; } return false; } template< typename SubType > bool SVGTextWriter::implGetTextPositionFromBitmap( const MetaAction* pAction, Point& raPos, bool& rbEmpty ) { const SubType* pA = static_cast(pAction); raPos = pA->GetPoint(); rbEmpty = false; return true; } /** setTextPosition * Set the start position of the next line of text. In case no text is found * the current action index is updated to the index value we reached while * searching for text. * * @returns {sal_Int32} * -2 if no text found and end of line is reached * -1 if no text found and end of paragraph is reached * 0 if no text found and end of text shape is reached * 1 if text found! */ sal_Int32 SVGTextWriter::setTextPosition(const GDIMetaFile& rMtf, size_t& nCurAction, sal_uInt32 nWriteFlags) { Point aPos; size_t nCount = rMtf.GetActionSize(); bool bEOL = false; bool bEOP = false; bool bETS = false; bool bConfigured = false; bool bEmpty = true; size_t nActionIndex = nCurAction + 1; for( ; nActionIndex < nCount; ++nActionIndex ) { const MetaAction* pAction = rMtf.GetAction( nActionIndex ); const MetaActionType nType = pAction->GetType(); switch( nType ) { case MetaActionType::TEXT: { bConfigured = implGetTextPosition( pAction, aPos, bEmpty ); } break; case MetaActionType::TEXTRECT: { bConfigured = implGetTextPosition( pAction, aPos, bEmpty ); } break; case MetaActionType::TEXTARRAY: { bConfigured = implGetTextPosition( pAction, aPos, bEmpty ); } break; case MetaActionType::FLOATTRANSPARENT: { const MetaFloatTransparentAction* pA = static_cast(pAction); GDIMetaFile aTmpMtf(pA->GetGDIMetaFile()); size_t nTmpAction = 0; if (setTextPosition(aTmpMtf, nTmpAction, nWriteFlags) == 1) { // 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, pA->getSVGTransparencyColorStops(), &maTextOpacity); } } break; case MetaActionType::STRETCHTEXT: { bConfigured = implGetTextPosition( pAction, aPos, bEmpty ); } break; case MetaActionType::BMPSCALE: { bConfigured = implGetTextPositionFromBitmap( pAction, aPos, bEmpty ); } break; case MetaActionType::BMPEXSCALE: { bConfigured = implGetTextPositionFromBitmap( pAction, aPos, bEmpty ); } break; // If we reach the end of the current line, paragraph or text shape // without finding any text we stop searching case MetaActionType::COMMENT: { const MetaCommentAction* pA = static_cast(pAction); const OString& rsComment = pA->GetComment(); if( rsComment.equalsIgnoreAsciiCase( "XTEXT_EOL" ) ) { bEOL = true; } else if( rsComment.equalsIgnoreAsciiCase( "XTEXT_EOP" ) ) { bEOP = true; OUString sContent; while( nextTextPortion() ) { sContent = mrCurrentTextPortion->getString(); if( sContent.isEmpty() ) { continue; } else { if( sContent == "\n" ) mbLineBreak = true; } } if( nextParagraph() ) { while( nextTextPortion() ) { sContent = mrCurrentTextPortion->getString(); if( sContent.isEmpty() ) { continue; } else { if( sContent == "\n" ) mbLineBreak = true; } } } } else if( rsComment.equalsIgnoreAsciiCase( "XTEXT_PAINTSHAPE_END" ) ) { bETS = true; } } break; default: break; } if( bConfigured || bEOL || bEOP || bETS ) break; } implMap( aPos, maTextPos ); if( bEmpty ) { nCurAction = nActionIndex; return ( bEOL ? -2 : ( bEOP ? -1 : 0 ) ); } else { return 1; } } void SVGTextWriter::setTextProperties( const GDIMetaFile& rMtf, size_t nCurAction ) { size_t nCount = rMtf.GetActionSize(); bool bEOP = false; bool bConfigured = false; for( size_t nActionIndex = nCurAction + 1; nActionIndex < nCount; ++nActionIndex ) { const MetaAction* pAction = rMtf.GetAction( nActionIndex ); const MetaActionType nType = pAction->GetType(); switch( nType ) { case MetaActionType::TEXTLINECOLOR: case MetaActionType::TEXTFILLCOLOR: case MetaActionType::TEXTCOLOR: case MetaActionType::TEXTALIGN: case MetaActionType::FONT: case MetaActionType::LAYOUTMODE: { const_cast(pAction)->Execute( mpVDev ); } break; case MetaActionType::TEXT: { const MetaTextAction* pA = static_cast(pAction); if( pA->GetLen() > 2 ) bConfigured = true; } break; case MetaActionType::TEXTRECT: { const MetaTextRectAction* pA = static_cast(pAction); if( pA->GetText().getLength() > 2 ) bConfigured = true; } break; case MetaActionType::TEXTARRAY: { const MetaTextArrayAction* pA = static_cast(pAction); if( pA->GetLen() > 2 ) bConfigured = true; } break; case MetaActionType::STRETCHTEXT: { const MetaStretchTextAction* pA = static_cast(pAction); if( pA->GetLen() > 2 ) bConfigured = true; } break; // If we reach the end of the paragraph without finding any text // we stop searching case MetaActionType::COMMENT: { const MetaCommentAction* pA = static_cast(pAction); const OString& rsComment = pA->GetComment(); if( rsComment.equalsIgnoreAsciiCase( "XTEXT_EOP" ) ) { bEOP = true; } } break; default: break; } if( bConfigured || bEOP ) break; } } void SVGTextWriter::addFontAttributes( bool bIsTextContainer ) { implSetCurrentFont(); if( maCurrentFont == maParentFont ) return; const OUString& rsCurFontName = maCurrentFont.GetFamilyName(); tools::Long nCurFontSize = maCurrentFont.GetFontHeight(); FontItalic eCurFontItalic = maCurrentFont.GetItalic(); FontWeight eCurFontWeight = maCurrentFont.GetWeight(); const OUString& rsParFontName = maParentFont.GetFamilyName(); tools::Long nParFontSize = maParentFont.GetFontHeight(); FontItalic eParFontItalic = maParentFont.GetItalic(); FontWeight eParFontWeight = maParentFont.GetWeight(); // Font Family if( rsCurFontName != rsParFontName ) { implSetFontFamily(); } // Font Size if( nCurFontSize != nParFontSize ) { mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontSize, OUString::number( nCurFontSize ) + "px" ); } // Font Style if( eCurFontItalic != eParFontItalic ) { OUString sFontStyle; if( eCurFontItalic != ITALIC_NONE ) { if( eCurFontItalic == ITALIC_OBLIQUE ) sFontStyle = "oblique"; else sFontStyle = "italic"; } else { sFontStyle = "normal"; } mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontStyle, sFontStyle ); } // Font Weight if( eCurFontWeight != eParFontWeight ) { sal_Int32 nFontWeight; switch( eCurFontWeight ) { case WEIGHT_THIN: nFontWeight = 100; break; case WEIGHT_ULTRALIGHT: nFontWeight = 200; break; case WEIGHT_LIGHT: nFontWeight = 300; break; case WEIGHT_SEMILIGHT: nFontWeight = 400; break; case WEIGHT_NORMAL: nFontWeight = 400; break; case WEIGHT_MEDIUM: nFontWeight = 500; break; case WEIGHT_SEMIBOLD: nFontWeight = 600; break; case WEIGHT_BOLD: nFontWeight = 700; break; case WEIGHT_ULTRABOLD: nFontWeight = 800; break; case WEIGHT_BLACK: nFontWeight = 900; break; default: nFontWeight = 400; break; } mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontWeight, OUString::number( nFontWeight ) ); } if( mrExport.IsUseNativeTextDecoration() ) { FontLineStyle eCurFontLineStyle = maCurrentFont.GetUnderline(); FontStrikeout eCurFontStrikeout = maCurrentFont.GetStrikeout(); FontLineStyle eParFontLineStyle = maParentFont.GetUnderline(); FontStrikeout eParFontStrikeout = maParentFont.GetStrikeout(); OUString sTextDecoration; bool bIsDecorationChanged = false; if( eCurFontLineStyle != eParFontLineStyle ) { if( eCurFontLineStyle != LINESTYLE_NONE ) sTextDecoration = "underline"; bIsDecorationChanged = true; } if( eCurFontStrikeout != eParFontStrikeout ) { if( eCurFontStrikeout != STRIKEOUT_NONE ) { if( !sTextDecoration.isEmpty() ) sTextDecoration += " "; sTextDecoration += "line-through"; } bIsDecorationChanged = true; } if( !sTextDecoration.isEmpty() ) { mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTextDecoration, sTextDecoration ); } else if( bIsDecorationChanged ) { sTextDecoration = "none"; mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTextDecoration, sTextDecoration ); } } if( bIsTextContainer ) maParentFont = maCurrentFont; } void SVGTextWriter::implSetFontFamily() { const OUString& rsFontName = maCurrentFont.GetFamilyName(); OUString sFontFamily( rsFontName.getToken( 0, ';' ) ); FontPitch ePitch = maCurrentFont.GetPitch(); if( ePitch == PITCH_FIXED ) { sFontFamily += ", monospace"; } else { FontFamily eFamily = maCurrentFont.GetFamilyType(); if( eFamily == FAMILY_ROMAN ) sFontFamily += ", serif"; else if( eFamily == FAMILY_SWISS ) sFontFamily += ", sans-serif"; } mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontFamily, sFontFamily ); } void SVGTextWriter::createParagraphEnumeration() { if( mrTextShape.is() ) { msShapeId = implGetValidIDFromInterface( Reference(mrTextShape, UNO_QUERY) ); Reference< XEnumerationAccess > xEnumerationAccess( mrTextShape, UNO_QUERY_THROW ); Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW ); if( xEnumeration.is() ) { mrParagraphEnumeration.set( xEnumeration ); } else { OSL_FAIL( "SVGTextWriter::createParagraphEnumeration: no valid xEnumeration interface found." ); } } else { OSL_FAIL( "SVGTextWriter::createParagraphEnumeration: no valid XText interface found." ); } } bool SVGTextWriter::nextParagraph() { mrTextPortionEnumeration.clear(); mrCurrentTextParagraph.clear(); mbIsNewListItem = false; mbIsListLevelStyleImage = false; if( !mrParagraphEnumeration || !mrParagraphEnumeration->hasMoreElements() ) return false; Reference < XTextContent > xTextContent( mrParagraphEnumeration->nextElement(), UNO_QUERY_THROW ); if( xTextContent.is() ) { Reference< XServiceInfo > xServiceInfo( xTextContent, UNO_QUERY_THROW ); #if OSL_DEBUG_LEVEL > 0 OUString sInfo; #endif if( xServiceInfo->supportsService( "com.sun.star.text.Paragraph" ) ) { mrCurrentTextParagraph.set( xTextContent ); Reference< XPropertySet > xPropSet( xTextContent, UNO_QUERY_THROW ); Reference< XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo(); if( xPropSetInfo->hasPropertyByName( "NumberingLevel" ) ) { sal_Int16 nListLevel = 0; if( xPropSet->getPropertyValue( "NumberingLevel" ) >>= nListLevel ) { mbIsNewListItem = true; #if OSL_DEBUG_LEVEL > 0 sInfo = "NumberingLevel: " + OUString::number( nListLevel ); mrExport.AddAttribute( XML_NAMESPACE_NONE, "style", sInfo ); #endif Reference< XIndexReplace > xNumRules; if( xPropSetInfo->hasPropertyByName( "NumberingRules" ) ) { xPropSet->getPropertyValue( "NumberingRules" ) >>= xNumRules; } if( xNumRules.is() && ( nListLevel < xNumRules->getCount() ) ) { bool bIsNumbered = true; OUString sNumberingIsNumber("NumberingIsNumber"); if( xPropSetInfo->hasPropertyByName( sNumberingIsNumber ) ) { if( !(xPropSet->getPropertyValue( sNumberingIsNumber ) >>= bIsNumbered ) ) { OSL_FAIL( "numbered paragraph without number info" ); bIsNumbered = false; } #if OSL_DEBUG_LEVEL > 0 if( bIsNumbered ) { sInfo = "true"; mrExport.AddAttribute( XML_NAMESPACE_NONE, "is-numbered", sInfo ); } #endif } mbIsNewListItem = bIsNumbered; if( bIsNumbered ) { Sequence aProps; if( xNumRules->getByIndex( nListLevel ) >>= aProps ) { sal_Int16 eType = NumberingType::CHAR_SPECIAL; sal_Unicode cBullet = 0xf095; const sal_Int32 nCount = aProps.getLength(); const PropertyValue* pPropArray = aProps.getConstArray(); for( sal_Int32 i = 0; i < nCount; ++i ) { const PropertyValue& rProp = pPropArray[i]; if( rProp.Name == "NumberingType" ) { rProp.Value >>= eType; } else if( rProp.Name == "BulletChar" ) { OUString sValue; rProp.Value >>= sValue; if( !sValue.isEmpty() ) { cBullet = sValue[0]; } } } meNumberingType = eType; mbIsListLevelStyleImage = ( NumberingType::BITMAP == meNumberingType ); if( NumberingType::CHAR_SPECIAL == meNumberingType ) { if( cBullet ) { if( cBullet < ' ' ) { cBullet = 0xF000 + 149; } mcBulletChar = cBullet; #if OSL_DEBUG_LEVEL > 0 sInfo = OUString::number( static_cast(cBullet) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, "bullet-char", sInfo ); #endif } } } } } } } Reference< XEnumerationAccess > xEnumerationAccess( xTextContent, UNO_QUERY_THROW ); Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW ); if( xEnumeration.is() && xEnumeration->hasMoreElements() ) { mrTextPortionEnumeration.set( xEnumeration ); } #if OSL_DEBUG_LEVEL > 0 sInfo = "Paragraph"; #endif } else if( xServiceInfo->supportsService( "com.sun.star.text.Table" ) ) { OSL_FAIL( "SVGTextWriter::nextParagraph: text tables are not handled." ); #if OSL_DEBUG_LEVEL > 0 sInfo = "Table"; #endif } else { OSL_FAIL( "SVGTextWriter::nextParagraph: Unknown text content." ); return false; } #if OSL_DEBUG_LEVEL > 0 mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", sInfo ); SvXMLElementExport aParaElem( mrExport, XML_NAMESPACE_NONE, "desc", mbIWS, mbIWS ); #endif } else { OSL_FAIL( "SVGTextWriter::nextParagraph: no XServiceInfo interface available for text content." ); return false; } const OUString& rParagraphId = implGetValidIDFromInterface( Reference(xTextContent, UNO_QUERY) ); if( !rParagraphId.isEmpty() ) { // 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; } bool SVGTextWriter::nextTextPortion() { mrCurrentTextPortion.clear(); mbIsURLField = false; if( !mrTextPortionEnumeration || !mrTextPortionEnumeration->hasMoreElements() ) return false; mbIsPlaceholderShape = false; Reference< XPropertySet > xPortionPropSet( mrTextPortionEnumeration->nextElement(), UNO_QUERY ); Reference< XPropertySetInfo > xPortionPropInfo( xPortionPropSet->getPropertySetInfo() ); Reference < XTextRange > xPortionTextRange( xPortionPropSet, UNO_QUERY); if( !xPortionPropSet || !xPortionPropInfo || !xPortionPropInfo->hasPropertyByName( "TextPortionType" ) ) return true; #if OSL_DEBUG_LEVEL > 0 OUString sInfo; OUString sPortionType; if( xPortionPropSet->getPropertyValue( "TextPortionType" ) >>= sPortionType ) { sInfo = "type: " + sPortionType + "; "; } #endif msPageCount = ""; msDateTimeType = ""; msTextFieldType = ""; if( xPortionTextRange.is() ) { #if OSL_DEBUG_LEVEL > 0 sInfo += "content: " + xPortionTextRange->getString() + "; "; #endif mrCurrentTextPortion.set( xPortionTextRange ); Reference < XPropertySet > xRangePropSet( xPortionTextRange, UNO_QUERY ); if( xRangePropSet.is() && xRangePropSet->getPropertySetInfo()->hasPropertyByName( "TextField" ) ) { Reference < XTextField > xTextField( xRangePropSet->getPropertyValue( "TextField" ), UNO_QUERY ); if( xTextField.is() ) { 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(); const OUString* pNames = aServices.getConstArray(); sal_Int32 nCount = aServices.getLength(); OUString sFieldName; // service name postfix of current field // search for TextField service name while( nCount-- ) { if ( pNames->matchIgnoreAsciiCase( sServicePrefix ) ) { // TextField found => postfix is field type! sFieldName = pNames->copy( sServicePrefix.getLength() ); break; } else if( pNames->startsWith( sPresentationServicePrefix ) ) { // TextField found => postfix is field type! sFieldName = pNames->copy( sPresentationServicePrefix.getLength() ); break; } ++pNames; } msTextFieldType = sFieldName; #if OSL_DEBUG_LEVEL > 0 sInfo += "text field type: " + sFieldName + "; content: " + xTextField->getPresentation( /* show command: */ false ) + "; "; #endif // 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 xTextFieldPropSet(xTextField, UNO_QUERY); if( xTextFieldPropSet.is() ) { Reference xPropSetInfo = xTextFieldPropSet->getPropertySetInfo(); if( xPropSetInfo.is() ) { // 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; if( xPropSetInfo->hasPropertyByName("IsFixed") && ( ( xTextFieldPropSet->getPropertyValue( "IsFixed" ) ) >>= bIsFixed ) && !bIsFixed ) { bool bIsDate = true; if( xPropSetInfo->hasPropertyByName("IsDate") && ( ( xTextFieldPropSet->getPropertyValue( "IsDate" ) ) >>= bIsDate ) ) { msDateTimeType = OUString::createFromAscii( bIsDate ? "Date" : "Time" ); } } } } } if( sFieldName == "DateTime" || sFieldName == "Header" || sFieldName == "Footer" || sFieldName == "PageNumber" || sFieldName == "PageName" ) { mbIsPlaceholderShape = true; } else if (sFieldName == "PageCount") { msPageCount = xTextField->getPresentation( /* show command: */ false ); } else { mbIsURLField = sFieldName == "URL"; if( mbIsURLField ) { Reference xTextFieldPropSet(xTextField, UNO_QUERY); if( xTextFieldPropSet.is() ) { OUString sURL; if( ( xTextFieldPropSet->getPropertyValue( sFieldName ) ) >>= sURL ) { #if OSL_DEBUG_LEVEL > 0 sInfo += "url: " + mrExport.GetRelativeReference( sURL ); #endif msUrl = mrExport.GetRelativeReference( sURL ); if( !msUrl.isEmpty() ) { implRegisterInterface( xPortionTextRange ); const OUString& rTextPortionId = implGetValidIDFromInterface( Reference(xPortionTextRange, UNO_QUERY) ); if( !rTextPortionId.isEmpty() ) { msHyperlinkIdList += rTextPortionId + " "; } } } } } } } } } #if OSL_DEBUG_LEVEL > 0 mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextPortion" ); SvXMLElementExport aPortionElem( mrExport, XML_NAMESPACE_NONE, "desc", mbIWS, mbIWS ); mrExport.GetDocHandler()->characters( sInfo ); #endif return true; } void SVGTextWriter::startTextShape() { if( mpTextShapeElem ) { OSL_FAIL( "SVGTextWriter::startTextShape: text shape already defined." ); } { mbIsTextShapeStarted = true; maParentFont = vcl::Font(); mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "SVGTextShape" ); // if text is rotated, set transform matrix at text element const vcl::Font& rFont = mpVDev->GetFont(); if( rFont.GetOrientation() ) { Point aRot( maTextPos ); OUString aTransform = "rotate(" + OUString::number( rFont.GetOrientation().get() * -0.1 ) + " " + OUString::number( aRot.X() ) + " " + OUString::number( aRot.Y() ) + ")"; mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTransform, aTransform ); } mpTextShapeElem.reset(new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemText, true, mbIWS )); startTextParagraph(); } } void SVGTextWriter::endTextShape() { endTextParagraph(); mrTextShape.clear(); mrParagraphEnumeration.clear(); mrCurrentTextParagraph.clear(); mpTextShapeElem.reset(); maTextOpacity.clear(); mbIsTextShapeStarted = false; // these need to be invoked after the element has been closed implExportHyperlinkIds(); implWriteBulletChars(); implWriteEmbeddedBitmaps(); } void SVGTextWriter::startTextParagraph() { endTextParagraph(); nextParagraph(); if( mbIsNewListItem ) { OUString sNumberingType; switch( meNumberingType ) { case NumberingType::CHAR_SPECIAL: sNumberingType = "bullet-style"; break; case NumberingType::BITMAP: sNumberingType = "image-style"; break; default: sNumberingType = "number-style"; break; } mrExport.AddAttribute( XML_NAMESPACE_NONE, "ooo:numbering-type", sNumberingType ); mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "ListItem" ); } else { mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextParagraph" ); } maParentFont = vcl::Font(); mpTextParagraphElem.reset(new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS )); if( !mbIsListLevelStyleImage ) { mbPositioningNeeded = true; } } void SVGTextWriter::endTextParagraph() { mrCurrentTextPortion.clear(); endTextPosition(); mbIsNewListItem = false; mbIsListLevelStyleImage = false; mbPositioningNeeded = false; mpTextParagraphElem.reset(); } void SVGTextWriter::startTextPosition( bool bExportX, bool bExportY ) { endTextPosition(); mnTextWidth = 0; mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextPosition" ); if( bExportX ) mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( maTextPos.X() ) ); if( bExportY ) mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( maTextPos.Y() ) ); mpTextPositionElem.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ) ); } void SVGTextWriter::endTextPosition() { mpTextPositionElem.reset(); } bool SVGTextWriter::hasTextOpacity() const { return !maTextOpacity.isEmpty(); } void SVGTextWriter::implExportHyperlinkIds() { if( !msHyperlinkIdList.isEmpty() ) { mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "HyperlinkIdList" ); SvXMLElementExport aDescElem( mrExport, XML_NAMESPACE_NONE, "desc", true, false ); mrExport.GetDocHandler()->characters( msHyperlinkIdList.trim() ); msHyperlinkIdList.clear(); } } void SVGTextWriter::implWriteBulletChars() { if( maBulletListItemMap.empty() ) return; mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "BulletChars" ); SvXMLElementExport aGroupElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ); OUString sId, sPosition, sScaling, sRefId; for (auto const& bulletListItem : maBulletListItemMap) { // (used by animations) // As id we use the id of the text portion placeholder with prefix // bullet-char-* sId = "bullet-char-" + bulletListItem.first; mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", sId ); mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "BulletChar" ); SvXMLElementExport aBulletCharElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ); // { const BulletListItemInfo& rInfo = bulletListItem.second; // Add positioning attribute through a translation sPosition = "translate(" + OUString::number( rInfo.aPos.X() ) + "," + OUString::number( rInfo.aPos.Y() ) + ")"; mrExport.AddAttribute( XML_NAMESPACE_NONE, "transform", sPosition ); mrAttributeWriter.AddPaintAttr( COL_TRANSPARENT, rInfo.aColor ); SvXMLElementExport aPositioningElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ); if (mrExport.IsEmbeddedBulletGlyph(rInfo.cBulletChar)) { // // Add size attribute through a scaling sScaling = "scale(" + OUString::number( rInfo.aFont.GetFontHeight() ) + "," + OUString::number( rInfo.aFont.GetFontHeight() )+ ")"; mrExport.AddAttribute( XML_NAMESPACE_NONE, "transform", sScaling ); // Add ref attribute sRefId = "#bullet-char-template-" + OUString::number( rInfo.cBulletChar ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId ); SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true ); } else { // tools::PolyPolygon aPolyPolygon; OUString aStr(rInfo.cBulletChar); mpVDev->Push(vcl::PushFlags::FONT); mpVDev->SetFont(rInfo.aFont); if (mpVDev->GetTextOutline(aPolyPolygon, aStr)) { OUString aPathString(SVGActionWriter::GetPathString(aPolyPolygon, false)); mrExport.AddAttribute(XML_NAMESPACE_NONE, "d", aPathString); SvXMLElementExport aPath(mrExport, XML_NAMESPACE_NONE, "path", true, true); } mpVDev->Pop(); } } // close aPositioningElem } // clear the map maBulletListItemMap.clear(); } template< typename MetaBitmapActionType > void SVGTextWriter::writeBitmapPlaceholder( const MetaBitmapActionType* pAction ) { // text position element const Point& rPos = pAction->GetPoint(); implMap( rPos, maTextPos ); startTextPosition(); mbPositioningNeeded = true; if( mbIsNewListItem ) { mbIsNewListItem = false; mbIsListLevelStyleImage = false; } // bitmap placeholder element BitmapChecksum nId = SVGActionWriter::GetChecksum( pAction ); OUString sId = "bitmap-placeholder(" + msShapeId + "." + OUString::number( nId ) + ")"; { mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", sId ); mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "BitmapPlaceholder" ); SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ); } endTextPosition(); } void SVGTextWriter::implWriteEmbeddedBitmaps() { if( !(mpTextEmbeddedBitmapMtf && mpTextEmbeddedBitmapMtf->GetActionSize()) ) return; mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "EmbeddedBitmaps" ); SvXMLElementExport aEmbBitmapGroupElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ); const GDIMetaFile& rMtf = *mpTextEmbeddedBitmapMtf; BitmapChecksum nId, nChecksum = 0; Point aPt; Size aSz; size_t nCount = rMtf.GetActionSize(); for( size_t nCurAction = 0; nCurAction < nCount; nCurAction++ ) { const MetaAction* pAction = rMtf.GetAction( nCurAction ); const MetaActionType nType = pAction->GetType(); switch( nType ) { case MetaActionType::BMPSCALE: { const MetaBmpScaleAction* pA = static_cast(pAction); // The conversion to BitmapEx is needed since at the point // where the bitmap is actually exported a Bitmap object is // converted to BitmapEx before computing the checksum used // to generate the element id. // (See GetBitmapChecksum in svgexport.cxx) nChecksum = BitmapEx( pA->GetBitmap() ).GetChecksum(); aPt = pA->GetPoint(); aSz = pA->GetSize(); } break; case MetaActionType::BMPEXSCALE: { const MetaBmpExScaleAction* pA = static_cast(pAction); nChecksum = pA->GetBitmapEx().GetChecksum(); aPt = pA->GetPoint(); aSz = pA->GetSize(); } break; default: break; } // (used by animations) { // embedded bitmap id nId = SVGActionWriter::GetChecksum( pAction ); OUString sId = "embedded-bitmap(" + msShapeId + "." + OUString::number( nId ) + ")"; mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", sId ); mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "EmbeddedBitmap" ); SvXMLElementExport aEmbBitmapElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ); // { // referenced bitmap template OUString sRefId = "#bitmap(" + OUString::number( nChecksum ) + ")"; Point aPoint; Size aSize; implMap( aPt, aPoint ); implMap( aSz, aSize ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aPoint.X() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPoint.Y() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId ); SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true ); } } // close aEmbBitmapElem } } void SVGTextWriter::writeTextPortion( const Point& rPos, const OUString& rText ) { if( rText.isEmpty() ) return; bool bStandAloneTextPortion = false; if( !isTextShapeStarted() ) { bStandAloneTextPortion = true; startTextShape(); } mbLineBreak = false; if( !mbIsNewListItem || mbIsListLevelStyleImage ) { bool bNotSync = true; OUString sContent; sal_Int32 nStartPos; while( bNotSync ) { if( mnLeftTextPortionLength <= 0 || !mrCurrentTextPortion.is() ) { if( !nextTextPortion() ) break; else { sContent = mrCurrentTextPortion->getString(); if( mbIsURLField && sContent.isEmpty() ) { Reference < XPropertySet > xPropSet( mrCurrentTextPortion, UNO_QUERY ); Reference < XTextField > xTextField( xPropSet->getPropertyValue( "TextField" ), UNO_QUERY ); sContent = xTextField->getPresentation( /* show command: */ false ); if( sContent.isEmpty() ) OSL_FAIL( "SVGTextWriter::writeTextPortion: content of URL TextField is empty." ); } mnLeftTextPortionLength = sContent.getLength(); } } else { sContent = mrCurrentTextPortion->getString(); } nStartPos = sContent.getLength() - mnLeftTextPortionLength; if( nStartPos < 0 ) nStartPos = 0; mnLeftTextPortionLength -= rText.getLength(); if( sContent.isEmpty() ) 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; } } assert(mpVDev); //invalid virtual device #if 0 const FontMetric aMetric( mpVDev->GetFontMetric() ); bool bTextSpecial = aMetric.IsShadow() || aMetric.IsOutline() || (aMetric.GetRelief() != FontRelief::NONE); if( true || !bTextSpecial ) { implWriteTextPortion( rPos, rText, mpVDev->GetTextColor() ); } else { // to be implemented } #else implWriteTextPortion( rPos, rText, mpVDev->GetTextColor() ); #endif if( bStandAloneTextPortion ) { endTextShape(); } } void SVGTextWriter::implWriteTextPortion( const Point& rPos, const OUString& rText, Color aTextColor ) { Point aPos; Point aBaseLinePos( rPos ); const FontMetric aMetric( mpVDev->GetFontMetric() ); const vcl::Font& rFont = mpVDev->GetFont(); if( rFont.GetAlignment() == ALIGN_TOP ) aBaseLinePos.AdjustY(aMetric.GetAscent() ); else if( rFont.GetAlignment() == ALIGN_BOTTOM ) aBaseLinePos.AdjustY( -(aMetric.GetDescent()) ); implMap( rPos, aPos ); if( mbPositioningNeeded ) { mbPositioningNeeded = false; maTextPos.setX( aPos.X() ); maTextPos.setY( aPos.Y() ); startTextPosition(); } else if( maTextPos.Y() != aPos.Y() ) { // In case the text position moved backward we could have a line break // so we end the current line and start a new one. if( mbLineBreak || ( ( maTextPos.X() + mnTextWidth ) > aPos.X() ) ) { mbLineBreak = false; maTextPos.setX( aPos.X() ); maTextPos.setY( aPos.Y() ); startTextPosition(); } else // superscript, subscript, list item numbering { maTextPos.setY( aPos.Y() ); startTextPosition( false /* do not export x attribute */ ); } } // we are dealing with a bullet, so set up this for the next text portion if( mbIsNewListItem ) { mbIsNewListItem = false; mbPositioningNeeded = true; if( meNumberingType == NumberingType::CHAR_SPECIAL ) { // Create an id for the current text portion implRegisterInterface( mrCurrentTextParagraph ); // Add the needed info to the BulletListItemMap OUString sId = implGetValidIDFromInterface( Reference(mrCurrentTextParagraph, UNO_QUERY) ); if( !sId.isEmpty() ) { sId += ".bp"; BulletListItemInfo& aBulletListItemInfo = maBulletListItemMap[ sId ]; aBulletListItemInfo.aFont = rFont; aBulletListItemInfo.aColor = aTextColor; aBulletListItemInfo.aPos = maTextPos; aBulletListItemInfo.cBulletChar = mcBulletChar; // Make this text portion a bullet placeholder mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", sId ); mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "BulletPlaceholder" ); SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ); return; } } } const OUString& rTextPortionId = implGetValidIDFromInterface( Reference(mrCurrentTextPortion, UNO_QUERY) ); if( !rTextPortionId.isEmpty() ) { mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", rTextPortionId ); } if( mbIsPlaceholderShape ) { 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 ); if (!maTextOpacity.isEmpty()) { mrExport.AddAttribute(XML_NAMESPACE_NONE, "fill-opacity", maTextOpacity); } mrAttributeWriter.AddPaintAttr( COL_TRANSPARENT, aTextColor ); // tag for link should be the innermost tag, inside if( !mbIsPlaceholderShape && mbIsURLField && !msUrl.isEmpty() ) { mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "UrlField" ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, msUrl ); SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, msUrl ); { SvXMLElementExport aSVGAElem( mrExport, XML_NAMESPACE_NONE, "a", mbIWS, mbIWS ); mrExport.GetDocHandler()->characters( rText ); } } else if ( !msPageCount.isEmpty() ) { mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "PageCount" ); SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ); mrExport.GetDocHandler()->characters( msPageCount ); } 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 ); } mnTextWidth += mpVDev->GetTextWidth( rText ); } SVGActionWriter::SVGActionWriter( SVGExport& rExport, SVGFontExport& rFontExport ) : mnCurGradientId( 1 ), mnCurMaskId( 1 ), mnCurPatternId( 1 ), mnCurClipPathId( 1 ), mrExport( rExport ), maContextHandler(), mrCurrentState( maContextHandler.getCurrentState() ), maAttributeWriter( rExport, rFontExport, mrCurrentState ), maTextWriter(rExport, maAttributeWriter, *this), mpVDev(VclPtr::Create()), mbClipAttrChanged( false ), mbIsPlaceholderShape( false ), mpEmbeddedBitmapsMap( nullptr ), mbIsPreview( false ) { mpVDev->EnableOutput( false ); maTargetMapMode = MapMode(MapUnit::Map100thMM); maTextWriter.setVirtualDevice( mpVDev, maTargetMapMode ); } SVGActionWriter::~SVGActionWriter() { mpVDev.disposeAndClear(); } tools::Long SVGActionWriter::ImplMap( sal_Int32 nVal ) const { Size aSz( nVal, nVal ); return ImplMap( aSz, aSz ).Width(); } Point& SVGActionWriter::ImplMap( const Point& rPt, Point& rDstPt ) const { rDstPt = OutputDevice::LogicToLogic( rPt, mpVDev->GetMapMode(), maTargetMapMode ); return rDstPt; } Size& SVGActionWriter::ImplMap( const Size& rSz, Size& rDstSz ) const { rDstSz = OutputDevice::LogicToLogic( rSz, mpVDev->GetMapMode(), maTargetMapMode ); return rDstSz; } void SVGActionWriter::ImplMap( const tools::Rectangle& rRect, tools::Rectangle& rDstRect ) const { Point aTL( rRect.TopLeft() ); Size aSz( rRect.GetSize() ); rDstRect = tools::Rectangle( ImplMap( aTL, aTL ), ImplMap( aSz, aSz ) ); } tools::Polygon& SVGActionWriter::ImplMap( const tools::Polygon& rPoly, tools::Polygon& rDstPoly ) const { rDstPoly = tools::Polygon( rPoly.GetSize() ); for( sal_uInt16 i = 0, nSize = rPoly.GetSize(); i < nSize; ++i ) { ImplMap( rPoly[ i ], rDstPoly[ i ] ); rDstPoly.SetFlags( i, rPoly.GetFlags( i ) ); } return rDstPoly; } tools::PolyPolygon& SVGActionWriter::ImplMap( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rDstPolyPoly ) const { tools::Polygon aPoly; rDstPolyPoly = tools::PolyPolygon(); for( sal_uInt16 i = 0, nCount = rPolyPoly.Count(); i < nCount; ++i ) { rDstPolyPoly.Insert( ImplMap( rPolyPoly[ i ], aPoly ) ); } return rDstPolyPoly; } OUString SVGActionWriter::GetPathString( const tools::PolyPolygon& rPolyPoly, bool bLine ) { OUStringBuffer aPathData; 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++ ) { const tools::Polygon& rPoly = rPolyPoly[ static_cast(i) ]; sal_uInt16 n = 1, nSize = rPoly.GetSize(); if( nSize > 1 ) { aPolyPoint = rPoly[ 0 ]; aPathData.append("M " + OUString::number( aPolyPoint.X() ) + aComma + OUString::number( aPolyPoint.Y() )); char nCurrentMode = 0; const bool bClose(!bLine || rPoly[0] == rPoly[nSize - 1]); while( n < nSize ) { aPathData.append(aBlank); if ( ( rPoly.GetFlags( n ) == PolyFlags::Control ) && ( ( n + 2 ) < nSize ) ) { if ( nCurrentMode != 'C' ) { nCurrentMode = 'C'; aPathData.append("C "); } for ( int j = 0; j < 3; j++ ) { if ( j ) aPathData.append(aBlank); aPolyPoint = rPoly[ n++ ]; aPathData.append( OUString::number(aPolyPoint.X()) + aComma + OUString::number( aPolyPoint.Y() ) ); } } else { if ( nCurrentMode != 'L' ) { nCurrentMode = 'L'; aPathData.append("L "); } aPolyPoint = rPoly[ n++ ]; aPathData.append( OUString::number(aPolyPoint.X()) + aComma + OUString::number(aPolyPoint.Y()) ); } } if(bClose) aPathData.append(" Z"); if( i < ( nCount - 1 ) ) aPathData.append(aBlank); } } return aPathData.makeStringAndClear(); } BitmapChecksum SVGActionWriter::GetChecksum( const MetaAction* pAction ) { GDIMetaFile aMtf; MetaAction* pA = const_cast(pAction); aMtf.AddAction( pA ); return SvmWriter::GetChecksum( aMtf ); } void SVGActionWriter::SetEmbeddedBitmapRefs( const MetaBitmapActionMap* pEmbeddedBitmapsMap ) { if (pEmbeddedBitmapsMap) mpEmbeddedBitmapsMap = pEmbeddedBitmapsMap; else OSL_FAIL( "SVGActionWriter::SetEmbeddedBitmapRefs: passed pointer is null" ); } void SVGActionWriter::ImplWriteLine( const Point& rPt1, const Point& rPt2, const Color* pLineColor ) { Point aPt1, aPt2; ImplMap( rPt1, aPt1 ); ImplMap( rPt2, aPt2 ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX1, OUString::number( aPt1.X() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY1, OUString::number( aPt1.Y() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX2, OUString::number( aPt2.X() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY2, OUString::number( aPt2.Y() ) ); if( pLineColor ) { // !!! mrExport.AddAttribute( XML_NAMESPACE_NONE, ... ) OSL_FAIL( "SVGActionWriter::ImplWriteLine: Line color not implemented" ); } { SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "line", true, true ); } } void SVGActionWriter::ImplWriteRect( const tools::Rectangle& rRect, tools::Long nRadX, tools::Long nRadY ) { tools::Rectangle aRect; ImplMap( rRect, aRect ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aRect.Left() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aRect.Top() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrWidth, OUString::number( aRect.GetWidth() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrHeight, OUString::number( aRect.GetHeight() ) ); if( nRadX ) mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrRX, OUString::number( ImplMap( nRadX ) ) ); if( nRadY ) mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrRY, OUString::number( ImplMap( nRadY ) ) ); SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "rect", true, true ); } void SVGActionWriter::ImplWriteEllipse( const Point& rCenter, tools::Long nRadX, tools::Long nRadY ) { Point aCenter; ImplMap( rCenter, aCenter ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrCX, OUString::number( aCenter.X() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrCY, OUString::number( aCenter.Y() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrRX, OUString::number( ImplMap( nRadX ) ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrRY, OUString::number( ImplMap( nRadY ) ) ); { SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "ellipse", true, true ); } } void SVGActionWriter::ImplAddLineAttr( const LineInfo &rAttrs ) { if ( rAttrs.IsDefault() ) return; sal_Int32 nStrokeWidth = ImplMap( rAttrs.GetWidth() ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStrokeWidth, OUString::number( nStrokeWidth ) ); // support for LineJoint switch(rAttrs.GetLineJoin()) { case basegfx::B2DLineJoin::NONE: case basegfx::B2DLineJoin::Miter: { mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "miter"); break; } case basegfx::B2DLineJoin::Bevel: { mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "bevel"); break; } case basegfx::B2DLineJoin::Round: { mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "round"); break; } } // support for LineCap switch(rAttrs.GetLineCap()) { default: /* css::drawing::LineCap_BUTT */ { // butt is Svg default, so no need to write until the exporter might write styles. // If this happens, activate here // mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "butt"); break; } case css::drawing::LineCap_ROUND: { mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "round"); break; } case css::drawing::LineCap_SQUARE: { mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "square"); break; } } } void SVGActionWriter::ImplWritePolyPolygon( const tools::PolyPolygon& rPolyPoly, bool bLineOnly, bool bApplyMapping ) { tools::PolyPolygon aPolyPoly; if( bApplyMapping ) ImplMap( rPolyPoly, aPolyPoly ); else aPolyPoly = rPolyPoly; // add path data attribute mrExport.AddAttribute( XML_NAMESPACE_NONE, "d", GetPathString( aPolyPoly, bLineOnly ) ); { // write polyline/polygon element SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "path", true, true ); } } void SVGActionWriter::ImplWriteShape( const SVGShapeDescriptor& rShape ) { tools::PolyPolygon aPolyPoly; ImplMap( rShape.maShapePolyPoly, aPolyPoly ); const bool bLineOnly = (rShape.maShapeFillColor == COL_TRANSPARENT) && (!rShape.moShapeGradient); tools::Rectangle aBoundRect( aPolyPoly.GetBoundRect() ); maAttributeWriter.AddPaintAttr( rShape.maShapeLineColor, rShape.maShapeFillColor, &aBoundRect, rShape.moShapeGradient ? &*rShape.moShapeGradient : nullptr ); if( !rShape.maId.isEmpty() ) mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, rShape.maId ); if( rShape.mnStrokeWidth ) { sal_Int32 nStrokeWidth = ImplMap( rShape.mnStrokeWidth ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStrokeWidth, OUString::number( nStrokeWidth ) ); } // support for LineJoin switch(rShape.maLineJoin) { case basegfx::B2DLineJoin::NONE: case basegfx::B2DLineJoin::Miter: { // miter is Svg default, so no need to write until the exporter might write styles. // If this happens, activate here // mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "miter"); break; } case basegfx::B2DLineJoin::Bevel: { mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "bevel"); break; } case basegfx::B2DLineJoin::Round: { mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "round"); break; } } // support for LineCap switch(rShape.maLineCap) { default: /* css::drawing::LineCap_BUTT */ { // butt is Svg default, so no need to write until the exporter might write styles. // If this happens, activate here // mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "butt"); break; } case css::drawing::LineCap_ROUND: { mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "round"); break; } case css::drawing::LineCap_SQUARE: { mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "square"); break; } } if( !rShape.maDashArray.empty() ) { OUStringBuffer aDashArrayStr; for( size_t k = 0; k < rShape.maDashArray.size(); ++k ) { const sal_Int32 nDash = ImplMap( FRound( rShape.maDashArray[ k ] ) ); if( k ) aDashArrayStr.append(","); aDashArrayStr.append( nDash ); } mrExport.AddAttribute( XML_NAMESPACE_NONE, "stroke-dasharray", aDashArrayStr.makeStringAndClear() ); } ImplWritePolyPolygon( aPolyPoly, bLineOnly, false ); } void SVGActionWriter::ImplCreateClipPathDef( const tools::PolyPolygon& rPolyPoly ) { OUString aClipPathId = aPrefixClipPathId + OUString::number( mnCurClipPathId++ ); SvXMLElementExport aElemDefs( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true ); { mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, aClipPathId ); mrExport.AddAttribute( XML_NAMESPACE_NONE, "clipPathUnits", "userSpaceOnUse" ); SvXMLElementExport aElemClipPath( mrExport, XML_NAMESPACE_NONE, "clipPath", true, true ); ImplWritePolyPolygon(rPolyPoly, false); } } void SVGActionWriter::ImplStartClipRegion(sal_Int32 nClipPathId) { assert(!mpCurrentClipRegionElem); if (nClipPathId == 0) return; OUString aUrl = OUString::Concat("url(#") + aPrefixClipPathId + OUString::number( nClipPathId ) + ")"; mrExport.AddAttribute( XML_NAMESPACE_NONE, "clip-path", aUrl ); mpCurrentClipRegionElem.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ) ); } void SVGActionWriter::ImplEndClipRegion() { if (mpCurrentClipRegionElem) { mpCurrentClipRegionElem.reset(); } } void SVGActionWriter::ImplWriteClipPath( const tools::PolyPolygon& rPolyPoly ) { ImplEndClipRegion(); if( rPolyPoly.Count() == 0 ) return; ImplCreateClipPathDef(rPolyPoly); mrCurrentState.nRegionClipPathId = mnCurClipPathId - 1; ImplStartClipRegion( mrCurrentState.nRegionClipPathId ); } void SVGActionWriter::ImplWritePattern( const tools::PolyPolygon& rPolyPoly, const Hatch* pHatch, const Gradient* pGradient, sal_uInt32 nWriteFlags ) { if( !rPolyPoly.Count() ) return; SvXMLElementExport aElemG( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ); OUString aPatternId = "pattern" + OUString::number( mnCurPatternId++ ); { SvXMLElementExport aElemDefs( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, aPatternId ); tools::Rectangle aRect; ImplMap( rPolyPoly.GetBoundRect(), aRect ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aRect.Left() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aRect.Top() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrWidth, OUString::number( aRect.GetWidth() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrHeight, OUString::number( aRect.GetHeight() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, "patternUnits", OUString( "userSpaceOnUse") ); { SvXMLElementExport aElemPattern( mrExport, XML_NAMESPACE_NONE, "pattern", true, true ); // The origin of a pattern is positioned at (aRect.Left(), aRect.Top()). // So we need to adjust the pattern coordinate. OUString aTransform = "translate(" + OUString::number( -aRect.Left() ) + "," + OUString::number( -aRect.Top() ) + ")"; mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTransform, aTransform ); { SvXMLElementExport aElemG2( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ); GDIMetaFile aTmpMtf; if( pHatch ) { mpVDev->AddHatchActions( rPolyPoly, *pHatch, aTmpMtf ); } else if ( pGradient ) { Gradient aGradient(*pGradient); aGradient.AddGradientActions( rPolyPoly.GetBoundRect(), aTmpMtf ); } ImplWriteActions( aTmpMtf, nWriteFlags, "" ); } } } OUString aPatternStyle = "fill:url(#" + aPatternId + ")"; mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStyle, aPatternStyle ); ImplWritePolyPolygon( rPolyPoly, false ); } void SVGActionWriter::ImplWriteGradientEx( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, sal_uInt32 nWriteFlags, const basegfx::BColorStops* pColorStops) { if ( rGradient.GetStyle() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ) { ImplWriteGradientLinear( rPolyPoly, rGradient, pColorStops ); } else { ImplWritePattern( rPolyPoly, nullptr, &rGradient, nWriteFlags ); } } void SVGActionWriter::ImplWriteGradientLinear( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient, const basegfx::BColorStops* pColorStops ) { if( !rPolyPoly.Count() ) return; SvXMLElementExport aElemG( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ); OUString aGradientId = "gradient" + OUString::number( mnCurGradientId++ ); { SvXMLElementExport aElemDefs( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, aGradientId ); { tools::Rectangle aTmpRect, aRect; Point aTmpCenter, aCenter; rGradient.GetBoundRect( rPolyPoly.GetBoundRect(), aTmpRect, aTmpCenter ); ImplMap( aTmpRect, aRect ); ImplMap( aTmpCenter, aCenter ); const Degree10 nAngle = rGradient.GetAngle() % 3600_deg10; tools::Polygon aPoly( 2 ); // Setting x value of a gradient vector to rotation center to // place a gradient vector in a target polygon. // This would help editing it in SVG editors like inkscape. aPoly[ 0 ].setX( aCenter.X() ); aPoly[ 1 ].setX( aCenter.X() ); aPoly[ 0 ].setY( aRect.Top() ); aPoly[ 1 ].setY( aRect.Bottom() ); aPoly.Rotate( aCenter, nAngle ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX1, OUString::number( aPoly[ 0 ].X() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY1, OUString::number( aPoly[ 0 ].Y() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX2, OUString::number( aPoly[ 1 ].X() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY2, OUString::number( aPoly[ 1 ].Y() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrGradientUnits, OUString( "userSpaceOnUse" ) ); } { SvXMLElementExport aElemLinearGradient( mrExport, XML_NAMESPACE_NONE, aXMLElemLinearGradient, true, true ); basegfx::BColorStops aColorStops; if (nullptr != pColorStops && pColorStops->size() > 1) { // 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 { // 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 ); } } } OUString aGradientStyle = "fill:url(#" + aGradientId + ")"; mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStyle, aGradientStyle ); ImplWritePolyPolygon( rPolyPoly, false ); } void SVGActionWriter::ImplWriteGradientStop( const Color& rColor, double fOffset ) { mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( fOffset ) ); OUString aStyle, aColor; aStyle += "stop-color:"; SVGAttributeWriter::ImplGetColorStr ( rColor, aColor ); aStyle += aColor; mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStyle, aStyle ); { SvXMLElementExport aElemStartStop( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true ); } } Color SVGActionWriter::ImplGetColorWithIntensity( const Color& rColor, sal_uInt16 nIntensity ) { sal_uInt8 nNewRed = static_cast( static_cast(rColor.GetRed()) * nIntensity / 100 ); sal_uInt8 nNewGreen = static_cast( static_cast(rColor.GetGreen()) * nIntensity / 100 ); sal_uInt8 nNewBlue = static_cast( static_cast(rColor.GetBlue()) * nIntensity / 100 ); return Color( nNewRed, nNewGreen, nNewBlue); } void SVGActionWriter::StartMask(const Point& rDestPt, const Size& rDestSize, const Gradient& rGradient, sal_uInt32 nWriteFlags, const basegfx::BColorStops* pColorStops, OUString* pTextFillOpacity) { OUString aStyle; if (rGradient.GetStartColor() == rGradient.GetEndColor()) { // Special case: constant alpha value. const Color& rColor = rGradient.GetStartColor(); const double fOpacity = 1.0 - static_cast(rColor.GetLuminance()) / 255; if (pTextFillOpacity) { // Don't write anything, return what is a value suitable for . *pTextFillOpacity = OUString::number(fOpacity); return; } else { aStyle = "opacity: " + OUString::number(fOpacity); } } else { OUString aMaskId = "mask" + OUString::number(mnCurMaskId++); { SvXMLElementExport aElemDefs(mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true); mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrId, aMaskId); { SvXMLElementExport aElemMask(mrExport, XML_NAMESPACE_NONE, "mask", true, true); const tools::PolyPolygon aPolyPolygon(tools::PolyPolygon(tools::Rectangle(rDestPt, rDestSize))); Gradient aGradient(rGradient); // swap gradient stops to adopt SVG mask Color aTmpColor(aGradient.GetStartColor()); sal_uInt16 nTmpIntensity(aGradient.GetStartIntensity()); aGradient.SetStartColor(aGradient.GetEndColor()); aGradient.SetStartIntensity(aGradient.GetEndIntensity()); aGradient.SetEndColor(aTmpColor); aGradient.SetEndIntensity(nTmpIntensity); // 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); } } aStyle = "mask:url(#" + aMaskId + ")"; } mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStyle, aStyle); } void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, const Size& rDestSize, const Gradient& rGradient, sal_uInt32 nWriteFlags, const basegfx::BColorStops* pColorStops) { Point aSrcPt(rMtf.GetPrefMapMode().GetOrigin()); const Size aSrcSize(rMtf.GetPrefSize()); const double fScaleX = aSrcSize.Width() ? static_cast(rDestSize.Width()) / aSrcSize.Width() : 1.0; const double fScaleY = aSrcSize.Height() ? static_cast(rDestSize.Height()) / aSrcSize.Height() : 1.0; tools::Long nMoveX, nMoveY; if (fScaleX != 1.0 || fScaleY != 1.0) { rMtf.Scale(fScaleX, fScaleY); aSrcPt.setX(FRound(aSrcPt.X() * fScaleX)); aSrcPt.setY(FRound(aSrcPt.Y() * fScaleY)); } nMoveX = rDestPt.X() - aSrcPt.X(); nMoveY = rDestPt.Y() - aSrcPt.Y(); if (nMoveX || nMoveY) rMtf.Move(nMoveX, nMoveY); { std::unique_ptr pElemG; if (!maTextWriter.hasTextOpacity()) { StartMask(rDestPt, rDestSize, rGradient, nWriteFlags, pColorStops); pElemG.reset( new SvXMLElementExport(mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true)); } mpVDev->Push(); ImplWriteActions( rMtf, nWriteFlags, "" ); mpVDev->Pop(); } } void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText, KernArraySpan pDXArray, tools::Long nWidth ) { const FontMetric aMetric( mpVDev->GetFontMetric() ); bool bTextSpecial = aMetric.IsShadow() || aMetric.IsOutline() || (aMetric.GetRelief() != FontRelief::NONE); if( !bTextSpecial ) { ImplWriteText( rPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); } else { if( aMetric.GetRelief() != FontRelief::NONE ) { Color aReliefColor( COL_LIGHTGRAY ); Color aTextColor( mpVDev->GetTextColor() ); if ( aTextColor == COL_BLACK ) aTextColor = COL_WHITE; // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct if (aTextColor == COL_WHITE) aReliefColor = COL_BLACK; Point aPos( rPos ); Point aOffset( 6, 6 ); if ( aMetric.GetRelief() == FontRelief::Engraved ) { aPos -= aOffset; } else { aPos += aOffset; } ImplWriteText( aPos, rText, pDXArray, nWidth, aReliefColor ); ImplWriteText( rPos, rText, pDXArray, nWidth, aTextColor ); } else { if( aMetric.IsShadow() ) { tools::Long nOff = 1 + ((aMetric.GetLineHeight()-24)/24); if ( aMetric.IsOutline() ) nOff += 6; Color aTextColor( mpVDev->GetTextColor() ); Color aShadowColor( COL_BLACK ); if ( (aTextColor == COL_BLACK) || (aTextColor.GetLuminance() < 8) ) aShadowColor = COL_LIGHTGRAY; Point aPos( rPos ); aPos += Point( nOff, nOff ); ImplWriteText( aPos, rText, pDXArray, nWidth, aShadowColor ); if( !aMetric.IsOutline() ) { ImplWriteText( rPos, rText, pDXArray, nWidth, aTextColor ); } } if( aMetric.IsOutline() ) { Point aPos = rPos + Point( -6, -6 ); ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); aPos = rPos + Point( +6, +6); ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); aPos = rPos + Point( -6, +0); ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); aPos = rPos + Point( -6, +6); ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); aPos = rPos + Point( +0, +6); ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); aPos = rPos + Point( +0, -6); ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); aPos = rPos + Point( +6, -1); ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); aPos = rPos + Point( +6, +0); ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); ImplWriteText( rPos, rText, pDXArray, nWidth, COL_WHITE ); } } } } void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText, KernArraySpan pDXArray, tools::Long nWidth, Color aTextColor ) { sal_Int32 nLen = rText.getLength(); Size aNormSize; Point aPos; Point aBaseLinePos( rPos ); const FontMetric aMetric( mpVDev->GetFontMetric() ); const vcl::Font& rFont = mpVDev->GetFont(); if( rFont.GetAlignment() == ALIGN_TOP ) aBaseLinePos.AdjustY(aMetric.GetAscent() ); else if( rFont.GetAlignment() == ALIGN_BOTTOM ) aBaseLinePos.AdjustY( -(aMetric.GetDescent()) ); ImplMap( rPos, aPos ); KernArray aTmpArray; // get text sizes if( !pDXArray.empty() ) { aNormSize = Size( mpVDev->GetTextWidth( rText ), 0 ); aTmpArray.assign(pDXArray); } else { aNormSize = Size( mpVDev->GetTextArray( rText, &aTmpArray ), 0 ); } // if text is rotated, set transform matrix at new g element if( rFont.GetOrientation() ) { Point aRot( aPos ); OUString aTransform = "rotate(" + OUString::number( rFont.GetOrientation().get() * -0.1 ) + " " + OUString::number( aRot.X() ) + " " + OUString::number( aRot.Y() ) + ")"; mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTransform, aTransform ); } maAttributeWriter.AddPaintAttr( COL_TRANSPARENT, aTextColor ); // for each line of text there should be at least one group element SvXMLElementExport aSVGGElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, false ); bool bIsPlaceholderField = false; if( mbIsPlaceholderShape ) { bIsPlaceholderField = rText.match( sPlaceholderTag ); // for a placeholder text field we export only one svg element if( bIsPlaceholderField ) { OUString sCleanTextContent; static const sal_Int32 nFrom = sPlaceholderTag.getLength(); if( rText.getLength() > nFrom ) { sCleanTextContent = rText.copy( nFrom ); } mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "PlaceholderText" ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aPos.X() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPos.Y() ) ); { SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, aXMLElemText, true, false ); // At least for the single slide case we need really to export placeholder text mrExport.GetDocHandler()->characters( sCleanTextContent ); } } } if( !bIsPlaceholderField ) { if( nLen > 1 ) { aNormSize.setWidth( aTmpArray[ nLen - 2 ] + mpVDev->GetTextWidth( OUString(rText[nLen - 1]) ) ); if( nWidth && aNormSize.Width() && ( nWidth != aNormSize.Width() ) ) { tools::Long i; const double fFactor = static_cast(nWidth) / aNormSize.Width(); for( i = 0; i < ( nLen - 1 ); i++ ) aTmpArray.set(i, FRound(aTmpArray[i] * fFactor)); } else { css::uno::Reference< css::i18n::XBreakIterator > xBI( vcl::unohelper::CreateBreakIterator() ); const css::lang::Locale& rLocale = Application::GetSettings().GetLanguageTag().getLocale(); sal_Int32 nCurPos = 0, nLastPos = 0, nX = aPos.X(); // write single glyphs at absolute text positions for( bool bCont = true; bCont; ) { sal_Int32 nCount = 1; nLastPos = nCurPos; nCurPos = xBI->nextCharacters( rText, nCurPos, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount ); nCount = nCurPos - nLastPos; bCont = ( nCurPos < rText.getLength() ) && nCount; if( nCount ) { const OUString aGlyph( rText.copy( nLastPos, nCount ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( nX ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPos.Y() ) ); { SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, aXMLElemText, true, false ); mrExport.GetDocHandler()->characters( aGlyph ); } if( bCont ) { // #118796# do NOT access pDXArray, it may be zero (!) sal_Int32 nDXWidth = aTmpArray[ nCurPos - 1 ]; nDXWidth = ImplMap( nDXWidth ); nX = aPos.X() + nDXWidth; } } } } } else { mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aPos.X() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPos.Y() ) ); { SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, aXMLElemText, true, false ); mrExport.GetDocHandler()->characters( rText ); } } } if( mrExport.IsUseNativeTextDecoration() ) return; if( rFont.GetStrikeout() == STRIKEOUT_NONE && rFont.GetUnderline() == LINESTYLE_NONE ) return; tools::Polygon aPoly( 4 ); const tools::Long nLineHeight = std::max( FRound( aMetric.GetLineHeight() * 0.05 ), 1 ); if( rFont.GetStrikeout() ) { const tools::Long nYLinePos = aBaseLinePos.Y() - FRound( aMetric.GetAscent() * 0.26 ); aPoly[ 0 ].setX( aBaseLinePos.X() ); aPoly[ 0 ].setY( nYLinePos - ( nLineHeight >> 1 ) ); aPoly[ 1 ].setX( aBaseLinePos.X() + aNormSize.Width() - 1 ); aPoly[ 1 ].setY( aPoly[ 0 ].Y() ); 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( tools::PolyPolygon(aPoly), false ); } if( rFont.GetUnderline() ) { const tools::Long nYLinePos = aBaseLinePos.Y() + ( nLineHeight << 1 ); aPoly[ 0 ].setX( aBaseLinePos.X() ); aPoly[ 0 ].setY( nYLinePos - ( nLineHeight >> 1 ) ); aPoly[ 1 ].setX( aBaseLinePos.X() + aNormSize.Width() - 1 ); aPoly[ 1 ].setY( aPoly[ 0 ].Y() ); 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( tools::PolyPolygon(aPoly), false ); } } namespace { void GetGraphicFromXShape(const css::uno::Reference* pShape, Graphic& rGraphic) { if (!pShape) { return; } uno::Reference xPropertySet(*pShape, uno::UNO_QUERY); if (!xPropertySet.is()) { return; } uno::Reference xGraphic; if (xPropertySet->getPropertySetInfo()->hasPropertyByName("Graphic")) { xPropertySet->getPropertyValue("Graphic") >>= xGraphic; } rGraphic= Graphic(xGraphic); } } void SVGActionWriter::ImplWriteBmp( const BitmapEx& rBmpEx, const Point& rPt, const Size& rSz, const Point& rSrcPt, const Size& rSrcSz, const css::uno::Reference* pShape ) { if( rBmpEx.IsEmpty() ) return; if( mpEmbeddedBitmapsMap && !mpEmbeddedBitmapsMap->empty()) { BitmapChecksum nChecksum = rBmpEx.GetChecksum(); if( mpEmbeddedBitmapsMap->find( nChecksum ) != mpEmbeddedBitmapsMap->end() ) { // OUString sTransform; Point aPoint; ImplMap( rPt, aPoint ); if( aPoint.X() != 0 || aPoint.Y() != 0 ) sTransform = "translate(" + OUString::number( aPoint.X() ) + ", " + OUString::number( aPoint.Y() ) + ")"; Size aSize; ImplMap( rSz, aSize ); MapMode aSourceMode( MapUnit::MapPixel ); Size aPrefSize = OutputDevice::LogicToLogic( rSrcSz, aSourceMode, maTargetMapMode ); Fraction aFractionX( aSize.Width(), aPrefSize.Width() ); Fraction aFractionY( aSize.Height(), aPrefSize.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() ) mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTransform, sTransform ); // referenced bitmap template OUString sRefId = "#bitmap(" + OUString::number( nChecksum ) + ")"; mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId ); SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true ); return; } } BitmapEx aBmpEx( rBmpEx ); const tools::Rectangle aBmpRect( Point(), rBmpEx.GetSizePixel() ); const tools::Rectangle aSrcRect( rSrcPt, rSrcSz ); if( aSrcRect != aBmpRect ) aBmpEx.Crop( aSrcRect ); if( aBmpEx.IsEmpty() ) return; SvMemoryStream aOStm( 65535, 65535 ); bool bCached = false; Graphic aGraphic; bool bJPG = false; if (pShape) { GetGraphicFromXShape(pShape, aGraphic); if (aGraphic.GetType() == GraphicType::Bitmap) { const BitmapEx& rGraphicBitmap = aGraphic.GetBitmapExRef(); if (rGraphicBitmap == rBmpEx) { bool bPNG = false; GfxLink aGfxLink = aGraphic.GetGfxLink(); if (aGfxLink.GetType() == GfxLinkType::NativePng) { bPNG = true; } else if (aGfxLink.GetType() == GfxLinkType::NativeJpg) { bJPG = true; } if (bPNG || bJPG) { aOStm.WriteBytes(aGfxLink.GetData(), aGfxLink.GetDataSize()); bCached = true; } } } } const BitmapEx* pBitmap = &rBmpEx; std::unique_ptr pNewBitmap; // for preview we generate downscaled images (1280x720 max) if (mbIsPreview) { Size aSize = rBmpEx.GetSizePixel(); double fX = static_cast(aSize.getWidth()) / 1280; double fY = static_cast(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(rBmpEx); pNewBitmap->Scale(aSize); pBitmap = pNewBitmap.get(); } } if( !(bCached || GraphicConverter::Export( aOStm, *pBitmap, ConvertDataFormat::PNG ) == ERRCODE_NONE) ) return; Point aPt; Size aSz; Sequence< sal_Int8 > aSeq( static_cast(aOStm.GetData()), aOStm.Tell() ); OUStringBuffer aBuffer; if (bJPG) { aBuffer.append("data:image/jpeg;base64,"); } else { aBuffer.append("data:image/png;base64,"); } ::comphelper::Base64::encode( aBuffer, aSeq ); ImplMap( rPt, aPt ); ImplMap( rSz, aSz ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aPt.X() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPt.Y() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrWidth, OUString::number( aSz.Width() ) ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrHeight, OUString::number( aSz.Height() ) ); // 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(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()); SvXMLElementExport aElem(mrExport, XML_NAMESPACE_NONE, "image", true, true); } else { // // // // // 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); } } void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf, sal_uInt32 nWriteFlags, const OUString& aElementId, const Reference< css::drawing::XShape >* pxShape, const GDIMetaFile* pTextEmbeddedBitmapMtf ) { // need a counter for the actions written per shape to avoid double ID // generation sal_Int32 nEntryCount(0); bool bUseElementId = !aElementId.isEmpty(); #if OSL_DEBUG_LEVEL > 0 bool bIsTextShape = false; if( !mrExport.IsUsePositionedCharacters() && pxShape && Reference< XText >( *pxShape, UNO_QUERY ).is() ) { bIsTextShape = true; } #endif mbIsPlaceholderShape = false; if( bUseElementId && ( aElementId == sPlaceholderTag ) ) { mbIsPlaceholderShape = true; // since we utilize aElementId in an improper way we reset the boolean // control variable bUseElementId to false before to go on bUseElementId = false; } for( size_t nCurAction = 0, nCount = rMtf.GetActionSize(); nCurAction < nCount; nCurAction++ ) { const MetaAction* pAction = rMtf.GetAction( nCurAction ); const MetaActionType nType = pAction->GetType(); #if OSL_DEBUG_LEVEL > 0 if( bIsTextShape ) { try { SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "desc", false, false ); OUStringBuffer sType(OUString::number(static_cast(nType))); if (pAction && (nType == MetaActionType::COMMENT)) { sType.append(": "); const MetaCommentAction* pA = static_cast(pAction); OString sComment = pA->GetComment(); if (!sComment.isEmpty()) { sType.append(OStringToOUString( sComment, RTL_TEXTENCODING_UTF8)); } if (sComment.equalsIgnoreAsciiCase("FIELD_SEQ_BEGIN")) { sal_uInt8 const*const pData = pA->GetData(); if (pData && (pA->GetDataSize())) { sal_uInt16 sz = static_cast((pA->GetDataSize()) / 2); if (sz) { sType.append(OUString::Concat("; ") + std::u16string_view( reinterpret_cast(pData), sz)); } } } } if (sType.getLength()) { mrExport.GetDocHandler()->characters( sType.makeStringAndClear()); } } catch( ... ) { const MetaCommentAction* pA = static_cast(pAction); SAL_WARN( "filter.svg", pA->GetComment() ); } } #endif switch( nType ) { case MetaActionType::PIXEL: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaPixelAction* pA = static_cast(pAction); maAttributeWriter.AddPaintAttr( pA->GetColor(), pA->GetColor() ); ImplWriteLine( pA->GetPoint(), pA->GetPoint(), &pA->GetColor() ); } } break; case MetaActionType::POINT: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaPointAction* pA = static_cast(pAction); maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetLineColor() ); ImplWriteLine( pA->GetPoint(), pA->GetPoint() ); } } break; case MetaActionType::LINE: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaLineAction* pA = static_cast(pAction); maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetLineColor() ); ImplWriteLine( pA->GetStartPoint(), pA->GetEndPoint() ); } } break; case MetaActionType::RECT: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() ); ImplWriteRect( static_cast(pAction)->GetRect() ); } } break; case MetaActionType::ROUNDRECT: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaRoundRectAction* pA = static_cast(pAction); maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() ); ImplWriteRect( pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound() ); } } break; case MetaActionType::ELLIPSE: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaEllipseAction* pA = static_cast(pAction); const tools::Rectangle& rRect = pA->GetRect(); maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() ); ImplWriteEllipse( rRect.Center(), rRect.GetWidth() >> 1, rRect.GetHeight() >> 1 ); } } break; case MetaActionType::ARC: case MetaActionType::PIE: case MetaActionType::CHORD: case MetaActionType::POLYGON: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { tools::Polygon aPoly; switch( nType ) { case MetaActionType::ARC: { const MetaArcAction* pA = static_cast(pAction); aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Arc ); } break; case MetaActionType::PIE: { const MetaPieAction* pA = static_cast(pAction); aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Pie ); } break; case MetaActionType::CHORD: { const MetaChordAction* pA = static_cast(pAction); aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Chord ); } break; case MetaActionType::POLYGON: aPoly = static_cast(pAction)->GetPolygon(); break; default: break; } if( aPoly.GetSize() ) { maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() ); ImplWritePolyPolygon( tools::PolyPolygon(aPoly), false ); } } } break; case MetaActionType::POLYLINE: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaPolyLineAction* pA = static_cast(pAction); const tools::Polygon& rPoly = pA->GetPolygon(); if( rPoly.GetSize() ) { maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), COL_TRANSPARENT ); ImplAddLineAttr( pA->GetLineInfo() ); ImplWritePolyPolygon( tools::PolyPolygon(rPoly), true ); } } } break; case MetaActionType::POLYPOLYGON: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaPolyPolygonAction* pA = static_cast(pAction); const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon(); if( rPolyPoly.Count() ) { maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() ); ImplWritePolyPolygon( rPolyPoly, false ); } } } break; case MetaActionType::GRADIENT: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaGradientAction* pA = static_cast(pAction); const tools::Polygon aRectPoly( pA->GetRect() ); const tools::PolyPolygon aRectPolyPoly( aRectPoly ); ImplWriteGradientEx( aRectPolyPoly, pA->GetGradient(), nWriteFlags, nullptr ); } } break; case MetaActionType::GRADIENTEX: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaGradientExAction* pA = static_cast(pAction); ImplWriteGradientEx( pA->GetPolyPolygon(), pA->GetGradient(), nWriteFlags, nullptr ); } } break; case MetaActionType::HATCH: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaHatchAction* pA = static_cast(pAction); ImplWritePattern( pA->GetPolyPolygon(), &pA->GetHatch(), nullptr, nWriteFlags ); } } break; case MetaActionType::Transparent: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaTransparentAction* pA = static_cast(pAction); const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon(); if( rPolyPoly.Count() ) { Color aNewLineColor( mpVDev->GetLineColor() ), aNewFillColor( mpVDev->GetFillColor() ); // 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( FRound( pA->GetTransparence() * 2.55 ) ) ); aNewFillColor.SetAlpha( 255 - sal::static_int_cast( FRound( pA->GetTransparence() * 2.55 ) ) ); maAttributeWriter.AddPaintAttr( aNewLineColor, aNewFillColor ); ImplWritePolyPolygon( rPolyPoly, false ); } } } break; case MetaActionType::FLOATTRANSPARENT: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaFloatTransparentAction* pA = static_cast(pAction); GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() ); ImplWriteMask( aTmpMtf, pA->GetPoint(), pA->GetSize(), pA->GetGradient(), nWriteFlags, pA->getSVGTransparencyColorStops() ); } } break; case MetaActionType::EPS: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaEPSAction* pA = static_cast(pAction); const GDIMetaFile& aGDIMetaFile( pA->GetSubstitute() ); bool bFound = false; for( sal_uInt32 k = 0, nCount2 = aGDIMetaFile.GetActionSize(); ( k < nCount2 ) && !bFound; ++k ) { const MetaAction* pSubstAct = aGDIMetaFile.GetAction( k ); if( pSubstAct->GetType() == MetaActionType::BMPSCALE ) { bFound = true; const MetaBmpScaleAction* pBmpScaleAction = static_cast(pSubstAct); ImplWriteBmp( BitmapEx(pBmpScaleAction->GetBitmap()), pA->GetPoint(), pA->GetSize(), Point(), pBmpScaleAction->GetBitmap().GetSizePixel(), pxShape ); } } } } break; case MetaActionType::COMMENT: { const MetaCommentAction* pA = static_cast(pAction); 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(pAction); } else if (MetaActionType::COMMENT == pAction->GetType() && static_cast(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(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; bool bDone = false; while( !bDone && ( ++nCurAction < nCount ) ) { pAction = rMtf.GetAction( nCurAction ); if( pAction->GetType() == MetaActionType::GRADIENTEX ) pGradAction = static_cast(pAction); else if( ( pAction->GetType() == MetaActionType::COMMENT ) && ( static_cast( pAction )->GetComment(). equalsIgnoreAsciiCase("XGRAD_SEQ_END") ) ) { bDone = true; } } if( pGradAction ) ImplWriteGradientEx( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), nWriteFlags, nullptr ); } else if( ( pA->GetComment().equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN") ) && ( nWriteFlags & SVGWRITER_WRITE_FILL ) && !( nWriteFlags & SVGWRITER_NO_SHAPE_COMMENTS ) && pA->GetDataSize() ) { // write open shape in every case if (mapCurShape) { ImplWriteShape( *mapCurShape ); mapCurShape.reset(); } SvMemoryStream aMemStm( const_cast(pA->GetData()), pA->GetDataSize(), StreamMode::READ ); SvtGraphicFill aFill; ReadSvtGraphicFill( aMemStm, aFill ); bool bGradient = SvtGraphicFill::fillGradient == aFill.getFillType() && ( SvtGraphicFill::GradientType::Linear == aFill.getGradientType() || SvtGraphicFill::GradientType::Radial == aFill.getGradientType() ); bool bSkip = ( SvtGraphicFill::fillSolid == aFill.getFillType() || bGradient ); if( bSkip ) { tools::PolyPolygon aShapePolyPoly; aFill.getPath( aShapePolyPoly ); if( aShapePolyPoly.Count() ) { mapCurShape.reset( new SVGShapeDescriptor ); if( bUseElementId ) { mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++); } mapCurShape->maShapePolyPoly = aShapePolyPoly; mapCurShape->maShapeFillColor = aFill.getFillColor(); mapCurShape->maShapeFillColor.SetAlpha( 255 - static_cast(FRound( 255.0 * aFill.getTransparency() )) ); if( bGradient ) { // step through following actions until the first Gradient/GradientEx action is found while (!mapCurShape->moShapeGradient && bSkip && (++nCurAction < nCount)) { pAction = rMtf.GetAction( nCurAction ); if( ( pAction->GetType() == MetaActionType::COMMENT ) && ( static_cast(pAction)->GetComment(). equalsIgnoreAsciiCase("XPATHFILL_SEQ_END") ) ) { bSkip = false; } else if( pAction->GetType() == MetaActionType::GRADIENTEX ) { mapCurShape->moShapeGradient.emplace( static_cast< const MetaGradientExAction* >( pAction )->GetGradient() ); } else if( pAction->GetType() == MetaActionType::GRADIENT ) { mapCurShape->moShapeGradient.emplace( static_cast< const MetaGradientAction* >( pAction )->GetGradient() ); } } } } else bSkip = false; } // skip rest of comment while( bSkip && ( ++nCurAction < nCount ) ) { pAction = rMtf.GetAction( nCurAction ); if( ( pAction->GetType() == MetaActionType::COMMENT ) && ( static_cast( pAction )->GetComment(). equalsIgnoreAsciiCase("XPATHFILL_SEQ_END") ) ) { bSkip = false; } } } else if( ( pA->GetComment().equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_BEGIN") ) && ( nWriteFlags & SVGWRITER_WRITE_FILL ) && !( nWriteFlags & SVGWRITER_NO_SHAPE_COMMENTS ) && pA->GetDataSize() ) { SvMemoryStream aMemStm( const_cast(pA->GetData()), pA->GetDataSize(), StreamMode::READ ); SvtGraphicStroke aStroke; tools::PolyPolygon aStartArrow, aEndArrow; ReadSvtGraphicStroke( aMemStm, aStroke ); aStroke.getStartArrow( aStartArrow ); aStroke.getEndArrow( aEndArrow ); // Currently no support for strokes with start/end arrow(s) // added that support tools::Polygon aPoly; aStroke.getPath(aPoly); if (mapCurShape) { if(1 != mapCurShape->maShapePolyPoly.Count() || !mapCurShape->maShapePolyPoly[0].IsEqual(aPoly)) { // this path action is not covering the same path than the already existing // fill polypolygon, so write out the fill polygon ImplWriteShape( *mapCurShape ); mapCurShape.reset(); } } if (!mapCurShape) { mapCurShape.reset( new SVGShapeDescriptor ); if( bUseElementId ) { mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++); } mapCurShape->maShapePolyPoly = tools::PolyPolygon(aPoly); } mapCurShape->maShapeLineColor = mpVDev->GetLineColor(); mapCurShape->maShapeLineColor.SetAlpha( 255 - static_cast(FRound( aStroke.getTransparency() * 255.0 )) ); mapCurShape->mnStrokeWidth = FRound( aStroke.getStrokeWidth() ); aStroke.getDashArray( mapCurShape->maDashArray ); // added support for LineJoin switch(aStroke.getJoinType()) { default: /* SvtGraphicStroke::joinMiter, SvtGraphicStroke::joinNone */ { mapCurShape->maLineJoin = basegfx::B2DLineJoin::Miter; break; } case SvtGraphicStroke::joinRound: { mapCurShape->maLineJoin = basegfx::B2DLineJoin::Round; break; } case SvtGraphicStroke::joinBevel: { mapCurShape->maLineJoin = basegfx::B2DLineJoin::Bevel; break; } } // added support for LineCap switch(aStroke.getCapType()) { default: /* SvtGraphicStroke::capButt */ { mapCurShape->maLineCap = css::drawing::LineCap_BUTT; break; } case SvtGraphicStroke::capRound: { mapCurShape->maLineCap = css::drawing::LineCap_ROUND; break; } case SvtGraphicStroke::capSquare: { mapCurShape->maLineCap = css::drawing::LineCap_SQUARE; break; } } if (mapCurShape->maShapePolyPoly.Count() && (aStartArrow.Count() || aEndArrow.Count())) { ImplWriteShape( *mapCurShape ); mapCurShape->maShapeFillColor = mapCurShape->maShapeLineColor; mapCurShape->maShapeLineColor = COL_TRANSPARENT; mapCurShape->mnStrokeWidth = 0; mapCurShape->maDashArray.clear(); mapCurShape->maLineJoin = basegfx::B2DLineJoin::Miter; mapCurShape->maLineCap = css::drawing::LineCap_BUTT; if(aStartArrow.Count()) { mapCurShape->maShapePolyPoly = aStartArrow; if( bUseElementId ) // #i124825# aElementId is optional, may be zero { mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++); } ImplWriteShape( *mapCurShape ); } if(aEndArrow.Count()) { mapCurShape->maShapePolyPoly = aEndArrow; if( bUseElementId ) // #i124825# aElementId is optional, may be zero { mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++); } ImplWriteShape( *mapCurShape ); } mapCurShape.reset(); } // write open shape in every case if (mapCurShape) { ImplWriteShape( *mapCurShape ); mapCurShape.reset(); } // skip rest of comment bool bSkip = true; while( bSkip && ( ++nCurAction < nCount ) ) { pAction = rMtf.GetAction( nCurAction ); if( ( pAction->GetType() == MetaActionType::COMMENT ) && ( static_cast(pAction)->GetComment(). equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_END") ) ) { bSkip = false; } } } else if( !mrExport.IsUsePositionedCharacters() && ( nWriteFlags & SVGWRITER_WRITE_TEXT ) ) { if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_PAINTSHAPE_BEGIN" ) ) { if( pxShape ) { Reference< XText > xText( *pxShape, UNO_QUERY ); if( xText.is() ) maTextWriter.setTextShape( xText, pTextEmbeddedBitmapMtf ); } maTextWriter.createParagraphEnumeration(); { // nTextFound == -1 => no text found // nTextFound == 0 => no text found and end of text shape reached // nTextFound == 1 => text found! sal_Int32 nTextFound = -1; while( ( nTextFound < 0 ) && ( nCurAction < nCount ) ) { nTextFound = maTextWriter.setTextPosition(rMtf, nCurAction, nWriteFlags); } // We found some text in the current text shape. if( nTextFound > 0 ) { maTextWriter.setTextProperties( rMtf, nCurAction ); maTextWriter.startTextShape(); } // We reached the end of the current text shape // without finding any text. So we need to go back // by one action in order to handle the // XTEXT_PAINTSHAPE_END action because on the next // loop the nCurAction is incremented by one. else { --nCurAction; } } } else if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_PAINTSHAPE_END" ) ) { maTextWriter.endTextShape(); } else if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_EOP" ) ) { const MetaAction* pNextAction = rMtf.GetAction( nCurAction + 1 ); if( !( ( pNextAction->GetType() == MetaActionType::COMMENT ) && ( static_cast(pNextAction)->GetComment().equalsIgnoreAsciiCase("XTEXT_PAINTSHAPE_END") ) )) { // nTextFound == -1 => no text found and end of paragraph reached // nTextFound == 0 => no text found and end of text shape reached // nTextFound == 1 => text found! sal_Int32 nTextFound = -1; while( ( nTextFound < 0 ) && ( nCurAction < nCount ) ) { nTextFound = maTextWriter.setTextPosition(rMtf, nCurAction, nWriteFlags); } // We found a paragraph with some text in the // current text shape. if( nTextFound > 0 ) { maTextWriter.setTextProperties( rMtf, nCurAction ); maTextWriter.startTextParagraph(); } // We reached the end of the current text shape // without finding any text. So we need to go back // by one action in order to handle the // XTEXT_PAINTSHAPE_END action because on the next // loop the nCurAction is incremented by one. else { --nCurAction; } } } else if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_EOL" ) ) { const MetaAction* pNextAction = rMtf.GetAction( nCurAction + 1 ); if( !( ( pNextAction->GetType() == MetaActionType::COMMENT ) && ( static_cast(pNextAction)->GetComment().equalsIgnoreAsciiCase("XTEXT_EOP") ) ) ) { // nTextFound == -2 => no text found and end of line reached // nTextFound == -1 => no text found and end of paragraph reached // nTextFound == 1 => text found! sal_Int32 nTextFound = -2; while( ( nTextFound < -1 ) && ( nCurAction < nCount ) ) { nTextFound = maTextWriter.setTextPosition(rMtf, nCurAction, nWriteFlags); } // We found a line with some text in the current // paragraph. if( nTextFound > 0 ) { maTextWriter.startTextPosition(); } // We reached the end of the current paragraph // without finding any text. So we need to go back // by one action in order to handle the XTEXT_EOP // action because on the next loop the nCurAction is // incremented by one. else { --nCurAction; } } } } 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 element. // The comment content has the following format: "SLIDE_BACKGROUND " const OString& sComment = pA->GetComment(); OUString sRefId = "#" + OUString::fromUtf8( o3tl::getToken(sComment, 1, ' ') ); mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId ); SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true ); } } break; case MetaActionType::BMP: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaBmpAction* pA = static_cast(pAction); ImplWriteBmp( BitmapEx(pA->GetBitmap()), pA->GetPoint(), mpVDev->PixelToLogic( pA->GetBitmap().GetSizePixel() ), Point(), pA->GetBitmap().GetSizePixel(), pxShape ); } } break; case MetaActionType::BMPSCALE: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaBmpScaleAction* pA = static_cast(pAction); // Bitmaps embedded into text shapes are collected and exported elsewhere. if( maTextWriter.isTextShapeStarted() ) { maTextWriter.writeBitmapPlaceholder( pA ); } else { ImplWriteBmp( BitmapEx(pA->GetBitmap()), pA->GetPoint(), pA->GetSize(), Point(), pA->GetBitmap().GetSizePixel(), pxShape ); } } } break; case MetaActionType::BMPSCALEPART: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaBmpScalePartAction* pA = static_cast(pAction); ImplWriteBmp( BitmapEx(pA->GetBitmap()), pA->GetDestPoint(), pA->GetDestSize(), pA->GetSrcPoint(), pA->GetSrcSize(), pxShape ); } } break; case MetaActionType::BMPEX: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaBmpExAction* pA = static_cast(pAction); ImplWriteBmp( pA->GetBitmapEx(), pA->GetPoint(), mpVDev->PixelToLogic( pA->GetBitmapEx().GetSizePixel() ), Point(), pA->GetBitmapEx().GetSizePixel(), pxShape ); } } break; case MetaActionType::BMPEXSCALE: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaBmpExScaleAction* pA = static_cast(pAction); // Bitmaps embedded into text shapes are collected and exported elsewhere. if( maTextWriter.isTextShapeStarted() ) { maTextWriter.writeBitmapPlaceholder( pA ); } else { ImplWriteBmp( pA->GetBitmapEx(), pA->GetPoint(), pA->GetSize(), Point(), pA->GetBitmapEx().GetSizePixel(), pxShape ); } } } break; case MetaActionType::BMPEXSCALEPART: { if( nWriteFlags & SVGWRITER_WRITE_FILL ) { const MetaBmpExScalePartAction* pA = static_cast(pAction); ImplWriteBmp( pA->GetBitmapEx(), pA->GetDestPoint(), pA->GetDestSize(), pA->GetSrcPoint(), pA->GetSrcSize(), pxShape ); } } break; case MetaActionType::TEXT: { if( nWriteFlags & SVGWRITER_WRITE_TEXT ) { const MetaTextAction* pA = static_cast(pAction); sal_Int32 aLength = std::min( pA->GetText().getLength(), pA->GetLen() ); const OUString aText = pA->GetText().copy( pA->GetIndex(), aLength ); if( !aText.isEmpty() ) { if( mrExport.IsUsePositionedCharacters() ) { vcl::Font aFont = ImplSetCorrectFontHeight(); maAttributeWriter.SetFontAttr( aFont ); ImplWriteText( pA->GetPoint(), aText, {}, 0 ); } else { maTextWriter.writeTextPortion( pA->GetPoint(), aText ); } } } } break; case MetaActionType::TEXTRECT: { if( nWriteFlags & SVGWRITER_WRITE_TEXT ) { const MetaTextRectAction* pA = static_cast(pAction); if (!pA->GetText().isEmpty()) { if( mrExport.IsUsePositionedCharacters() ) { vcl::Font aFont = ImplSetCorrectFontHeight(); maAttributeWriter.SetFontAttr( aFont ); ImplWriteText( pA->GetRect().TopLeft(), pA->GetText(), {}, 0 ); } maTextWriter.writeTextPortion( pA->GetRect().TopLeft(), pA->GetText() ); } } } break; case MetaActionType::TEXTARRAY: { if( nWriteFlags & SVGWRITER_WRITE_TEXT ) { const MetaTextArrayAction* pA = static_cast(pAction); sal_Int32 aLength = std::min( pA->GetText().getLength(), pA->GetLen() ); const OUString aText = pA->GetText().copy( pA->GetIndex(), aLength ); if( !aText.isEmpty() ) { if( mrExport.IsUsePositionedCharacters() ) { vcl::Font aFont = ImplSetCorrectFontHeight(); maAttributeWriter.SetFontAttr( aFont ); ImplWriteText( pA->GetPoint(), aText, pA->GetDXArray(), 0 ); } else { maTextWriter.writeTextPortion( pA->GetPoint(), aText ); } } } } break; case MetaActionType::STRETCHTEXT: { if( nWriteFlags & SVGWRITER_WRITE_TEXT ) { const MetaStretchTextAction* pA = static_cast(pAction); sal_Int32 aLength = std::min( pA->GetText().getLength(), pA->GetLen() ); const OUString aText = pA->GetText().copy( pA->GetIndex(), aLength ); if( !aText.isEmpty() ) { if( mrExport.IsUsePositionedCharacters() ) { vcl::Font aFont = ImplSetCorrectFontHeight(); maAttributeWriter.SetFontAttr( aFont ); ImplWriteText( pA->GetPoint(), aText, {}, pA->GetWidth() ); } else { maTextWriter.writeTextPortion( pA->GetPoint(), aText ); } } } } break; case MetaActionType::CLIPREGION: case MetaActionType::ISECTRECTCLIPREGION: case MetaActionType::ISECTREGIONCLIPREGION: case MetaActionType::MOVECLIPREGION: { const_cast(pAction)->Execute( mpVDev ); const vcl::Region& rClipRegion = mpVDev->GetActiveClipRegion(); ImplWriteClipPath( rClipRegion.GetAsPolyPolygon() ); mbClipAttrChanged = true; } break; case MetaActionType::PUSH: { const MetaPushAction* pA = static_cast(pAction); vcl::PushFlags mnFlags = pA->GetFlags(); const_cast(pAction)->Execute( mpVDev ); maContextHandler.pushState( mnFlags ); } break; case MetaActionType::POP: { const_cast(pAction)->Execute( mpVDev ); vcl::PushFlags mnFlags = maContextHandler.getPushFlags(); maContextHandler.popState(); if( mnFlags & vcl::PushFlags::CLIPREGION ) { ImplEndClipRegion(); ImplStartClipRegion( mrCurrentState.nRegionClipPathId ); } } break; case MetaActionType::REFPOINT: case MetaActionType::MAPMODE: case MetaActionType::LINECOLOR: case MetaActionType::FILLCOLOR: case MetaActionType::TEXTLINECOLOR: case MetaActionType::TEXTFILLCOLOR: case MetaActionType::TEXTCOLOR: case MetaActionType::TEXTALIGN: case MetaActionType::FONT: case MetaActionType::LAYOUTMODE: { const_cast(pAction)->Execute( mpVDev ); } break; case MetaActionType::RASTEROP: case MetaActionType::MASK: case MetaActionType::MASKSCALE: case MetaActionType::MASKSCALEPART: case MetaActionType::WALLPAPER: case MetaActionType::TEXTLINE: case MetaActionType::TEXTLANGUAGE: { // !!! >>> we don't want to support these actions } break; default: SAL_WARN("filter.svg", "SVGActionWriter::ImplWriteActions: unsupported MetaAction # " << sal_Int32(nType)); break; } } } vcl::Font SVGActionWriter::ImplSetCorrectFontHeight() const { vcl::Font aFont( mpVDev->GetFont() ); Size aSz; ImplMap( Size( 0, aFont.GetFontHeight() ), aSz ); aFont.SetFontHeight( aSz.Height() ); return aFont; } void SVGActionWriter::WriteMetaFile( const Point& rPos100thmm, const Size& rSize100thmm, const GDIMetaFile& rMtf, sal_uInt32 nWriteFlags, const OUString& aElementId, const Reference< css::drawing::XShape >* pXShape, const GDIMetaFile* pTextEmbeddedBitmapMtf ) { MapMode aMapMode( rMtf.GetPrefMapMode() ); Size aPrefSize( rMtf.GetPrefSize() ); Fraction aFractionX( aMapMode.GetScaleX() ); Fraction aFractionY( aMapMode.GetScaleY() ); mpVDev->Push(); Size aSize( OutputDevice::LogicToLogic(rSize100thmm, MapMode(MapUnit::Map100thMM), aMapMode) ); aFractionX *= Fraction( aSize.Width(), aPrefSize.Width() ); aMapMode.SetScaleX( aFractionX ); aFractionY *= Fraction( aSize.Height(), aPrefSize.Height() ); aMapMode.SetScaleY( aFractionY ); Point aOffset( OutputDevice::LogicToLogic(rPos100thmm, MapMode(MapUnit::Map100thMM), aMapMode ) ); aOffset += aMapMode.GetOrigin(); aMapMode.SetOrigin( aOffset ); mpVDev->SetMapMode( aMapMode ); mapCurShape.reset(); ImplWriteActions( rMtf, nWriteFlags, aElementId, pXShape, pTextEmbeddedBitmapMtf ); maTextWriter.endTextParagraph(); ImplEndClipRegion(); // draw open shape that doesn't have a border if (mapCurShape) { ImplWriteShape( *mapCurShape ); mapCurShape.reset(); } mpVDev->Pop(); } SVGWriter::SVGWriter( const Sequence& args, const Reference< XComponentContext >& rxCtx ) : mxContext(rxCtx) { if(args.getLength()==1) args[0]>>=maFilterData; } SVGWriter::~SVGWriter() { } void SAL_CALL SVGWriter::write( const Reference& rxDocHandler, const Sequence& rMtfSeq ) { SvMemoryStream aMemStm( const_cast(rMtfSeq.getConstArray()), rMtfSeq.getLength(), StreamMode::READ ); GDIMetaFile aMtf; SvmReader aReader( aMemStm ); aReader.Read( aMtf ); rtl::Reference pWriter(new SVGExport( mxContext, rxDocHandler, maFilterData )); pWriter->writeMtf( aMtf ); } // XServiceInfo sal_Bool SVGWriter::supportsService(const OUString& sServiceName) { return cppu::supportsService(this, sServiceName); } OUString SVGWriter::getImplementationName() { return "com.sun.star.comp.Draw.SVGWriter"; } css::uno::Sequence< OUString > SVGWriter::getSupportedServiceNames() { return { "com.sun.star.svg.SVGWriter" }; } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* filter_SVGWriter_get_implementation( css::uno::XComponentContext* context, css::uno::Sequence const& args) { return cppu::acquire(new SVGWriter(args, context)); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */