summaryrefslogtreecommitdiffstats
path: root/canvas/source/directx/dx_canvashelper.cxx
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /canvas/source/directx/dx_canvashelper.cxx
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'canvas/source/directx/dx_canvashelper.cxx')
-rw-r--r--canvas/source/directx/dx_canvashelper.cxx814
1 files changed, 814 insertions, 0 deletions
diff --git a/canvas/source/directx/dx_canvashelper.cxx b/canvas/source/directx/dx_canvashelper.cxx
new file mode 100644
index 000000000..2ca2e09e2
--- /dev/null
+++ b/canvas/source/directx/dx_canvashelper.cxx
@@ -0,0 +1,814 @@
+/* -*- 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 <sal/config.h>
+#include <sal/log.hxx>
+
+#include <algorithm>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/utils/canvastools.hxx>
+#include <com/sun/star/rendering/CompositeOperation.hpp>
+#include <com/sun/star/rendering/PathCapType.hpp>
+#include <com/sun/star/rendering/PathJoinType.hpp>
+#include <com/sun/star/rendering/RepaintResult.hpp>
+#include <com/sun/star/rendering/TexturingMode.hpp>
+#include <comphelper/sequence.hxx>
+#include <o3tl/char16_t2wchar_t.hxx>
+#include <rtl/math.hxx>
+#include <tools/diagnose_ex.h>
+
+#include <canvas/canvastools.hxx>
+
+#include "dx_canvasfont.hxx"
+#include "dx_canvashelper.hxx"
+#include "dx_impltools.hxx"
+#include "dx_spritecanvas.hxx"
+#include "dx_textlayout.hxx"
+#include "dx_vcltools.hxx"
+
+using namespace ::com::sun::star;
+
+namespace dxcanvas
+{
+ namespace
+ {
+ Gdiplus::LineCap gdiLineCapFromCap( sal_Int8 nCapType )
+ {
+ switch( nCapType )
+ {
+ case rendering::PathCapType::BUTT:
+ return Gdiplus::LineCapFlat;
+
+ case rendering::PathCapType::ROUND:
+ return Gdiplus::LineCapRound;
+
+ case rendering::PathCapType::SQUARE:
+ return Gdiplus::LineCapSquare;
+
+ default:
+ ENSURE_OR_THROW( false,
+ "gdiLineCapFromCap(): Unexpected cap type" );
+ }
+
+ return Gdiplus::LineCapFlat;
+ }
+
+ Gdiplus::DashCap gdiDashCapFromCap( sal_Int8 nCapType )
+ {
+ switch( nCapType )
+ {
+ case rendering::PathCapType::BUTT:
+ return Gdiplus::DashCapFlat;
+
+ case rendering::PathCapType::ROUND:
+ return Gdiplus::DashCapRound;
+
+ // Gdiplus does not know square, using flat would make short
+ // dashes disappear, so use triangle as the closest one.
+ case rendering::PathCapType::SQUARE:
+ return Gdiplus::DashCapTriangle;
+
+ default:
+ ENSURE_OR_THROW( false,
+ "gdiDashCapFromCap(): Unexpected cap type" );
+ }
+
+ return Gdiplus::DashCapFlat;
+ }
+
+ Gdiplus::LineJoin gdiJoinFromJoin( sal_Int8 nJoinType )
+ {
+ switch( nJoinType )
+ {
+ case rendering::PathJoinType::NONE:
+ SAL_WARN( "canvas.directx", "gdiJoinFromJoin(): Join NONE not possible, mapping to BEVEL (closest to NONE)" );
+ return Gdiplus::LineJoinBevel;
+
+ case rendering::PathJoinType::MITER:
+ // in GDI+ fallback to Bevel, if miter limit is exceeded, is not done
+ // by Gdiplus::LineJoinMiter but by Gdiplus::LineJoinMiterClipped
+ return Gdiplus::LineJoinMiterClipped;
+
+ case rendering::PathJoinType::ROUND:
+ return Gdiplus::LineJoinRound;
+
+ case rendering::PathJoinType::BEVEL:
+ return Gdiplus::LineJoinBevel;
+
+ default:
+ ENSURE_OR_THROW( false,
+ "gdiJoinFromJoin(): Unexpected join type" );
+ }
+
+ return Gdiplus::LineJoinMiter;
+ }
+ }
+
+ CanvasHelper::CanvasHelper() :
+ mpGdiPlusUser( GDIPlusUser::createInstance() ),
+ mpDevice( nullptr ),
+ mpGraphicsProvider(),
+ maOutputOffset()
+ {
+ }
+
+ void CanvasHelper::disposing()
+ {
+ mpGraphicsProvider.reset();
+ mpDevice = nullptr;
+ mpGdiPlusUser.reset();
+ }
+
+ void CanvasHelper::setDevice( rendering::XGraphicDevice& rDevice )
+ {
+ mpDevice = &rDevice;
+ }
+
+ void CanvasHelper::setTarget( const GraphicsProviderSharedPtr& rTarget )
+ {
+ ENSURE_OR_THROW( rTarget,
+ "CanvasHelper::setTarget(): Invalid target" );
+ ENSURE_OR_THROW( !mpGraphicsProvider,
+ "CanvasHelper::setTarget(): target set, old target would be overwritten" );
+
+ mpGraphicsProvider = rTarget;
+ }
+
+ void CanvasHelper::setTarget( const GraphicsProviderSharedPtr& rTarget,
+ const ::basegfx::B2ISize& rOutputOffset )
+ {
+ ENSURE_OR_THROW( rTarget,
+ "CanvasHelper::setTarget(): invalid target" );
+ ENSURE_OR_THROW( !mpGraphicsProvider,
+ "CanvasHelper::setTarget(): target set, old target would be overwritten" );
+
+ mpGraphicsProvider = rTarget;
+ maOutputOffset = rOutputOffset;
+ }
+
+ void CanvasHelper::clear()
+ {
+ if( needOutput() )
+ {
+ GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
+ Gdiplus::Color aClearColor{Gdiplus::ARGB(Gdiplus::Color::White)};
+
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == pGraphics->SetCompositingMode(
+ Gdiplus::CompositingModeSourceCopy ), // force set, don't blend
+ "CanvasHelper::clear(): GDI+ SetCompositingMode call failed" );
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == pGraphics->Clear( aClearColor ),
+ "CanvasHelper::clear(): GDI+ Clear call failed" );
+ }
+ }
+
+ void CanvasHelper::drawPoint( const rendering::XCanvas* /*pCanvas*/,
+ const geometry::RealPoint2D& aPoint,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ if( needOutput() )
+ {
+ GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
+
+ setupGraphicsState( pGraphics, viewState, renderState );
+
+ Gdiplus::SolidBrush aBrush(
+ Gdiplus::Color(
+ tools::sequenceToArgb(renderState.DeviceColor)) );
+
+ // determine size of one-by-one device pixel ellipse
+ Gdiplus::Matrix aMatrix;
+ pGraphics->GetTransform(&aMatrix);
+ aMatrix.Invert();
+ Gdiplus::PointF vector(1, 1);
+ aMatrix.TransformVectors(&vector);
+
+ // paint a one-by-one circle, with the given point
+ // in the middle (rounded to float)
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == pGraphics->FillEllipse( &aBrush,
+ // disambiguate call
+ Gdiplus::REAL(aPoint.X),
+ Gdiplus::REAL(aPoint.Y),
+ Gdiplus::REAL(vector.X),
+ Gdiplus::REAL(vector.Y) ),
+ "CanvasHelper::drawPoint(): GDI+ call failed" );
+ }
+ }
+
+ void CanvasHelper::drawLine( const rendering::XCanvas* /*pCanvas*/,
+ const geometry::RealPoint2D& aStartPoint,
+ const geometry::RealPoint2D& aEndPoint,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ if( needOutput() )
+ {
+ GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
+
+ setupGraphicsState( pGraphics, viewState, renderState );
+
+ Gdiplus::Pen aPen(
+ Gdiplus::Color(
+ tools::sequenceToArgb(renderState.DeviceColor)),
+ Gdiplus::REAL(0.0) );
+
+ // #122683# Switched precedence of pixel offset
+ // mode. Seemingly, polygon stroking needs
+ // PixelOffsetModeNone to achieve visually pleasing
+ // results, whereas all other operations (e.g. polygon
+ // fills, bitmaps) look better with PixelOffsetModeHalf.
+ const Gdiplus::PixelOffsetMode aOldMode(
+ pGraphics->GetPixelOffsetMode() );
+ pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone );
+
+ Gdiplus::Status hr = pGraphics->DrawLine( &aPen,
+ Gdiplus::REAL(aStartPoint.X), // disambiguate call
+ Gdiplus::REAL(aStartPoint.Y),
+ Gdiplus::REAL(aEndPoint.X),
+ Gdiplus::REAL(aEndPoint.Y) );
+ pGraphics->SetPixelOffsetMode( aOldMode );
+
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == hr,
+ "CanvasHelper::drawLine(): GDI+ call failed" );
+ }
+ }
+
+ void CanvasHelper::drawBezier( const rendering::XCanvas* /*pCanvas*/,
+ const geometry::RealBezierSegment2D& aBezierSegment,
+ const geometry::RealPoint2D& aEndPoint,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ if( needOutput() )
+ {
+ GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
+
+ setupGraphicsState( pGraphics, viewState, renderState );
+
+ Gdiplus::Pen aPen(
+ Gdiplus::Color(
+ tools::sequenceToArgb(renderState.DeviceColor)),
+ Gdiplus::REAL(0.0) );
+
+ // #122683# Switched precedence of pixel offset
+ // mode. Seemingly, polygon stroking needs
+ // PixelOffsetModeNone to achieve visually pleasing
+ // results, whereas all other operations (e.g. polygon
+ // fills, bitmaps) look better with PixelOffsetModeHalf.
+ const Gdiplus::PixelOffsetMode aOldMode(
+ pGraphics->GetPixelOffsetMode() );
+ pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone );
+
+ Gdiplus::Status hr = pGraphics->DrawBezier( &aPen,
+ Gdiplus::REAL(aBezierSegment.Px), // disambiguate call
+ Gdiplus::REAL(aBezierSegment.Py),
+ Gdiplus::REAL(aBezierSegment.C1x),
+ Gdiplus::REAL(aBezierSegment.C1y),
+ Gdiplus::REAL(aEndPoint.X),
+ Gdiplus::REAL(aEndPoint.Y),
+ Gdiplus::REAL(aBezierSegment.C2x),
+ Gdiplus::REAL(aBezierSegment.C2y) );
+
+ pGraphics->SetPixelOffsetMode( aOldMode );
+
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == hr,
+ "CanvasHelper::drawBezier(): GDI+ call failed" );
+ }
+ }
+
+ uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas* /*pCanvas*/,
+ const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ ENSURE_OR_THROW( xPolyPolygon.is(),
+ "CanvasHelper::drawPolyPolygon: polygon is NULL");
+
+ if( needOutput() )
+ {
+ GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
+
+ setupGraphicsState( pGraphics, viewState, renderState );
+
+ Gdiplus::Pen aPen(
+ Gdiplus::Color(
+ tools::sequenceToArgb(renderState.DeviceColor)),
+ Gdiplus::REAL(0.0) );
+
+ // #122683# Switched precedence of pixel offset
+ // mode. Seemingly, polygon stroking needs
+ // PixelOffsetModeNone to achieve visually pleasing
+ // results, whereas all other operations (e.g. polygon
+ // fills, bitmaps) look better with PixelOffsetModeHalf.
+ const Gdiplus::PixelOffsetMode aOldMode(
+ pGraphics->GetPixelOffsetMode() );
+ pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone );
+
+ GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ) );
+
+ // TODO(E1): Return value
+ Gdiplus::Status hr = pGraphics->DrawPath( &aPen, pPath.get() );
+
+ pGraphics->SetPixelOffsetMode( aOldMode );
+
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == hr,
+ "CanvasHelper::drawPolyPolygon(): GDI+ call failed" );
+ }
+
+ // TODO(P1): Provide caching here.
+ return uno::Reference< rendering::XCachedPrimitive >(nullptr);
+ }
+
+ uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas* /*pCanvas*/,
+ const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState,
+ const rendering::StrokeAttributes& strokeAttributes )
+ {
+ ENSURE_OR_THROW( xPolyPolygon.is(),
+ "CanvasHelper::drawPolyPolygon: polygon is NULL");
+
+ if( needOutput() )
+ {
+ GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
+
+ setupGraphicsState( pGraphics, viewState, renderState );
+
+
+ // Setup stroke pen
+
+
+ Gdiplus::Pen aPen(
+ Gdiplus::Color(
+ tools::sequenceToArgb(renderState.DeviceColor)),
+ static_cast< Gdiplus::REAL >(strokeAttributes.StrokeWidth) );
+
+ // #122683# Switched precedence of pixel offset
+ // mode. Seemingly, polygon stroking needs
+ // PixelOffsetModeNone to achieve visually pleasing
+ // results, whereas all other operations (e.g. polygon
+ // fills, bitmaps) look better with PixelOffsetModeHalf.
+ const Gdiplus::PixelOffsetMode aOldMode(
+ pGraphics->GetPixelOffsetMode() );
+ pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone );
+
+ const bool bIsMiter(rendering::PathJoinType::MITER == strokeAttributes.JoinType);
+ const bool bIsNone(rendering::PathJoinType::NONE == strokeAttributes.JoinType);
+
+ if(bIsMiter)
+ aPen.SetMiterLimit( static_cast< Gdiplus::REAL >(strokeAttributes.MiterLimit) );
+
+ const std::vector< Gdiplus::REAL >& rDashArray(
+ ::comphelper::sequenceToContainer< std::vector< Gdiplus::REAL >, double >(
+ strokeAttributes.DashArray ) );
+ if( !rDashArray.empty() )
+ {
+ aPen.SetDashPattern( rDashArray.data(),
+ rDashArray.size() );
+ }
+ aPen.SetLineCap( gdiLineCapFromCap(strokeAttributes.StartCapType),
+ gdiLineCapFromCap(strokeAttributes.EndCapType),
+ gdiDashCapFromCap(strokeAttributes.StartCapType));
+ if(!bIsNone)
+ aPen.SetLineJoin( gdiJoinFromJoin(strokeAttributes.JoinType) );
+
+ GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon, bIsNone ) );
+
+ // TODO(E1): Return value
+ Gdiplus::Status hr = pGraphics->DrawPath( &aPen, pPath.get() );
+
+ pGraphics->SetPixelOffsetMode( aOldMode );
+
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == hr,
+ "CanvasHelper::strokePolyPolygon(): GDI+ call failed" );
+ }
+
+ // TODO(P1): Provide caching here.
+ return uno::Reference< rendering::XCachedPrimitive >(nullptr);
+ }
+
+ uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas* /*pCanvas*/,
+ const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/,
+ const rendering::ViewState& /*viewState*/,
+ const rendering::RenderState& /*renderState*/,
+ const uno::Sequence< rendering::Texture >& /*textures*/,
+ const rendering::StrokeAttributes& /*strokeAttributes*/ )
+ {
+ // TODO
+ return uno::Reference< rendering::XCachedPrimitive >(nullptr);
+ }
+
+ uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas* /*pCanvas*/,
+ const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/,
+ const rendering::ViewState& /*viewState*/,
+ const rendering::RenderState& /*renderState*/,
+ const uno::Sequence< rendering::Texture >& /*textures*/,
+ const uno::Reference< geometry::XMapping2D >& /*xMapping*/,
+ const rendering::StrokeAttributes& /*strokeAttributes*/ )
+ {
+ // TODO
+ return uno::Reference< rendering::XCachedPrimitive >(nullptr);
+ }
+
+ uno::Reference< rendering::XPolyPolygon2D > CanvasHelper::queryStrokeShapes( const rendering::XCanvas* /*pCanvas*/,
+ const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/,
+ const rendering::ViewState& /*viewState*/,
+ const rendering::RenderState& /*renderState*/,
+ const rendering::StrokeAttributes& /*strokeAttributes*/ )
+ {
+ // TODO
+ return uno::Reference< rendering::XPolyPolygon2D >(nullptr);
+ }
+
+ uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillPolyPolygon( const rendering::XCanvas* /*pCanvas*/,
+ const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ ENSURE_OR_THROW( xPolyPolygon.is(),
+ "CanvasHelper::fillPolyPolygon: polygon is NULL");
+
+ if( needOutput() )
+ {
+ GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
+
+ setupGraphicsState( pGraphics, viewState, renderState );
+
+ Gdiplus::SolidBrush aBrush(
+ tools::sequenceToArgb(renderState.DeviceColor));
+
+ GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ) );
+
+ // TODO(F1): FillRule
+ ENSURE_OR_THROW( Gdiplus::Ok == pGraphics->FillPath( &aBrush, pPath.get() ),
+ "CanvasHelper::fillPolyPolygon(): GDI+ call failed " );
+ }
+
+ // TODO(P1): Provide caching here.
+ return uno::Reference< rendering::XCachedPrimitive >(nullptr);
+ }
+
+ uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas* /*pCanvas*/,
+ const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/,
+ const rendering::ViewState& /*viewState*/,
+ const rendering::RenderState& /*renderState*/,
+ const uno::Sequence< rendering::Texture >& /*textures*/,
+ const uno::Reference< geometry::XMapping2D >& /*xMapping*/ )
+ {
+ // TODO
+ return uno::Reference< rendering::XCachedPrimitive >(nullptr);
+ }
+
+ uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* /*pCanvas*/,
+ const rendering::FontRequest& fontRequest,
+ const uno::Sequence< beans::PropertyValue >& extraFontProperties,
+ const geometry::Matrix2D& fontMatrix )
+ {
+ if( needOutput() )
+ {
+ return uno::Reference< rendering::XCanvasFont >(
+ new CanvasFont(fontRequest, extraFontProperties, fontMatrix ) );
+ }
+
+ return uno::Reference< rendering::XCanvasFont >();
+ }
+
+ uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* /*pCanvas*/,
+ const rendering::FontInfo& /*aFilter*/,
+ const uno::Sequence< beans::PropertyValue >& /*aFontProperties*/ )
+ {
+ // TODO
+ return uno::Sequence< rendering::FontInfo >();
+ }
+
+ uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* /*pCanvas*/,
+ const rendering::StringContext& text,
+ const uno::Reference< rendering::XCanvasFont >& xFont,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState,
+ sal_Int8 /*textDirection*/ )
+ {
+ ENSURE_OR_THROW( xFont.is(),
+ "CanvasHelper::drawText: font is NULL");
+
+ if( needOutput() )
+ {
+ GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
+
+ setupGraphicsState( pGraphics, viewState, renderState );
+
+ Gdiplus::SolidBrush aBrush(
+ Gdiplus::Color(
+ tools::sequenceToArgb(renderState.DeviceColor)));
+
+ CanvasFont::ImplRef pFont(
+ tools::canvasFontFromXFont(xFont) );
+
+ // Move glyphs up, such that output happens at the font
+ // baseline.
+ Gdiplus::PointF aPoint( 0.0,
+ static_cast<Gdiplus::REAL>(-(pFont->getFont()->GetSize()*
+ pFont->getCellAscent() /
+ pFont->getEmHeight())) );
+
+ // TODO(F1): According to
+ // http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q307208,
+ // we might have to revert to GDI and ExTextOut here,
+ // since GDI+ takes the scalability a little bit too
+ // far...
+
+ // TODO(F2): Proper layout (BiDi, CTL)! IMHO must use
+ // DrawDriverString here, and perform layouting myself...
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == pGraphics->DrawString( o3tl::toW(text.Text.copy( text.StartPosition,
+ text.Length ).getStr()),
+ text.Length,
+ pFont->getFont().get(),
+ aPoint,
+ &aBrush ),
+ "CanvasHelper::drawText(): GDI+ call failed" );
+ }
+
+ return uno::Reference< rendering::XCachedPrimitive >(nullptr);
+ }
+
+ uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* /*pCanvas*/,
+ const uno::Reference< rendering::XTextLayout >& xLayoutetText,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ ENSURE_OR_THROW( xLayoutetText.is(),
+ "CanvasHelper::drawTextLayout: layout is NULL");
+
+ if( needOutput() )
+ {
+ TextLayout* pTextLayout =
+ dynamic_cast< TextLayout* >( xLayoutetText.get() );
+
+ ENSURE_OR_THROW( pTextLayout,
+ "CanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" );
+
+ pTextLayout->draw( mpGraphicsProvider->getGraphics(),
+ viewState,
+ renderState,
+ maOutputOffset,
+ mpDevice,
+ false );
+ }
+
+ return uno::Reference< rendering::XCachedPrimitive >(nullptr);
+ }
+
+ uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas* /*pCanvas*/,
+ const uno::Reference< rendering::XBitmap >& xBitmap,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ ENSURE_OR_THROW( xBitmap.is(),
+ "CanvasHelper::drawBitmap: bitmap is NULL");
+
+ if( needOutput() )
+ {
+ // check whether one of our own objects - need to retrieve
+ // bitmap _before_ calling
+ // GraphicsProvider::getGraphics(), to avoid locking our
+ // own surface.
+ BitmapSharedPtr pGdiBitmap;
+ BitmapProvider* pBitmap = dynamic_cast< BitmapProvider* >(xBitmap.get());
+ if( pBitmap )
+ {
+ IBitmapSharedPtr pDXBitmap( pBitmap->getBitmap() );
+ if( pDXBitmap )
+ pGdiBitmap = pDXBitmap->getBitmap();
+ }
+
+ GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
+ setupGraphicsState( pGraphics, viewState, renderState );
+
+ if( pGdiBitmap )
+ tools::drawGdiPlusBitmap(pGraphics,pGdiBitmap);
+ else
+ tools::drawVCLBitmapFromXBitmap(pGraphics,
+ xBitmap);
+ }
+
+ // TODO(P1): Provide caching here.
+ return uno::Reference< rendering::XCachedPrimitive >(nullptr);
+ }
+
+ uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas* pCanvas,
+ const uno::Reference< rendering::XBitmap >& xBitmap,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ ENSURE_OR_THROW( xBitmap.is(),
+ "CanvasHelper::drawBitmap: bitmap is NULL");
+
+ // no color set -> this is equivalent to a plain drawBitmap(), then
+ if( renderState.DeviceColor.getLength() < 3 )
+ return drawBitmap( pCanvas, xBitmap, viewState, renderState );
+
+ if( needOutput() )
+ {
+ GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() );
+
+ setupGraphicsState( pGraphics, viewState, renderState );
+
+ BitmapSharedPtr pBitmap( tools::bitmapFromXBitmap( xBitmap ) );
+ Gdiplus::Rect aRect( 0, 0,
+ pBitmap->GetWidth(),
+ pBitmap->GetHeight() );
+
+ // Setup an ImageAttributes with an alpha-modulating
+ // color matrix.
+ rendering::ARGBColor aARGBColor(
+ mpDevice->getDeviceColorSpace()->convertToARGB(renderState.DeviceColor)[0]);
+
+ Gdiplus::ImageAttributes aImgAttr;
+ tools::setModulateImageAttributes( aImgAttr,
+ aARGBColor.Red,
+ aARGBColor.Green,
+ aARGBColor.Blue,
+ aARGBColor.Alpha );
+
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == pGraphics->DrawImage( pBitmap.get(),
+ aRect,
+ 0, 0,
+ pBitmap->GetWidth(),
+ pBitmap->GetHeight(),
+ Gdiplus::UnitPixel,
+ &aImgAttr ),
+ "CanvasHelper::drawBitmapModulated(): GDI+ call failed" );
+ }
+
+ // TODO(P1): Provide caching here.
+ return uno::Reference< rendering::XCachedPrimitive >(nullptr);
+ }
+
+ uno::Reference< rendering::XGraphicDevice > CanvasHelper::getDevice()
+ {
+ return uno::Reference< rendering::XGraphicDevice >(mpDevice);
+ }
+
+ // private helper
+
+
+ Gdiplus::CompositingMode CanvasHelper::calcCompositingMode( sal_Int8 nMode )
+ {
+ Gdiplus::CompositingMode aRet( Gdiplus::CompositingModeSourceOver );
+
+ switch( nMode )
+ {
+ case rendering::CompositeOperation::OVER:
+ case rendering::CompositeOperation::CLEAR:
+ aRet = Gdiplus::CompositingModeSourceOver;
+ break;
+
+ case rendering::CompositeOperation::SOURCE:
+ aRet = Gdiplus::CompositingModeSourceCopy;
+ break;
+
+ case rendering::CompositeOperation::DESTINATION:
+ case rendering::CompositeOperation::UNDER:
+ case rendering::CompositeOperation::INSIDE:
+ case rendering::CompositeOperation::INSIDE_REVERSE:
+ case rendering::CompositeOperation::OUTSIDE:
+ case rendering::CompositeOperation::OUTSIDE_REVERSE:
+ case rendering::CompositeOperation::ATOP:
+ case rendering::CompositeOperation::ATOP_REVERSE:
+ case rendering::CompositeOperation::XOR:
+ case rendering::CompositeOperation::ADD:
+ case rendering::CompositeOperation::SATURATE:
+ // TODO(F2): Problem, because GDI+ only knows about two compositing modes
+ aRet = Gdiplus::CompositingModeSourceOver;
+ break;
+
+ default:
+ ENSURE_OR_THROW( false, "CanvasHelper::calcCompositingMode: unexpected mode" );
+ break;
+ }
+
+ return aRet;
+ }
+
+ void CanvasHelper::setupGraphicsState( GraphicsSharedPtr const & rGraphics,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ ENSURE_OR_THROW( needOutput(),
+ "CanvasHelper::setupGraphicsState: primary graphics invalid" );
+ ENSURE_OR_THROW( mpDevice,
+ "CanvasHelper::setupGraphicsState: reference device invalid" );
+
+ // setup view transform first. Clipping e.g. depends on it
+ ::basegfx::B2DHomMatrix aTransform;
+ ::canvas::tools::getViewStateTransform(aTransform, viewState);
+
+ // add output offset
+ if( !maOutputOffset.equalZero() )
+ {
+ const basegfx::B2DHomMatrix aOutputOffset(basegfx::utils::createTranslateB2DHomMatrix(
+ maOutputOffset.getX(), maOutputOffset.getY()));
+ aTransform = aOutputOffset * aTransform;
+ }
+
+ Gdiplus::Matrix aMatrix;
+ tools::gdiPlusMatrixFromB2DHomMatrix( aMatrix, aTransform );
+
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == rGraphics->SetTransform( &aMatrix ),
+ "CanvasHelper::setupGraphicsState(): Failed to set GDI+ transformation" );
+
+ // setup view and render state clipping
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == rGraphics->ResetClip(),
+ "CanvasHelper::setupGraphicsState(): Failed to reset GDI+ clip" );
+
+ if( viewState.Clip.is() )
+ {
+ GraphicsPathSharedPtr aClipPath( tools::graphicsPathFromXPolyPolygon2D( viewState.Clip ) );
+
+ // TODO(P3): Cache clip. SetClip( GraphicsPath ) performs abyssmally on GDI+.
+ // Try SetClip( Rect ) or similar for simple clip paths (need some support in
+ // LinePolyPolygon, then)
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == rGraphics->SetClip( aClipPath.get(),
+ Gdiplus::CombineModeIntersect ),
+ "CanvasHelper::setupGraphicsState(): Cannot set GDI+ clip" );
+ }
+
+ // setup overall transform only now. View clip above was relative to
+ // view transform
+ ::canvas::tools::mergeViewAndRenderTransform(aTransform,
+ viewState,
+ renderState);
+
+ // add output offset
+ if( !maOutputOffset.equalZero() )
+ {
+ const basegfx::B2DHomMatrix aOutputOffset(basegfx::utils::createTranslateB2DHomMatrix(
+ maOutputOffset.getX(), maOutputOffset.getY()));
+ aTransform = aOutputOffset * aTransform;
+ }
+
+ tools::gdiPlusMatrixFromB2DHomMatrix( aMatrix, aTransform );
+
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == rGraphics->SetTransform( &aMatrix ),
+ "CanvasHelper::setupGraphicsState(): Cannot set GDI+ transformation" );
+
+ if( renderState.Clip.is() )
+ {
+ GraphicsPathSharedPtr aClipPath( tools::graphicsPathFromXPolyPolygon2D( renderState.Clip ) );
+
+ // TODO(P3): Cache clip. SetClip( GraphicsPath ) performs abyssmally on GDI+.
+ // Try SetClip( Rect ) or similar for simple clip paths (need some support in
+ // LinePolyPolygon, then)
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == rGraphics->SetClip( aClipPath.get(),
+ Gdiplus::CombineModeIntersect ),
+ "CanvasHelper::setupGraphicsState(): Cannot set GDI+ clip" );
+ }
+
+ // setup compositing
+ const Gdiplus::CompositingMode eCompositing( calcCompositingMode( renderState.CompositeOperation ) );
+ ENSURE_OR_THROW(
+ Gdiplus::Ok == rGraphics->SetCompositingMode( eCompositing ),
+ "CanvasHelper::setupGraphicsState(): Cannot set GDI* compositing mode)" );
+ }
+
+ void CanvasHelper::flush() const
+ {
+ if( needOutput() )
+ mpGraphicsProvider->getGraphics()->Flush( Gdiplus::FlushIntentionSync );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */