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