1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_canvas.hxx" 26 27 #include <canvas/debug.hxx> 28 #include <tools/diagnose_ex.h> 29 30 #include <rtl/logfile.hxx> 31 #include <rtl/math.hxx> 32 33 #include <com/sun/star/rendering/TexturingMode.hpp> 34 #include <com/sun/star/rendering/CompositeOperation.hpp> 35 #include <com/sun/star/rendering/RepaintResult.hpp> 36 #include <com/sun/star/rendering/PathCapType.hpp> 37 #include <com/sun/star/rendering/PathJoinType.hpp> 38 39 #include <basegfx/matrix/b2dhommatrix.hxx> 40 #include <basegfx/point/b2dpoint.hxx> 41 #include <basegfx/tools/canvastools.hxx> 42 #include <basegfx/matrix/b2dhommatrixtools.hxx> 43 44 #include <comphelper/sequence.hxx> 45 #include <canvas/canvastools.hxx> 46 47 #include "dx_spritecanvas.hxx" 48 #include "dx_impltools.hxx" 49 #include "dx_vcltools.hxx" 50 #include "dx_canvasfont.hxx" 51 #include "dx_textlayout.hxx" 52 #include "dx_canvashelper.hxx" 53 54 #include <algorithm> 55 56 57 using namespace ::com::sun::star; 58 59 namespace dxcanvas 60 { 61 namespace 62 { gdiCapFromCap(sal_Int8 nCapType)63 Gdiplus::LineCap gdiCapFromCap( sal_Int8 nCapType ) 64 { 65 switch( nCapType ) 66 { 67 case rendering::PathCapType::BUTT: 68 return Gdiplus::LineCapFlat; 69 70 case rendering::PathCapType::ROUND: 71 return Gdiplus::LineCapRound; 72 73 case rendering::PathCapType::SQUARE: 74 return Gdiplus::LineCapSquare; 75 76 default: 77 ENSURE_OR_THROW( false, 78 "gdiCapFromCap(): Unexpected cap type" ); 79 } 80 81 return Gdiplus::LineCapFlat; 82 } 83 gdiJoinFromJoin(sal_Int8 nJoinType)84 Gdiplus::LineJoin gdiJoinFromJoin( sal_Int8 nJoinType ) 85 { 86 switch( nJoinType ) 87 { 88 case rendering::PathJoinType::NONE: 89 OSL_ENSURE( false, 90 "gdiJoinFromJoin(): Join NONE not possible, mapping to MITER" ); 91 // FALLTHROUGH intended 92 case rendering::PathJoinType::MITER: 93 return Gdiplus::LineJoinMiter; 94 95 case rendering::PathJoinType::ROUND: 96 return Gdiplus::LineJoinRound; 97 98 case rendering::PathJoinType::BEVEL: 99 return Gdiplus::LineJoinBevel; 100 101 default: 102 ENSURE_OR_THROW( false, 103 "gdiJoinFromJoin(): Unexpected join type" ); 104 } 105 106 return Gdiplus::LineJoinMiter; 107 } 108 } 109 CanvasHelper()110 CanvasHelper::CanvasHelper() : 111 mpGdiPlusUser( GDIPlusUser::createInstance() ), 112 mpDevice( NULL ), 113 mpGraphicsProvider(), 114 maOutputOffset() 115 { 116 } 117 disposing()118 void CanvasHelper::disposing() 119 { 120 mpGraphicsProvider.reset(); 121 mpDevice = NULL; 122 mpGdiPlusUser.reset(); 123 } 124 setDevice(rendering::XGraphicDevice & rDevice)125 void CanvasHelper::setDevice( rendering::XGraphicDevice& rDevice ) 126 { 127 mpDevice = &rDevice; 128 } 129 setTarget(const GraphicsProviderSharedPtr & rTarget)130 void CanvasHelper::setTarget( const GraphicsProviderSharedPtr& rTarget ) 131 { 132 ENSURE_OR_THROW( rTarget, 133 "CanvasHelper::setTarget(): Invalid target" ); 134 ENSURE_OR_THROW( !mpGraphicsProvider.get(), 135 "CanvasHelper::setTarget(): target set, old target would be overwritten" ); 136 137 mpGraphicsProvider = rTarget; 138 } 139 setTarget(const GraphicsProviderSharedPtr & rTarget,const::basegfx::B2ISize & rOutputOffset)140 void CanvasHelper::setTarget( const GraphicsProviderSharedPtr& rTarget, 141 const ::basegfx::B2ISize& rOutputOffset ) 142 { 143 ENSURE_OR_THROW( rTarget, 144 "CanvasHelper::setTarget(): invalid target" ); 145 ENSURE_OR_THROW( !mpGraphicsProvider.get(), 146 "CanvasHelper::setTarget(): target set, old target would be overwritten" ); 147 148 mpGraphicsProvider = rTarget; 149 maOutputOffset = rOutputOffset; 150 } 151 clear()152 void CanvasHelper::clear() 153 { 154 if( needOutput() ) 155 { 156 GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); 157 Gdiplus::Color aClearColor = Gdiplus::Color((Gdiplus::ARGB)Gdiplus::Color::White); 158 159 ENSURE_OR_THROW( 160 Gdiplus::Ok == pGraphics->SetCompositingMode( 161 Gdiplus::CompositingModeSourceCopy ), // force set, don't blend 162 "CanvasHelper::clear(): GDI+ SetCompositingMode call failed" ); 163 ENSURE_OR_THROW( 164 Gdiplus::Ok == pGraphics->Clear( aClearColor ), 165 "CanvasHelper::clear(): GDI+ Clear call failed" ); 166 } 167 } 168 drawPoint(const rendering::XCanvas *,const geometry::RealPoint2D & aPoint,const rendering::ViewState & viewState,const rendering::RenderState & renderState)169 void CanvasHelper::drawPoint( const rendering::XCanvas* /*pCanvas*/, 170 const geometry::RealPoint2D& aPoint, 171 const rendering::ViewState& viewState, 172 const rendering::RenderState& renderState ) 173 { 174 if( needOutput() ) 175 { 176 GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); 177 178 setupGraphicsState( pGraphics, viewState, renderState ); 179 180 Gdiplus::SolidBrush aBrush( 181 Gdiplus::Color( 182 tools::sequenceToArgb(renderState.DeviceColor)) ); 183 184 // determine size of one-by-one device pixel ellipse 185 Gdiplus::Matrix aMatrix; 186 pGraphics->GetTransform(&aMatrix); 187 aMatrix.Invert(); 188 Gdiplus::PointF vector(1, 1); 189 aMatrix.TransformVectors(&vector); 190 191 // paint a one-by-one circle, with the given point 192 // in the middle (rounded to float) 193 ENSURE_OR_THROW( 194 Gdiplus::Ok == pGraphics->FillEllipse( &aBrush, 195 // disambiguate call 196 Gdiplus::REAL(aPoint.X), 197 Gdiplus::REAL(aPoint.Y), 198 Gdiplus::REAL(vector.X), 199 Gdiplus::REAL(vector.Y) ), 200 "CanvasHelper::drawPoint(): GDI+ call failed" ); 201 } 202 } 203 drawLine(const rendering::XCanvas *,const geometry::RealPoint2D & aStartPoint,const geometry::RealPoint2D & aEndPoint,const rendering::ViewState & viewState,const rendering::RenderState & renderState)204 void CanvasHelper::drawLine( const rendering::XCanvas* /*pCanvas*/, 205 const geometry::RealPoint2D& aStartPoint, 206 const geometry::RealPoint2D& aEndPoint, 207 const rendering::ViewState& viewState, 208 const rendering::RenderState& renderState ) 209 { 210 if( needOutput() ) 211 { 212 GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); 213 214 setupGraphicsState( pGraphics, viewState, renderState ); 215 216 Gdiplus::Pen aPen( 217 Gdiplus::Color( 218 tools::sequenceToArgb(renderState.DeviceColor)), 219 Gdiplus::REAL(0.0) ); 220 221 // #122683# Switched precedence of pixel offset 222 // mode. Seemingly, polygon stroking needs 223 // PixelOffsetModeNone to achieve visually pleasing 224 // results, whereas all other operations (e.g. polygon 225 // fills, bitmaps) look better with PixelOffsetModeHalf. 226 const Gdiplus::PixelOffsetMode aOldMode( 227 pGraphics->GetPixelOffsetMode() ); 228 pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); 229 230 Gdiplus::Status hr = pGraphics->DrawLine( &aPen, 231 Gdiplus::REAL(aStartPoint.X), // disambiguate call 232 Gdiplus::REAL(aStartPoint.Y), 233 Gdiplus::REAL(aEndPoint.X), 234 Gdiplus::REAL(aEndPoint.Y) ); 235 pGraphics->SetPixelOffsetMode( aOldMode ); 236 237 ENSURE_OR_THROW( 238 Gdiplus::Ok == hr, 239 "CanvasHelper::drawLine(): GDI+ call failed" ); 240 } 241 } 242 drawBezier(const rendering::XCanvas *,const geometry::RealBezierSegment2D & aBezierSegment,const geometry::RealPoint2D & aEndPoint,const rendering::ViewState & viewState,const rendering::RenderState & renderState)243 void CanvasHelper::drawBezier( const rendering::XCanvas* /*pCanvas*/, 244 const geometry::RealBezierSegment2D& aBezierSegment, 245 const geometry::RealPoint2D& aEndPoint, 246 const rendering::ViewState& viewState, 247 const rendering::RenderState& renderState ) 248 { 249 if( needOutput() ) 250 { 251 GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); 252 253 setupGraphicsState( pGraphics, viewState, renderState ); 254 255 Gdiplus::Pen aPen( 256 Gdiplus::Color( 257 tools::sequenceToArgb(renderState.DeviceColor)), 258 Gdiplus::REAL(0.0) ); 259 260 // #122683# Switched precedence of pixel offset 261 // mode. Seemingly, polygon stroking needs 262 // PixelOffsetModeNone to achieve visually pleasing 263 // results, whereas all other operations (e.g. polygon 264 // fills, bitmaps) look better with PixelOffsetModeHalf. 265 const Gdiplus::PixelOffsetMode aOldMode( 266 pGraphics->GetPixelOffsetMode() ); 267 pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); 268 269 Gdiplus::Status hr = pGraphics->DrawBezier( &aPen, 270 Gdiplus::REAL(aBezierSegment.Px), // disambiguate call 271 Gdiplus::REAL(aBezierSegment.Py), 272 Gdiplus::REAL(aBezierSegment.C1x), 273 Gdiplus::REAL(aBezierSegment.C1y), 274 Gdiplus::REAL(aEndPoint.X), 275 Gdiplus::REAL(aEndPoint.Y), 276 Gdiplus::REAL(aBezierSegment.C2x), 277 Gdiplus::REAL(aBezierSegment.C2y) ); 278 279 pGraphics->SetPixelOffsetMode( aOldMode ); 280 281 ENSURE_OR_THROW( 282 Gdiplus::Ok == hr, 283 "CanvasHelper::drawBezier(): GDI+ call failed" ); 284 } 285 } 286 drawPolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> & xPolyPolygon,const rendering::ViewState & viewState,const rendering::RenderState & renderState)287 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas* /*pCanvas*/, 288 const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, 289 const rendering::ViewState& viewState, 290 const rendering::RenderState& renderState ) 291 { 292 ENSURE_OR_THROW( xPolyPolygon.is(), 293 "CanvasHelper::drawPolyPolygon: polygon is NULL"); 294 295 if( needOutput() ) 296 { 297 GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); 298 299 setupGraphicsState( pGraphics, viewState, renderState ); 300 301 Gdiplus::Pen aPen( 302 Gdiplus::Color( 303 tools::sequenceToArgb(renderState.DeviceColor)), 304 Gdiplus::REAL(0.0) ); 305 306 // #122683# Switched precedence of pixel offset 307 // mode. Seemingly, polygon stroking needs 308 // PixelOffsetModeNone to achieve visually pleasing 309 // results, whereas all other operations (e.g. polygon 310 // fills, bitmaps) look better with PixelOffsetModeHalf. 311 const Gdiplus::PixelOffsetMode aOldMode( 312 pGraphics->GetPixelOffsetMode() ); 313 pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); 314 315 GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ) ); 316 317 // TODO(E1): Return value 318 Gdiplus::Status hr = pGraphics->DrawPath( &aPen, pPath.get() ); 319 320 pGraphics->SetPixelOffsetMode( aOldMode ); 321 322 ENSURE_OR_THROW( 323 Gdiplus::Ok == hr, 324 "CanvasHelper::drawPolyPolygon(): GDI+ call failed" ); 325 } 326 327 // TODO(P1): Provide caching here. 328 return uno::Reference< rendering::XCachedPrimitive >(NULL); 329 } 330 strokePolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> & xPolyPolygon,const rendering::ViewState & viewState,const rendering::RenderState & renderState,const rendering::StrokeAttributes & strokeAttributes)331 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas* /*pCanvas*/, 332 const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, 333 const rendering::ViewState& viewState, 334 const rendering::RenderState& renderState, 335 const rendering::StrokeAttributes& strokeAttributes ) 336 { 337 ENSURE_OR_THROW( xPolyPolygon.is(), 338 "CanvasHelper::drawPolyPolygon: polygon is NULL"); 339 340 if( needOutput() ) 341 { 342 GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); 343 344 setupGraphicsState( pGraphics, viewState, renderState ); 345 346 347 // Setup stroke pen 348 // ---------------- 349 350 Gdiplus::Pen aPen( 351 Gdiplus::Color( 352 tools::sequenceToArgb(renderState.DeviceColor)), 353 static_cast< Gdiplus::REAL >(strokeAttributes.StrokeWidth) ); 354 355 // #122683# Switched precedence of pixel offset 356 // mode. Seemingly, polygon stroking needs 357 // PixelOffsetModeNone to achieve visually pleasing 358 // results, whereas all other operations (e.g. polygon 359 // fills, bitmaps) look better with PixelOffsetModeHalf. 360 const Gdiplus::PixelOffsetMode aOldMode( 361 pGraphics->GetPixelOffsetMode() ); 362 pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); 363 364 const bool bIsMiter(rendering::PathJoinType::MITER == strokeAttributes.JoinType); 365 const bool bIsNone(rendering::PathJoinType::NONE == strokeAttributes.JoinType); 366 367 if(bIsMiter) 368 aPen.SetMiterLimit( static_cast< Gdiplus::REAL >(strokeAttributes.MiterLimit) ); 369 370 const ::std::vector< Gdiplus::REAL >& rDashArray( 371 ::comphelper::sequenceToContainer< ::std::vector< Gdiplus::REAL > >( 372 strokeAttributes.DashArray ) ); 373 if( !rDashArray.empty() ) 374 { 375 aPen.SetDashPattern( &rDashArray[0], 376 rDashArray.size() ); 377 } 378 aPen.SetLineCap( gdiCapFromCap(strokeAttributes.StartCapType), 379 gdiCapFromCap(strokeAttributes.EndCapType), 380 Gdiplus::DashCapFlat ); 381 if(!bIsNone) 382 aPen.SetLineJoin( gdiJoinFromJoin(strokeAttributes.JoinType) ); 383 384 GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon, bIsNone ) ); 385 386 // TODO(E1): Return value 387 Gdiplus::Status hr = pGraphics->DrawPath( &aPen, pPath.get() ); 388 389 pGraphics->SetPixelOffsetMode( aOldMode ); 390 391 ENSURE_OR_THROW( 392 Gdiplus::Ok == hr, 393 "CanvasHelper::strokePolyPolygon(): GDI+ call failed" ); 394 } 395 396 // TODO(P1): Provide caching here. 397 return uno::Reference< rendering::XCachedPrimitive >(NULL); 398 } 399 strokeTexturedPolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> &,const rendering::ViewState &,const rendering::RenderState &,const uno::Sequence<rendering::Texture> &,const rendering::StrokeAttributes &)400 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, 401 const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, 402 const rendering::ViewState& /*viewState*/, 403 const rendering::RenderState& /*renderState*/, 404 const uno::Sequence< rendering::Texture >& /*textures*/, 405 const rendering::StrokeAttributes& /*strokeAttributes*/ ) 406 { 407 // TODO 408 return uno::Reference< rendering::XCachedPrimitive >(NULL); 409 } 410 strokeTextureMappedPolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> &,const rendering::ViewState &,const rendering::RenderState &,const uno::Sequence<rendering::Texture> &,const uno::Reference<geometry::XMapping2D> &,const rendering::StrokeAttributes &)411 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, 412 const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, 413 const rendering::ViewState& /*viewState*/, 414 const rendering::RenderState& /*renderState*/, 415 const uno::Sequence< rendering::Texture >& /*textures*/, 416 const uno::Reference< geometry::XMapping2D >& /*xMapping*/, 417 const rendering::StrokeAttributes& /*strokeAttributes*/ ) 418 { 419 // TODO 420 return uno::Reference< rendering::XCachedPrimitive >(NULL); 421 } 422 queryStrokeShapes(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> &,const rendering::ViewState &,const rendering::RenderState &,const rendering::StrokeAttributes &)423 uno::Reference< rendering::XPolyPolygon2D > CanvasHelper::queryStrokeShapes( const rendering::XCanvas* /*pCanvas*/, 424 const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, 425 const rendering::ViewState& /*viewState*/, 426 const rendering::RenderState& /*renderState*/, 427 const rendering::StrokeAttributes& /*strokeAttributes*/ ) 428 { 429 // TODO 430 return uno::Reference< rendering::XPolyPolygon2D >(NULL); 431 } 432 fillPolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> & xPolyPolygon,const rendering::ViewState & viewState,const rendering::RenderState & renderState)433 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillPolyPolygon( const rendering::XCanvas* /*pCanvas*/, 434 const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, 435 const rendering::ViewState& viewState, 436 const rendering::RenderState& renderState ) 437 { 438 ENSURE_OR_THROW( xPolyPolygon.is(), 439 "CanvasHelper::fillPolyPolygon: polygon is NULL"); 440 441 if( needOutput() ) 442 { 443 GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); 444 445 setupGraphicsState( pGraphics, viewState, renderState ); 446 447 Gdiplus::SolidBrush aBrush( 448 tools::sequenceToArgb(renderState.DeviceColor)); 449 450 GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ) ); 451 452 // TODO(F1): FillRule 453 ENSURE_OR_THROW( Gdiplus::Ok == pGraphics->FillPath( &aBrush, pPath.get() ), 454 "CanvasHelper::fillPolyPolygon(): GDI+ call failed " ); 455 } 456 457 // TODO(P1): Provide caching here. 458 return uno::Reference< rendering::XCachedPrimitive >(NULL); 459 } 460 fillTextureMappedPolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> &,const rendering::ViewState &,const rendering::RenderState &,const uno::Sequence<rendering::Texture> &,const uno::Reference<geometry::XMapping2D> &)461 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, 462 const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, 463 const rendering::ViewState& /*viewState*/, 464 const rendering::RenderState& /*renderState*/, 465 const uno::Sequence< rendering::Texture >& /*textures*/, 466 const uno::Reference< geometry::XMapping2D >& /*xMapping*/ ) 467 { 468 // TODO 469 return uno::Reference< rendering::XCachedPrimitive >(NULL); 470 } 471 createFont(const rendering::XCanvas *,const rendering::FontRequest & fontRequest,const uno::Sequence<beans::PropertyValue> & extraFontProperties,const geometry::Matrix2D & fontMatrix)472 uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* /*pCanvas*/, 473 const rendering::FontRequest& fontRequest, 474 const uno::Sequence< beans::PropertyValue >& extraFontProperties, 475 const geometry::Matrix2D& fontMatrix ) 476 { 477 if( needOutput() ) 478 { 479 return uno::Reference< rendering::XCanvasFont >( 480 new CanvasFont(fontRequest, extraFontProperties, fontMatrix ) ); 481 } 482 483 return uno::Reference< rendering::XCanvasFont >(); 484 } 485 queryAvailableFonts(const rendering::XCanvas *,const rendering::FontInfo &,const uno::Sequence<beans::PropertyValue> &)486 uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* /*pCanvas*/, 487 const rendering::FontInfo& /*aFilter*/, 488 const uno::Sequence< beans::PropertyValue >& /*aFontProperties*/ ) 489 { 490 // TODO 491 return uno::Sequence< rendering::FontInfo >(); 492 } 493 drawText(const rendering::XCanvas *,const rendering::StringContext & text,const uno::Reference<rendering::XCanvasFont> & xFont,const rendering::ViewState & viewState,const rendering::RenderState & renderState,sal_Int8)494 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* /*pCanvas*/, 495 const rendering::StringContext& text, 496 const uno::Reference< rendering::XCanvasFont >& xFont, 497 const rendering::ViewState& viewState, 498 const rendering::RenderState& renderState, 499 sal_Int8 /*textDirection*/ ) 500 { 501 ENSURE_OR_THROW( xFont.is(), 502 "CanvasHelper::drawText: font is NULL"); 503 504 if( needOutput() ) 505 { 506 GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); 507 508 setupGraphicsState( pGraphics, viewState, renderState ); 509 510 Gdiplus::SolidBrush aBrush( 511 Gdiplus::Color( 512 tools::sequenceToArgb(renderState.DeviceColor))); 513 514 CanvasFont::ImplRef pFont( 515 tools::canvasFontFromXFont(xFont) ); 516 517 // Move glyphs up, such that output happens at the font 518 // baseline. 519 Gdiplus::PointF aPoint( 0.0, 520 static_cast<Gdiplus::REAL>(-(pFont->getFont()->GetSize()* 521 pFont->getCellAscent() / 522 pFont->getEmHeight())) ); 523 524 // TODO(F1): According to 525 // http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q307208, 526 // we might have to revert to GDI and ExTextOut here, 527 // since GDI+ takes the scalability a little bit too 528 // far... 529 530 // TODO(F2): Proper layout (BiDi, CTL)! IMHO must use 531 // DrawDriverString here, and perform layouting myself... 532 ENSURE_OR_THROW( 533 Gdiplus::Ok == pGraphics->DrawString( reinterpret_cast<LPCWSTR>( 534 text.Text.copy( text.StartPosition, 535 text.Length ).getStr()), 536 text.Length, 537 pFont->getFont().get(), 538 aPoint, 539 &aBrush ), 540 "CanvasHelper::drawText(): GDI+ call failed" ); 541 } 542 543 return uno::Reference< rendering::XCachedPrimitive >(NULL); 544 } 545 drawTextLayout(const rendering::XCanvas *,const uno::Reference<rendering::XTextLayout> & xLayoutetText,const rendering::ViewState & viewState,const rendering::RenderState & renderState)546 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* /*pCanvas*/, 547 const uno::Reference< rendering::XTextLayout >& xLayoutetText, 548 const rendering::ViewState& viewState, 549 const rendering::RenderState& renderState ) 550 { 551 ENSURE_OR_THROW( xLayoutetText.is(), 552 "CanvasHelper::drawTextLayout: layout is NULL"); 553 554 if( needOutput() ) 555 { 556 TextLayout* pTextLayout = 557 dynamic_cast< TextLayout* >( xLayoutetText.get() ); 558 559 ENSURE_OR_THROW( pTextLayout, 560 "CanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" ); 561 562 pTextLayout->draw( mpGraphicsProvider->getGraphics(), 563 viewState, 564 renderState, 565 maOutputOffset, 566 mpDevice, 567 false ); 568 } 569 570 return uno::Reference< rendering::XCachedPrimitive >(NULL); 571 } 572 drawBitmap(const rendering::XCanvas *,const uno::Reference<rendering::XBitmap> & xBitmap,const rendering::ViewState & viewState,const rendering::RenderState & renderState)573 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas* /*pCanvas*/, 574 const uno::Reference< rendering::XBitmap >& xBitmap, 575 const rendering::ViewState& viewState, 576 const rendering::RenderState& renderState ) 577 { 578 ENSURE_OR_THROW( xBitmap.is(), 579 "CanvasHelper::drawBitmap: bitmap is NULL"); 580 581 if( needOutput() ) 582 { 583 // check whether one of our own objects - need to retrieve 584 // bitmap _before_ calling 585 // GraphicsProvider::getGraphics(), to avoid locking our 586 // own surface. 587 BitmapSharedPtr pGdiBitmap; 588 BitmapProvider* pBitmap = dynamic_cast< BitmapProvider* >(xBitmap.get()); 589 if( pBitmap ) 590 { 591 IBitmapSharedPtr pDXBitmap( pBitmap->getBitmap() ); 592 if( pDXBitmap ) 593 pGdiBitmap = pDXBitmap->getBitmap(); 594 } 595 596 GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); 597 setupGraphicsState( pGraphics, viewState, renderState ); 598 599 if( pGdiBitmap ) 600 tools::drawGdiPlusBitmap(pGraphics,pGdiBitmap); 601 else 602 tools::drawVCLBitmapFromXBitmap(pGraphics, 603 xBitmap); 604 } 605 606 // TODO(P1): Provide caching here. 607 return uno::Reference< rendering::XCachedPrimitive >(NULL); 608 } 609 drawBitmapModulated(const rendering::XCanvas * pCanvas,const uno::Reference<rendering::XBitmap> & xBitmap,const rendering::ViewState & viewState,const rendering::RenderState & renderState)610 uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas* pCanvas, 611 const uno::Reference< rendering::XBitmap >& xBitmap, 612 const rendering::ViewState& viewState, 613 const rendering::RenderState& renderState ) 614 { 615 ENSURE_OR_THROW( xBitmap.is(), 616 "CanvasHelper::drawBitmap: bitmap is NULL"); 617 618 // no color set -> this is equivalent to a plain drawBitmap(), then 619 if( renderState.DeviceColor.getLength() < 3 ) 620 return drawBitmap( pCanvas, xBitmap, viewState, renderState ); 621 622 if( needOutput() ) 623 { 624 GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); 625 626 setupGraphicsState( pGraphics, viewState, renderState ); 627 628 BitmapSharedPtr pBitmap( tools::bitmapFromXBitmap( xBitmap ) ); 629 Gdiplus::Rect aRect( 0, 0, 630 pBitmap->GetWidth(), 631 pBitmap->GetHeight() ); 632 633 // Setup an ImageAttributes with an alpha-modulating 634 // color matrix. 635 const rendering::ARGBColor& rARGBColor( 636 mpDevice->getDeviceColorSpace()->convertToARGB(renderState.DeviceColor)[0]); 637 638 Gdiplus::ImageAttributes aImgAttr; 639 tools::setModulateImageAttributes( aImgAttr, 640 rARGBColor.Red, 641 rARGBColor.Green, 642 rARGBColor.Blue, 643 rARGBColor.Alpha ); 644 645 ENSURE_OR_THROW( 646 Gdiplus::Ok == pGraphics->DrawImage( pBitmap.get(), 647 aRect, 648 0, 0, 649 pBitmap->GetWidth(), 650 pBitmap->GetHeight(), 651 Gdiplus::UnitPixel, 652 &aImgAttr, 653 NULL, 654 NULL ), 655 "CanvasHelper::drawBitmapModulated(): GDI+ call failed" ); 656 } 657 658 // TODO(P1): Provide caching here. 659 return uno::Reference< rendering::XCachedPrimitive >(NULL); 660 } 661 getDevice()662 uno::Reference< rendering::XGraphicDevice > CanvasHelper::getDevice() 663 { 664 return uno::Reference< rendering::XGraphicDevice >(mpDevice); 665 } 666 667 // private helper 668 // -------------------------------------------------- 669 calcCompositingMode(sal_Int8 nMode)670 Gdiplus::CompositingMode CanvasHelper::calcCompositingMode( sal_Int8 nMode ) 671 { 672 Gdiplus::CompositingMode aRet( Gdiplus::CompositingModeSourceOver ); 673 674 switch( nMode ) 675 { 676 case rendering::CompositeOperation::OVER: 677 // FALLTHROUGH intended 678 case rendering::CompositeOperation::CLEAR: 679 aRet = Gdiplus::CompositingModeSourceOver; 680 break; 681 682 case rendering::CompositeOperation::SOURCE: 683 aRet = Gdiplus::CompositingModeSourceCopy; 684 break; 685 686 case rendering::CompositeOperation::DESTINATION: 687 // FALLTHROUGH intended 688 case rendering::CompositeOperation::UNDER: 689 // FALLTHROUGH intended 690 case rendering::CompositeOperation::INSIDE: 691 // FALLTHROUGH intended 692 case rendering::CompositeOperation::INSIDE_REVERSE: 693 // FALLTHROUGH intended 694 case rendering::CompositeOperation::OUTSIDE: 695 // FALLTHROUGH intended 696 case rendering::CompositeOperation::OUTSIDE_REVERSE: 697 // FALLTHROUGH intended 698 case rendering::CompositeOperation::ATOP: 699 // FALLTHROUGH intended 700 case rendering::CompositeOperation::ATOP_REVERSE: 701 // FALLTHROUGH intended 702 case rendering::CompositeOperation::XOR: 703 // FALLTHROUGH intended 704 case rendering::CompositeOperation::ADD: 705 // FALLTHROUGH intended 706 case rendering::CompositeOperation::SATURATE: 707 // TODO(F2): Problem, because GDI+ only knows about two compositing modes 708 aRet = Gdiplus::CompositingModeSourceOver; 709 break; 710 711 default: 712 ENSURE_OR_THROW( false, "CanvasHelper::calcCompositingMode: unexpected mode" ); 713 break; 714 } 715 716 return aRet; 717 } 718 setupGraphicsState(GraphicsSharedPtr & rGraphics,const rendering::ViewState & viewState,const rendering::RenderState & renderState)719 void CanvasHelper::setupGraphicsState( GraphicsSharedPtr& rGraphics, 720 const rendering::ViewState& viewState, 721 const rendering::RenderState& renderState ) 722 { 723 ENSURE_OR_THROW( needOutput(), 724 "CanvasHelper::setupGraphicsState: primary graphics invalid" ); 725 ENSURE_OR_THROW( mpDevice, 726 "CanvasHelper::setupGraphicsState: reference device invalid" ); 727 728 // setup view transform first. Clipping e.g. depends on it 729 ::basegfx::B2DHomMatrix aTransform; 730 ::canvas::tools::getViewStateTransform(aTransform, viewState); 731 732 // add output offset 733 if( !maOutputOffset.equalZero() ) 734 { 735 const basegfx::B2DHomMatrix aOutputOffset(basegfx::tools::createTranslateB2DHomMatrix( 736 maOutputOffset.getX(), maOutputOffset.getY())); 737 aTransform = aOutputOffset * aTransform; 738 } 739 740 Gdiplus::Matrix aMatrix; 741 tools::gdiPlusMatrixFromB2DHomMatrix( aMatrix, aTransform ); 742 743 ENSURE_OR_THROW( 744 Gdiplus::Ok == rGraphics->SetTransform( &aMatrix ), 745 "CanvasHelper::setupGraphicsState(): Failed to set GDI+ transformation" ); 746 747 // setup view and render state clipping 748 ENSURE_OR_THROW( 749 Gdiplus::Ok == rGraphics->ResetClip(), 750 "CanvasHelper::setupGraphicsState(): Failed to reset GDI+ clip" ); 751 752 if( viewState.Clip.is() ) 753 { 754 GraphicsPathSharedPtr aClipPath( tools::graphicsPathFromXPolyPolygon2D( viewState.Clip ) ); 755 756 // TODO(P3): Cache clip. SetClip( GraphicsPath ) performs abyssmally on GDI+. 757 // Try SetClip( Rect ) or similar for simple clip paths (need some support in 758 // LinePolyPolygon, then) 759 ENSURE_OR_THROW( 760 Gdiplus::Ok == rGraphics->SetClip( aClipPath.get(), 761 Gdiplus::CombineModeIntersect ), 762 "CanvasHelper::setupGraphicsState(): Cannot set GDI+ clip" ); 763 } 764 765 // setup overall transform only now. View clip above was relative to 766 // view transform 767 ::canvas::tools::mergeViewAndRenderTransform(aTransform, 768 viewState, 769 renderState); 770 771 // add output offset 772 if( !maOutputOffset.equalZero() ) 773 { 774 const basegfx::B2DHomMatrix aOutputOffset(basegfx::tools::createTranslateB2DHomMatrix( 775 maOutputOffset.getX(), maOutputOffset.getY())); 776 aTransform = aOutputOffset * aTransform; 777 } 778 779 tools::gdiPlusMatrixFromB2DHomMatrix( aMatrix, aTransform ); 780 781 ENSURE_OR_THROW( 782 Gdiplus::Ok == rGraphics->SetTransform( &aMatrix ), 783 "CanvasHelper::setupGraphicsState(): Cannot set GDI+ transformation" ); 784 785 if( renderState.Clip.is() ) 786 { 787 GraphicsPathSharedPtr aClipPath( tools::graphicsPathFromXPolyPolygon2D( renderState.Clip ) ); 788 789 // TODO(P3): Cache clip. SetClip( GraphicsPath ) performs abyssmally on GDI+. 790 // Try SetClip( Rect ) or similar for simple clip paths (need some support in 791 // LinePolyPolygon, then) 792 ENSURE_OR_THROW( 793 Gdiplus::Ok == rGraphics->SetClip( aClipPath.get(), 794 Gdiplus::CombineModeIntersect ), 795 "CanvasHelper::setupGraphicsState(): Cannot set GDI+ clip" ); 796 } 797 798 // setup compositing 799 const Gdiplus::CompositingMode eCompositing( calcCompositingMode( renderState.CompositeOperation ) ); 800 ENSURE_OR_THROW( 801 Gdiplus::Ok == rGraphics->SetCompositingMode( eCompositing ), 802 "CanvasHelper::setupGraphicsState(): Cannot set GDI* compositing mode)" ); 803 } 804 flush() const805 void CanvasHelper::flush() const 806 { 807 if( needOutput() ) 808 mpGraphicsProvider->getGraphics()->Flush( Gdiplus::FlushIntentionSync ); 809 } 810 } 811