diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /canvas/source/cairo | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.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/cairo')
29 files changed, 6541 insertions, 0 deletions
diff --git a/canvas/source/cairo/cairo_cachedbitmap.cxx b/canvas/source/cairo/cairo_cachedbitmap.cxx new file mode 100644 index 000000000..111f797e2 --- /dev/null +++ b/canvas/source/cairo/cairo_cachedbitmap.cxx @@ -0,0 +1,79 @@ +/* -*- 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 <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/RepaintResult.hpp> +#include <utility> +#include <tools/diagnose_ex.h> + +#include "cairo_cachedbitmap.hxx" +#include "cairo_repainttarget.hxx" + + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + CachedBitmap::CachedBitmap( SurfaceSharedPtr pSurface, + const rendering::ViewState& rUsedViewState, + rendering::RenderState aUsedRenderState, + const uno::Reference< rendering::XCanvas >& rTarget ) : + CachedPrimitiveBase( rUsedViewState, rTarget ), + mpSurface(std::move( pSurface )), + maRenderState(std::move( aUsedRenderState )) + {} + + void SAL_CALL CachedBitmap::disposing() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mpSurface.reset(); + CachedPrimitiveBase::disposing(); + } + + ::sal_Int8 CachedBitmap::doRedraw( const rendering::ViewState& rNewState, + const rendering::ViewState& /*rOldState*/, + const uno::Reference< rendering::XCanvas >& rTargetCanvas, + bool bSameViewTransform ) + { + ENSURE_OR_THROW( bSameViewTransform, + "CachedBitmap::doRedraw(): base called with changed view transform " + "(told otherwise during construction)" ); + + RepaintTarget* pTarget = dynamic_cast< RepaintTarget* >(rTargetCanvas.get()); + + ENSURE_OR_THROW( pTarget, + "CachedBitmap::redraw(): cannot cast target to RepaintTarget" ); + + if( !pTarget->repaint( mpSurface, + rNewState, + maRenderState ) ) + { + // target failed to repaint + return rendering::RepaintResult::FAILED; + } + + return rendering::RepaintResult::REDRAWN; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_cachedbitmap.hxx b/canvas/source/cairo/cairo_cachedbitmap.hxx new file mode 100644 index 000000000..159b54bc2 --- /dev/null +++ b/canvas/source/cairo/cairo_cachedbitmap.hxx @@ -0,0 +1,58 @@ +/* -*- 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 . + */ + +#pragma once + +#include <base/cachedprimitivebase.hxx> +#include <com/sun/star/rendering/RenderState.hpp> + +#include <vcl/cairo.hxx> + +/* Definition of CachedBitmap class */ + +namespace cairocanvas +{ + class CachedBitmap : public ::canvas::CachedPrimitiveBase + { + public: + + /** Create an XCachedPrimitive for given GraphicObject + */ + CachedBitmap( ::cairo::SurfaceSharedPtr pSurface, + const css::rendering::ViewState& rUsedViewState, + css::rendering::RenderState aUsedRenderState, + const css::uno::Reference< css::rendering::XCanvas >& rTarget ); + + /// Dispose all internal references + virtual void SAL_CALL disposing() override; + + private: + virtual ::sal_Int8 doRedraw( const css::rendering::ViewState& rNewState, + const css::rendering::ViewState& rOldState, + const css::uno::Reference< + css::rendering::XCanvas >& rTargetCanvas, + bool bSameViewTransform ) override; + + + ::cairo::SurfaceSharedPtr mpSurface; + const css::rendering::RenderState maRenderState; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvas.cxx b/canvas/source/cairo/cairo_canvas.cxx new file mode 100644 index 000000000..622213dee --- /dev/null +++ b/canvas/source/cairo/cairo_canvas.cxx @@ -0,0 +1,191 @@ +/* -*- 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 <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <osl/mutex.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/sysdata.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include "cairo_canvas.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + Canvas::Canvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& /*rxContext*/ ) : + maArguments(aArguments) + { + } + + void Canvas::initialize() + { + // #i64742# Only perform initialization when not in probe mode + if( !maArguments.hasElements() ) + return; + + assert( !SkiaHelper::isVCLSkiaEnabled() ); + + /* maArguments: + 0: ptr to creating instance (Window or VirtualDevice) + 1: current bounds of creating instance + 2: bool, denoting always on top state for Window (always false for VirtualDevice) + 3: XWindow for creating Window (or empty for VirtualDevice) + 4: SystemGraphicsData as a streamed Any + */ + SAL_INFO("canvas.cairo","Canvas created " << this); + + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 5 && + maArguments[0].getValueTypeClass() == uno::TypeClass_HYPER && + maArguments[4].getValueTypeClass() == uno::TypeClass_SEQUENCE, + "Canvas::initialize: wrong number of arguments, or wrong types" ); + + // We expect a single Any here, containing a pointer to a valid + // VCL output device, on which to output (mostly needed for text) + sal_Int64 nPtr = 0; + maArguments[0] >>= nPtr; + OutputDevice* pOutDev = reinterpret_cast<OutputDevice*>(nPtr); + ENSURE_ARG_OR_THROW( pOutDev != nullptr, + "Canvas::initialize: invalid OutDev pointer" ); + + awt::Rectangle aBounds; + maArguments[1] >>= aBounds; + + uno::Sequence<sal_Int8> aSeq; + maArguments[4] >>= aSeq; + + const SystemGraphicsData* pSysData=reinterpret_cast<const SystemGraphicsData*>(aSeq.getConstArray()); + if( !pSysData || !pSysData->nSize ) + throw lang::NoSupportException( "Passed SystemGraphicsData invalid!" ); + + bool bHasCairo = pOutDev->SupportsCairo(); + ENSURE_ARG_OR_THROW(bHasCairo, "SpriteCanvas::SpriteCanvas: No Cairo capability"); + + // setup helper + maDeviceHelper.init( *this, *pOutDev ); + + maCanvasHelper.init( basegfx::B2ISize(aBounds.Width, aBounds.Height), *this, this ); + + // forward surface to render on to canvashelper + maCanvasHelper.setSurface( maDeviceHelper.getSurface(), false ); + + maArguments.realloc(0); + } + + Canvas::~Canvas() + { + SAL_INFO("canvas.cairo", "CairoCanvas destroyed" ); + } + + void Canvas::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // forward to parent + CanvasBaseT::disposeThis(); + } + + OUString SAL_CALL Canvas::getServiceName( ) + { + return "com.sun.star.rendering.Canvas.Cairo"; + } + + // XServiceInfo + sal_Bool Canvas::supportsService(const OUString& sServiceName) + { + return cppu::supportsService(this, sServiceName); + + } + OUString Canvas::getImplementationName() + { + return "com.sun.star.comp.rendering.Canvas.Cairo"; + } + css::uno::Sequence< OUString > Canvas::getSupportedServiceNames() + { + return { getServiceName() }; + } + + bool Canvas::repaint( const SurfaceSharedPtr& pSurface, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + return maCanvasHelper.repaint( pSurface, viewState, renderState ); + } + + SurfaceSharedPtr Canvas::getSurface() + { + return maDeviceHelper.getSurface(); + } + + SurfaceSharedPtr Canvas::createSurface( const ::basegfx::B2ISize& rSize, int aContent ) + { + return maDeviceHelper.createSurface( rSize, aContent ); + } + + SurfaceSharedPtr Canvas::createSurface( ::Bitmap& rBitmap ) + { + SurfaceSharedPtr pSurface; + + BitmapSystemData aData; + if( rBitmap.GetSystemData( aData ) ) { + const Size& rSize = rBitmap.GetSizePixel(); + + pSurface = maDeviceHelper.createSurface( aData, rSize ); + } + + return pSurface; + } + + SurfaceSharedPtr Canvas::changeSurface() + { + // non-modifiable surface here + return SurfaceSharedPtr(); + } + + OutputDevice* Canvas::getOutputDevice() + { + return maDeviceHelper.getOutputDevice(); + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_rendering_Canvas_Cairo_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + rtl::Reference<cairocanvas::Canvas> p = new cairocanvas::Canvas(args, context); + p->acquire(); + try { + p->initialize(); + } catch (css::uno::Exception&) { + p->dispose(); + p->release(); + throw; + } + return static_cast<cppu::OWeakObject*>(p.get()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvas.hxx b/canvas/source/cairo/cairo_canvas.hxx new file mode 100644 index 000000000..0c41a8a5c --- /dev/null +++ b/canvas/source/cairo/cairo_canvas.hxx @@ -0,0 +1,142 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <base/basemutexhelper.hxx> +#include <base/bitmapcanvasbase.hxx> +#include <base/graphicdevicebase.hxx> +#include <base/integerbitmapbase.hxx> + +#include "cairo_canvashelper.hxx" +#include "cairo_devicehelper.hxx" +#include "cairo_repainttarget.hxx" +#include "cairo_surfaceprovider.hxx" + +namespace cairocanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName, + css::lang::XServiceInfo > GraphicDeviceBase_Base; + typedef ::canvas::GraphicDeviceBase< ::canvas::BaseMutexHelper< GraphicDeviceBase_Base >, + DeviceHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasBase_Base; + + /** Mixin SurfaceProvider + + Have to mixin the SurfaceProvider before deriving from + ::canvas::CanvasBase, as this template should already + implement some of those interface methods. + + The reason why this appears kinda convoluted is the fact that + we cannot specify non-IDL types as WeakComponentImplHelper + template args, and furthermore, don't want to derive + ::canvas::CanvasBase directly from + SurfaceProvider (because derivees of + ::canvas::CanvasBase have to explicitly forward the + XInterface methods (e.g. via DECLARE_UNO3_AGG_DEFAULTS) + anyway). + */ + class CanvasBaseSurfaceProvider_Base : public CanvasBase_Base, + public SurfaceProvider + { + }; + + typedef ::canvas::IntegerBitmapBase< + canvas::BitmapCanvasBase2< + CanvasBaseSurfaceProvider_Base, + CanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject> > CanvasBaseT; + + /** Product of this component's factory. + + The Canvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class Canvas : public CanvasBaseT, + public RepaintTarget + { + public: + Canvas( const css::uno::Sequence< css::uno::Any >& aArguments, + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// For resource tracking + virtual ~Canvas() override; + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( Canvas, GraphicDeviceBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + // XServiceInfo + virtual sal_Bool SAL_CALL supportsService(const OUString& sServiceName) override; + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // RepaintTarget + virtual bool repaint( const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) override; + + // SurfaceProvider + virtual ::cairo::SurfaceSharedPtr getSurface() override; + virtual ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, int aContent ) override; + virtual ::cairo::SurfaceSharedPtr createSurface( ::Bitmap& rBitmap ) override; + virtual ::cairo::SurfaceSharedPtr changeSurface() override; + virtual OutputDevice* getOutputDevice() override; + + private: + css::uno::Sequence< css::uno::Any > maArguments; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvasbitmap.cxx b/canvas/source/cairo/cairo_canvasbitmap.cxx new file mode 100644 index 000000000..fa33b83eb --- /dev/null +++ b/canvas/source/cairo/cairo_canvasbitmap.cxx @@ -0,0 +1,163 @@ +/* -*- 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 <cppuhelper/supportsservice.hxx> +#include <tools/diagnose_ex.h> +#include <utility> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapTools.hxx> + +#include <cairo.h> + +#include "cairo_canvasbitmap.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + CanvasBitmap::CanvasBitmap( const ::basegfx::B2ISize& rSize, + SurfaceProviderRef rSurfaceProvider, + rendering::XGraphicDevice* pDevice, + bool bHasAlpha ) : + mpSurfaceProvider(std::move( rSurfaceProvider )), + maSize(rSize), + mbHasAlpha(bHasAlpha) + { + ENSURE_OR_THROW( mpSurfaceProvider.is(), + "CanvasBitmap::CanvasBitmap(): Invalid surface or device" ); + + SAL_INFO( + "canvas.cairo", + "bitmap size: " << rSize.getX() << "x" << rSize.getY()); + + mpBufferSurface = mpSurfaceProvider->createSurface( rSize, bHasAlpha ? CAIRO_CONTENT_COLOR_ALPHA : CAIRO_CONTENT_COLOR ); + mpBufferCairo = mpBufferSurface->getCairo(); + + maCanvasHelper.init( rSize, *mpSurfaceProvider, pDevice ); + maCanvasHelper.setSurface( mpBufferSurface, bHasAlpha ); + + // clear bitmap to 100% transparent + maCanvasHelper.clear(); + } + + void CanvasBitmap::disposeThis() + { + mpSurfaceProvider.clear(); + + mpBufferCairo.reset(); + mpBufferSurface.reset(); + + // forward to parent + CanvasBitmap_Base::disposeThis(); + } + + SurfaceSharedPtr CanvasBitmap::getSurface() + { + return mpBufferSurface; + } + + SurfaceSharedPtr CanvasBitmap::createSurface( const ::basegfx::B2ISize& rSize, int aContent ) + { + return mpSurfaceProvider->createSurface(rSize,aContent); + } + + SurfaceSharedPtr CanvasBitmap::createSurface( ::Bitmap& rBitmap ) + { + return mpSurfaceProvider->createSurface(rBitmap); + } + + SurfaceSharedPtr CanvasBitmap::changeSurface() + { + // non-modifiable surface here + return SurfaceSharedPtr(); + } + + OutputDevice* CanvasBitmap::getOutputDevice() + { + return mpSurfaceProvider->getOutputDevice(); + } + + bool CanvasBitmap::repaint( const SurfaceSharedPtr& pSurface, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + return maCanvasHelper.repaint( pSurface, viewState, renderState ); + } + + uno::Any SAL_CALL CanvasBitmap::getFastPropertyValue( sal_Int32 nHandle ) + { + uno::Any aRV( sal_Int32(0) ); + // 0 ... get BitmapEx + // 1 ... get Pixbuf with bitmap RGB content + // 2 ... return nothing (empty Any) + switch( nHandle ) + { + case 0: + { + aRV <<= reinterpret_cast<sal_Int64>( nullptr ); + if ( !mbHasAlpha ) + break; + + BitmapEx* pBitmapEx = vcl::bitmap::CreateFromCairoSurface( + ::Size( maSize.getX(), maSize.getY() ), + getSurface()->getCairoSurface().get()); + if (pBitmapEx) + aRV <<= reinterpret_cast<sal_Int64>( pBitmapEx ); + + break; + } + case 1: + { + aRV = getOutputDevice()->GetNativeSurfaceHandle(mpBufferSurface, maSize); + break; + } + case 2: + { + // Always return nothing - for the RGB surface support. + // Alpha code paths go via the above case 0. + aRV = uno::Any(); + break; + } + } + + return aRV; + } + + OUString SAL_CALL CanvasBitmap::getImplementationName( ) + { + return "CairoCanvas.CanvasBitmap"; + } + + sal_Bool SAL_CALL CanvasBitmap::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasBitmap::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.CanvasBitmap" }; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvasbitmap.hxx b/canvas/source/cairo/cairo_canvasbitmap.hxx new file mode 100644 index 000000000..8826bc82e --- /dev/null +++ b/canvas/source/cairo/cairo_canvasbitmap.hxx @@ -0,0 +1,126 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <comphelper/uno3.hxx> + +#include <base/bitmapcanvasbase.hxx> +#include <base/basemutexhelper.hxx> +#include <base/integerbitmapbase.hxx> + +#include "cairo_canvashelper.hxx" +#include "cairo_repainttarget.hxx" + + +/* Definition of CanvasBitmap class */ + +namespace cairocanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::lang::XServiceInfo, + css::beans::XFastPropertySet > CanvasBitmapBase_Base; + class CanvasBitmapSpriteSurface_Base : + public ::canvas::BaseMutexHelper<CanvasBitmapBase_Base>, + public SurfaceProvider + { + }; + + typedef ::canvas::IntegerBitmapBase< + canvas::BitmapCanvasBase2< + CanvasBitmapSpriteSurface_Base, + CanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject> > CanvasBitmap_Base; + + class CanvasBitmap : public CanvasBitmap_Base, + public RepaintTarget + { + public: + /** Create a canvas bitmap for the given surface + + @param rSize + Size of the bitmap + + @param rDevice + Reference device, with which bitmap should be compatible + */ + CanvasBitmap( const ::basegfx::B2ISize& rSize, + SurfaceProviderRef rDevice, + css::rendering::XGraphicDevice* pDevice, + bool bHasAlpha ); + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( CanvasBitmap, CanvasBitmapBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // SurfaceProvider + virtual ::cairo::SurfaceSharedPtr getSurface() override; + virtual ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, int aContent ) override; + virtual ::cairo::SurfaceSharedPtr createSurface( ::Bitmap& rBitmap ) override; + virtual ::cairo::SurfaceSharedPtr changeSurface() override; + virtual OutputDevice* getOutputDevice() override; + + // RepaintTarget + virtual bool repaint( const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) override; + + // XFastPropertySet + // used to retrieve BitmapEx pointer or X Pixmap handles for this bitmap + // handle values have these meanings: + // 0 ... get pointer to BitmapEx + // 1 ... get X pixmap handle to rgb content + // 2 ... FIXME: leftover? ever called with this? always returns nothing (empty Any) + // returned any contains either BitmapEx pointer or array of three Any value + // 1st a bool value: true - free the pixmap after used by XFreePixmap, false do nothing, the pixmap is used internally in the canvas + // 2nd the pixmap handle (sal_Int64) + // 3rd the pixmap depth + virtual css::uno::Any SAL_CALL getFastPropertyValue(sal_Int32 nHandle) override; + virtual void SAL_CALL setFastPropertyValue(sal_Int32, const css::uno::Any&) override {} + + private: + SurfaceProviderRef mpSurfaceProvider; + ::cairo::SurfaceSharedPtr mpBufferSurface; + ::cairo::CairoSharedPtr mpBufferCairo; + + const ::basegfx::B2ISize maSize; + const bool mbHasAlpha; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvascustomsprite.cxx b/canvas/source/cairo/cairo_canvascustomsprite.cxx new file mode 100644 index 000000000..b88507178 --- /dev/null +++ b/canvas/source/cairo/cairo_canvascustomsprite.cxx @@ -0,0 +1,151 @@ +/* -*- 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 <basegfx/point/b2dpoint.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <tools/diagnose_ex.h> + +#include <canvas/canvastools.hxx> +#include <cairo.h> + +#include "cairo_canvascustomsprite.hxx" +#include "cairo_spritecanvas.hxx" + + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + CanvasCustomSprite::CanvasCustomSprite( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rRefDevice ) : + mpSpriteCanvas( rRefDevice ), + maSize( ::canvas::tools::roundUp( rSpriteSize.Width ), + ::canvas::tools::roundUp( rSpriteSize.Height ) ) + { + ENSURE_OR_THROW( rRefDevice, + "CanvasCustomSprite::CanvasCustomSprite(): Invalid sprite canvas" ); + + SAL_INFO( "canvas.cairo", "sprite size: " << ::canvas::tools::roundUp( rSpriteSize.Width ) << ", " << ::canvas::tools::roundUp( rSpriteSize.Height )); + + mpBufferSurface = mpSpriteCanvas->createSurface( maSize, CAIRO_CONTENT_COLOR_ALPHA ); + + maCanvasHelper.init( maSize, + *rRefDevice, + rRefDevice.get() ); + maCanvasHelper.setSurface( mpBufferSurface, true ); + + maSpriteHelper.init( rSpriteSize, + rRefDevice ); + maSpriteHelper.setSurface( mpBufferSurface ); + + // clear sprite to 100% transparent + maCanvasHelper.clear(); + } + + void CanvasCustomSprite::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mpSpriteCanvas.clear(); + mpBufferSurface.reset(); + + // forward to parent + CanvasCustomSpriteBaseT::disposeThis(); + } + + void CanvasCustomSprite::redraw( const CairoSharedPtr& pCairo, + bool bBufferedUpdate ) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + redraw( pCairo, maSpriteHelper.getPosPixel(), bBufferedUpdate ); + } + + void CanvasCustomSprite::redraw( const CairoSharedPtr& pCairo, + const ::basegfx::B2DPoint& rOrigOutputPos, + bool bBufferedUpdate ) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + maSpriteHelper.redraw( pCairo, + rOrigOutputPos, + mbSurfaceDirty, + bBufferedUpdate ); + + mbSurfaceDirty = false; + } + + bool CanvasCustomSprite::repaint( const SurfaceSharedPtr& pSurface, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + return maCanvasHelper.repaint( pSurface, viewState, renderState ); + } + + SurfaceSharedPtr CanvasCustomSprite::getSurface() + { + return mpBufferSurface; + } + + SurfaceSharedPtr CanvasCustomSprite::createSurface( const ::basegfx::B2ISize& rSize, int aContent ) + { + return mpSpriteCanvas->createSurface(rSize,aContent); + } + + SurfaceSharedPtr CanvasCustomSprite::createSurface( ::Bitmap& rBitmap ) + { + return mpSpriteCanvas->createSurface(rBitmap); + } + + SurfaceSharedPtr CanvasCustomSprite::changeSurface() + { + SAL_INFO( "canvas.cairo", "replacing sprite background surface"); + + mpBufferSurface = mpSpriteCanvas->createSurface( maSize, CAIRO_CONTENT_COLOR ); + maSpriteHelper.setSurface( mpBufferSurface ); + + return mpBufferSurface; + } + + OutputDevice* CanvasCustomSprite::getOutputDevice() + { + return mpSpriteCanvas->getOutputDevice(); + } + + OUString SAL_CALL CanvasCustomSprite::getImplementationName() + { + return "CairoCanvas.CanvasCustomSprite"; + } + + sal_Bool SAL_CALL CanvasCustomSprite::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasCustomSprite::getSupportedServiceNames() + { + return { "com.sun.star.rendering.CanvasCustomSprite" }; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvascustomsprite.hxx b/canvas/source/cairo/cairo_canvascustomsprite.hxx new file mode 100644 index 000000000..ffe766ab0 --- /dev/null +++ b/canvas/source/cairo/cairo_canvascustomsprite.hxx @@ -0,0 +1,145 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XCustomSprite.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> + +#include <basegfx/point/b2dpoint.hxx> + +#include <base/basemutexhelper.hxx> +#include <base/canvascustomspritebase.hxx> + +#include <vcl/cairo.hxx> + +#include "cairo_sprite.hxx" +#include "cairo_canvashelper.hxx" +#include "cairo_repainttarget.hxx" +#include "cairo_spritehelper.hxx" +#include "cairo_spritecanvas.hxx" + + +namespace cairocanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XCustomSprite, + css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::lang::XServiceInfo > CanvasCustomSpriteBase_Base; + /** Mixin Sprite + + Have to mixin the Sprite interface before deriving from + ::canvas::CanvasCustomSpriteBase, as this template should + already implement some of those interface methods. + + The reason why this appears kinda convoluted is the fact that + we cannot specify non-IDL types as WeakComponentImplHelper + template args, and furthermore, don't want to derive + ::canvas::CanvasCustomSpriteBase directly from + ::canvas::Sprite (because derivees of + ::canvas::CanvasCustomSpriteBase have to explicitly forward + the XInterface methods (e.g. via DECLARE_UNO3_AGG_DEFAULTS) + anyway). Basically, ::canvas::CanvasCustomSpriteBase should + remain a base class that provides implementation, not to + enforce any specific interface on its derivees. + */ + class CanvasCustomSpriteSpriteBase_Base : public ::canvas::BaseMutexHelper< CanvasCustomSpriteBase_Base >, + public Sprite, + public SurfaceProvider + { + }; + + typedef ::canvas::CanvasCustomSpriteBase< CanvasCustomSpriteSpriteBase_Base, + SpriteHelper, + CanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasCustomSpriteBaseT; + + /* Definition of CanvasCustomSprite class */ + + class CanvasCustomSprite : public CanvasCustomSpriteBaseT, + public RepaintTarget + { + public: + /** Create a custom sprite + + @param rSpriteSize + Size of the sprite in pixel + + @param rRefDevice + Associated output device + + @param rSpriteCanvas + Target canvas + + @param rDevice + Target DX device + */ + CanvasCustomSprite( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rRefDevice ); + + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcount Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( CanvasCustomSprite, CanvasCustomSpriteBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // Sprite + virtual void redraw( const ::cairo::CairoSharedPtr& pCairo, + bool bBufferedUpdate ) const override; + virtual void redraw( const ::cairo::CairoSharedPtr& pCairo, + const ::basegfx::B2DPoint& rOrigOutputPos, + bool bBufferedUpdate ) const override; + + // RepaintTarget + virtual bool repaint( const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) override; + + // SurfaceProvider + virtual ::cairo::SurfaceSharedPtr getSurface() override; + virtual ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, int aContent ) override; + virtual ::cairo::SurfaceSharedPtr createSurface( ::Bitmap& rBitmap ) override; + virtual ::cairo::SurfaceSharedPtr changeSurface() override; + virtual OutputDevice* getOutputDevice() override; + + private: + /** MUST hold here, too, since CanvasHelper only contains a + raw pointer (without refcounting) + */ + SpriteCanvasRef mpSpriteCanvas; + ::cairo::SurfaceSharedPtr mpBufferSurface; + ::basegfx::B2ISize maSize; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvasfont.cxx b/canvas/source/cairo/cairo_canvasfont.cxx new file mode 100644 index 000000000..36c1733ef --- /dev/null +++ b/canvas/source/cairo/cairo_canvasfont.cxx @@ -0,0 +1,156 @@ +/* -*- 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 <basegfx/numeric/ftools.hxx> +#include <com/sun/star/rendering/PanoseProportion.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <rtl/math.hxx> +#include <utility> +#include <vcl/metric.hxx> + +#include "cairo_canvasfont.hxx" +#include "cairo_textlayout.hxx" + +using namespace ::com::sun::star; + +namespace cairocanvas +{ + + CanvasFont::CanvasFont( const rendering::FontRequest& rFontRequest, + const uno::Sequence< beans::PropertyValue >& /*rExtraFontProperties*/, + const geometry::Matrix2D& rFontMatrix, + SurfaceProviderRef rDevice ) : + CanvasFont_Base( m_aMutex ), + maFont( vcl::Font( rFontRequest.FontDescription.FamilyName, + rFontRequest.FontDescription.StyleName, + Size( 0, ::basegfx::fround(rFontRequest.CellSize) ) ) ), + maFontRequest( rFontRequest ), + mpRefDevice(std::move( rDevice )) + { + maFont->SetAlignment( ALIGN_BASELINE ); + maFont->SetCharSet( (rFontRequest.FontDescription.IsSymbolFont==css::util::TriState_YES) ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); + maFont->SetVertical( rFontRequest.FontDescription.IsVertical==css::util::TriState_YES ); + + // TODO(F2): improve panose->vclenum conversion + maFont->SetWeight( static_cast<FontWeight>(rFontRequest.FontDescription.FontDescription.Weight) ); + maFont->SetItalic( (rFontRequest.FontDescription.FontDescription.Letterform<=8) ? ITALIC_NONE : ITALIC_NORMAL ); + maFont->SetPitch( + rFontRequest.FontDescription.FontDescription.Proportion == rendering::PanoseProportion::MONO_SPACED + ? PITCH_FIXED : PITCH_VARIABLE); + + maFont->SetLanguage( LanguageTag::convertToLanguageType( rFontRequest.Locale, false)); + + // adjust to stretched/shrunk font + if( ::rtl::math::approxEqual( rFontMatrix.m00, rFontMatrix.m11) ) + return; + + VclPtr<OutputDevice> pOutDev( mpRefDevice->getOutputDevice() ); + + if( !pOutDev ) + return; + + const bool bOldMapState( pOutDev->IsMapModeEnabled() ); + pOutDev->EnableMapMode(false); + + const Size aSize = pOutDev->GetFontMetric( *maFont ).GetFontSize(); + + const double fDividend( rFontMatrix.m10 + rFontMatrix.m11 ); + double fStretch = rFontMatrix.m00 + rFontMatrix.m01; + + if( !::basegfx::fTools::equalZero( fDividend) ) + fStretch /= fDividend; + + const tools::Long nNewWidth = ::basegfx::fround( aSize.Width() * fStretch ); + + maFont->SetAverageFontWidth( nNewWidth ); + + pOutDev->EnableMapMode(bOldMapState); + } + + void SAL_CALL CanvasFont::disposing() + { + SolarMutexGuard aGuard; + + mpRefDevice.clear(); + } + + uno::Reference< rendering::XTextLayout > SAL_CALL CanvasFont::createTextLayout( const rendering::StringContext& aText, sal_Int8 nDirection, sal_Int64 nRandomSeed ) + { + SolarMutexGuard aGuard; + + if( !mpRefDevice.is() ) + return uno::Reference< rendering::XTextLayout >(); // we're disposed + + return new TextLayout( aText, + nDirection, + nRandomSeed, + Reference( this ), + mpRefDevice ); + } + + rendering::FontRequest SAL_CALL CanvasFont::getFontRequest( ) + { + SolarMutexGuard aGuard; + + return maFontRequest; + } + + rendering::FontMetrics SAL_CALL CanvasFont::getFontMetrics( ) + { + // TODO(F1) + return rendering::FontMetrics(); + } + + uno::Sequence< double > SAL_CALL CanvasFont::getAvailableSizes( ) + { + // TODO(F1) + return uno::Sequence< double >(); + } + + uno::Sequence< beans::PropertyValue > SAL_CALL CanvasFont::getExtraFontProperties( ) + { + // TODO(F1) + return uno::Sequence< beans::PropertyValue >(); + } + + OUString SAL_CALL CanvasFont::getImplementationName() + { + return "CairoCanvas::CanvasFont"; + } + + sal_Bool SAL_CALL CanvasFont::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasFont::getSupportedServiceNames() + { + return { "com.sun.star.rendering.CanvasFont" }; + } + + vcl::Font const & CanvasFont::getVCLFont() const + { + return *maFont; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvasfont.hxx b/canvas/source/cairo/cairo_canvasfont.hxx new file mode 100644 index 000000000..0ebe9acd8 --- /dev/null +++ b/canvas/source/cairo/cairo_canvasfont.hxx @@ -0,0 +1,83 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/geometry/Matrix2D.hpp> +#include <com/sun/star/rendering/FontRequest.hpp> +#include <com/sun/star/rendering/XCanvasFont.hpp> + +#include <vcl/font.hxx> + +#include <vclwrapper.hxx> + +#include "cairo_surfaceprovider.hxx" + + +/* Definition of CanvasFont class */ + +namespace cairocanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XCanvasFont, + css::lang::XServiceInfo > CanvasFont_Base; + + class CanvasFont : public ::cppu::BaseMutex, + public CanvasFont_Base + { + public: + typedef rtl::Reference<CanvasFont> Reference; + /// make noncopyable + CanvasFont(const CanvasFont&) = delete; + const CanvasFont& operator=(const CanvasFont&) = delete; + + CanvasFont( const css::rendering::FontRequest& fontRequest, + const css::uno::Sequence< css::beans::PropertyValue >& extraFontProperties, + const css::geometry::Matrix2D& rFontMatrix, + SurfaceProviderRef rDevice ); + + /// Dispose all internal references + virtual void SAL_CALL disposing() override; + + // XCanvasFont + virtual css::uno::Reference< css::rendering::XTextLayout > SAL_CALL createTextLayout( const css::rendering::StringContext& aText, sal_Int8 nDirection, sal_Int64 nRandomSeed ) override; + virtual css::rendering::FontRequest SAL_CALL getFontRequest( ) override; + virtual css::rendering::FontMetrics SAL_CALL getFontMetrics( ) override; + virtual css::uno::Sequence< double > SAL_CALL getAvailableSizes( ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getExtraFontProperties( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + vcl::Font const & getVCLFont() const; + + private: + ::canvas::vcltools::VCLObject<vcl::Font> maFont; + css::rendering::FontRequest maFontRequest; + SurfaceProviderRef mpRefDevice; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvashelper.cxx b/canvas/source/cairo/cairo_canvashelper.cxx new file mode 100644 index 000000000..8f46c5b85 --- /dev/null +++ b/canvas/source/cairo/cairo_canvashelper.cxx @@ -0,0 +1,2047 @@ +/* -*- 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 <tuple> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/utils/keystoplerp.hxx> +#include <basegfx/utils/lerp.hxx> +#include <com/sun/star/rendering/ColorComponentTag.hpp> +#include <com/sun/star/rendering/ColorSpaceType.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/IntegerBitmapLayout.hpp> +#include <com/sun/star/rendering/PathCapType.hpp> +#include <com/sun/star/rendering/PathJoinType.hpp> +#include <com/sun/star/rendering/RenderingIntent.hpp> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <com/sun/star/rendering/XIntegerBitmapColorSpace.hpp> +#include <com/sun/star/util/Endianness.hpp> +#include <comphelper/sequence.hxx> +#include <cppuhelper/implbase.hxx> +#include <rtl/math.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/virdev.hxx> + +#include <canvas/canvastools.hxx> +#include <parametricpolypolygon.hxx> +#include <cairo.h> + +#include "cairo_cachedbitmap.hxx" +#include "cairo_canvasbitmap.hxx" +#include "cairo_canvashelper.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + CanvasHelper::CanvasHelper() : + mpSurfaceProvider(nullptr), + mpDevice(nullptr), + mbHaveAlpha() + { + } + + void CanvasHelper::disposing() + { + mpSurface.reset(); + mpCairo.reset(); + mpVirtualDevice.disposeAndClear(); + mpDevice = nullptr; + mpSurfaceProvider = nullptr; + } + + void CanvasHelper::init( const ::basegfx::B2ISize& rSizePixel, + SurfaceProvider& rSurfaceProvider, + rendering::XGraphicDevice* pDevice ) + { + maSize = rSizePixel; + mpSurfaceProvider = &rSurfaceProvider; + mpDevice = pDevice; + } + + void CanvasHelper::setSize( const ::basegfx::B2ISize& rSize ) + { + maSize = rSize; + } + + void CanvasHelper::setSurface( const SurfaceSharedPtr& pSurface, bool bHasAlpha ) + { + mbHaveAlpha = bHasAlpha; + mpVirtualDevice.disposeAndClear(); + mpSurface = pSurface; + mpCairo = pSurface->getCairo(); + } + + static void setColor( cairo_t* pCairo, + const uno::Sequence<double>& rColor ) + { + if( rColor.getLength() > 3 ) + { + cairo_set_source_rgba( pCairo, + rColor[0], + rColor[1], + rColor[2], + rColor[3] ); + } + else if( rColor.getLength() == 3 ) + cairo_set_source_rgb( pCairo, + rColor[0], + rColor[1], + rColor[2] ); + } + + void CanvasHelper::useStates( const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + bool bSetColor ) + { + cairo_matrix_t aViewMatrix; + cairo_matrix_t aRenderMatrix; + cairo_matrix_t aCombinedMatrix; + + cairo_matrix_init( &aViewMatrix, + viewState.AffineTransform.m00, viewState.AffineTransform.m10, viewState.AffineTransform.m01, + viewState.AffineTransform.m11, viewState.AffineTransform.m02, viewState.AffineTransform.m12); + cairo_matrix_init( &aRenderMatrix, + renderState.AffineTransform.m00, renderState.AffineTransform.m10, renderState.AffineTransform.m01, + renderState.AffineTransform.m11, renderState.AffineTransform.m02, renderState.AffineTransform.m12); + cairo_matrix_multiply( &aCombinedMatrix, &aRenderMatrix, &aViewMatrix); + + if( viewState.Clip.is() ) + { + SAL_INFO( "canvas.cairo", "view clip"); + + aViewMatrix.x0 = basegfx::fround( aViewMatrix.x0 ); + aViewMatrix.y0 = basegfx::fround( aViewMatrix.y0 ); + cairo_set_matrix( mpCairo.get(), &aViewMatrix ); + doPolyPolygonPath( viewState.Clip, Clip ); + } + + aCombinedMatrix.x0 = basegfx::fround( aCombinedMatrix.x0 ); + aCombinedMatrix.y0 = basegfx::fround( aCombinedMatrix.y0 ); + cairo_set_matrix( mpCairo.get(), &aCombinedMatrix ); + + if( renderState.Clip.is() ) + { + SAL_INFO( "canvas.cairo", "render clip BEGIN"); + + doPolyPolygonPath( renderState.Clip, Clip ); + SAL_INFO( "canvas.cairo", "render clip END"); + } + + if( bSetColor ) + setColor(mpCairo.get(),renderState.DeviceColor); + + cairo_operator_t compositingMode( CAIRO_OPERATOR_OVER ); + switch( renderState.CompositeOperation ) + { + case rendering::CompositeOperation::CLEAR: + compositingMode = CAIRO_OPERATOR_CLEAR; + break; + case rendering::CompositeOperation::SOURCE: + compositingMode = CAIRO_OPERATOR_SOURCE; + break; + case rendering::CompositeOperation::DESTINATION: + compositingMode = CAIRO_OPERATOR_DEST; + break; + case rendering::CompositeOperation::OVER: + compositingMode = CAIRO_OPERATOR_OVER; + break; + case rendering::CompositeOperation::UNDER: + compositingMode = CAIRO_OPERATOR_DEST; + break; + case rendering::CompositeOperation::INSIDE: + compositingMode = CAIRO_OPERATOR_IN; + break; + case rendering::CompositeOperation::INSIDE_REVERSE: + compositingMode = CAIRO_OPERATOR_OUT; + break; + case rendering::CompositeOperation::OUTSIDE: + compositingMode = CAIRO_OPERATOR_DEST_OVER; + break; + case rendering::CompositeOperation::OUTSIDE_REVERSE: + compositingMode = CAIRO_OPERATOR_DEST_OUT; + break; + case rendering::CompositeOperation::ATOP: + compositingMode = CAIRO_OPERATOR_ATOP; + break; + case rendering::CompositeOperation::ATOP_REVERSE: + compositingMode = CAIRO_OPERATOR_DEST_ATOP; + break; + case rendering::CompositeOperation::XOR: + compositingMode = CAIRO_OPERATOR_XOR; + break; + case rendering::CompositeOperation::ADD: + compositingMode = CAIRO_OPERATOR_ADD; + break; + case rendering::CompositeOperation::SATURATE: + compositingMode = CAIRO_OPERATOR_SATURATE; + break; + } + cairo_set_operator( mpCairo.get(), compositingMode ); + } + + void CanvasHelper::clear() + { + SAL_INFO( "canvas.cairo", "clear whole area: " << maSize.getX() << " x " << maSize.getY() ); + + if( !mpCairo ) + return; + + cairo_save( mpCairo.get() ); + + cairo_identity_matrix( mpCairo.get() ); + // this does not really differ from all-zero, as cairo + // internally converts to premultiplied alpha. but anyway, + // this keeps it consistent with the other canvas impls + if( mbHaveAlpha ) + cairo_set_source_rgba( mpCairo.get(), 1.0, 1.0, 1.0, 0.0 ); + else + cairo_set_source_rgb( mpCairo.get(), 1.0, 1.0, 1.0 ); + cairo_set_operator( mpCairo.get(), CAIRO_OPERATOR_SOURCE ); + + cairo_rectangle( mpCairo.get(), 0, 0, maSize.getX(), maSize.getY() ); + cairo_fill( mpCairo.get() ); + + cairo_restore( mpCairo.get() ); + } + + void CanvasHelper::drawLine( const rendering::XCanvas* /*pCanvas*/, + const geometry::RealPoint2D& aStartPoint, + const geometry::RealPoint2D& aEndPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( !mpCairo ) + return; + + cairo_save( mpCairo.get() ); + + cairo_set_line_width( mpCairo.get(), 1 ); + + useStates( viewState, renderState, true ); + + cairo_move_to( mpCairo.get(), aStartPoint.X + 0.5, aStartPoint.Y + 0.5 ); + cairo_line_to( mpCairo.get(), aEndPoint.X + 0.5, aEndPoint.Y + 0.5 ); + cairo_stroke( mpCairo.get() ); + + cairo_restore( mpCairo.get() ); + } + + void CanvasHelper::drawBezier( const rendering::XCanvas* , + const geometry::RealBezierSegment2D& aBezierSegment, + const geometry::RealPoint2D& aEndPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( !mpCairo ) + return; + + cairo_save( mpCairo.get() ); + + cairo_set_line_width( mpCairo.get(), 1 ); + + useStates( viewState, renderState, true ); + + cairo_move_to( mpCairo.get(), aBezierSegment.Px + 0.5, aBezierSegment.Py + 0.5 ); + // tdf#99165 correction of control points not needed here, only hairlines drawn + // (see cairo_set_line_width above) + cairo_curve_to( mpCairo.get(), + aBezierSegment.C1x + 0.5, aBezierSegment.C1y + 0.5, + aBezierSegment.C2x + 0.5, aBezierSegment.C2y + 0.5, + aEndPoint.X + 0.5, aEndPoint.Y + 0.5 ); + cairo_stroke( mpCairo.get() ); + + cairo_restore( mpCairo.get() ); + } + +constexpr OUStringLiteral PARAMETRICPOLYPOLYGON_IMPLEMENTATION_NAME = u"Canvas::ParametricPolyPolygon"; + + /** surfaceFromXBitmap Create a surface from XBitmap + * @param xBitmap bitmap image that will be used for the surface + * @param bHasAlpha will be set to true if resulting surface has alpha + * + * This is a helper function for the other surfaceFromXBitmap(). + * This function tries to create surface from xBitmap by checking if xBitmap is CanvasBitmap or SpriteCanvas. + * + * @return created surface or NULL + **/ + static SurfaceSharedPtr surfaceFromXBitmap( const uno::Reference< rendering::XBitmap >& xBitmap ) + { + CanvasBitmap* pBitmapImpl = dynamic_cast< CanvasBitmap* >( xBitmap.get() ); + if( pBitmapImpl ) + return pBitmapImpl->getSurface(); + + SurfaceProvider* pSurfaceProvider = dynamic_cast<SurfaceProvider*>( xBitmap.get() ); + if( pSurfaceProvider ) + return pSurfaceProvider->getSurface(); + + return SurfaceSharedPtr(); + } + + static ::BitmapEx bitmapExFromXBitmap( const uno::Reference< rendering::XBitmap >& xBitmap ) + { + // TODO(F1): Add support for floating point bitmap formats + uno::Reference<rendering::XIntegerReadOnlyBitmap> xIntBmp(xBitmap, + uno::UNO_QUERY_THROW); + ::BitmapEx aBmpEx = vcl::unotools::bitmapExFromXBitmap(xIntBmp); + if( !aBmpEx.IsEmpty() ) + return aBmpEx; + + // TODO(F1): extract pixel from XBitmap interface + ENSURE_OR_THROW( false, + "bitmapExFromXBitmap(): could not extract BitmapEx" ); + + return ::BitmapEx(); + } + + /** surfaceFromXBitmap Create a surface from XBitmap + * @param xBitmap bitmap image that will be used for the surface + * @param rDevice reference to the device into which we want to draw + * @param data will be filled with alpha data, if xBitmap is alpha/transparent image + * @param bHasAlpha will be set to true if resulting surface has alpha + * + * This function tries various methods for creating a surface from xBitmap. It also uses + * the helper function surfaceFromXBitmap( xBitmap, bHasAlpha ) + * + * @return created surface or NULL + **/ + static SurfaceSharedPtr surfaceFromXBitmap( const uno::Reference< rendering::XBitmap >& xBitmap, const SurfaceProviderRef& rSurfaceProvider, unsigned char*& data, bool& bHasAlpha ) + { + bHasAlpha = xBitmap->hasAlpha(); + SurfaceSharedPtr pSurface = surfaceFromXBitmap( xBitmap ); + if( pSurface ) + data = nullptr; + else + { + ::BitmapEx aBmpEx = bitmapExFromXBitmap(xBitmap); + ::Bitmap aBitmap = aBmpEx.GetBitmap(); + + // there's no pixmap for alpha bitmap. we might still + // use rgb pixmap and only access alpha pixels the + // slow way. now we just speedup rgb bitmaps + if( !aBmpEx.IsAlpha() ) + { + pSurface = rSurfaceProvider->createSurface( aBitmap ); + data = nullptr; + bHasAlpha = false; + } + + if( !pSurface ) + { + tools::Long nWidth; + tools::Long nHeight; + vcl::bitmap::CanvasCairoExtractBitmapData(aBmpEx, aBitmap, data, bHasAlpha, nWidth, nHeight); + + pSurface = rSurfaceProvider->getOutputDevice()->CreateSurface( + CairoSurfaceSharedPtr( + cairo_image_surface_create_for_data( + data, + bHasAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, + nWidth, nHeight, nWidth*4 ), + &cairo_surface_destroy) ); + + SAL_INFO( "canvas.cairo","image: " << nWidth << " x " << nHeight << " alpha: " << bHasAlpha); + } + } + + return pSurface; + } + + static void addColorStops( cairo_pattern_t* pPattern, const uno::Sequence< uno::Sequence< double > >& rColors, const uno::Sequence< double >& rStops, bool bReverseStops ) + { + int i; + + OSL_ASSERT( rColors.getLength() == rStops.getLength() ); + + for( i = 0; i < rColors.getLength(); i++ ) + { + const uno::Sequence< double >& rColor( rColors[i] ); + float stop = bReverseStops ? 1 - rStops[i] : rStops[i]; + if( rColor.getLength() == 3 ) + cairo_pattern_add_color_stop_rgb( pPattern, stop, rColor[0], rColor[1], rColor[2] ); + else if( rColor.getLength() == 4 ) + { + double alpha = rColor[3]; + // cairo expects premultiplied alpha + cairo_pattern_add_color_stop_rgba( pPattern, stop, rColor[0]*alpha, rColor[1]*alpha, rColor[2]*alpha, alpha ); + } + } + } + + static uno::Sequence<double> lerp(const uno::Sequence<double>& rLeft, const uno::Sequence<double>& rRight, double fAlpha) + { + if( rLeft.getLength() == 3 ) + { + return + { + basegfx::utils::lerp(rLeft[0],rRight[0],fAlpha), + basegfx::utils::lerp(rLeft[1],rRight[1],fAlpha), + basegfx::utils::lerp(rLeft[2],rRight[2],fAlpha) + }; + } + else if( rLeft.getLength() == 4 ) + { + return + { + basegfx::utils::lerp(rLeft[0],rRight[0],fAlpha), + basegfx::utils::lerp(rLeft[1],rRight[1],fAlpha), + basegfx::utils::lerp(rLeft[2],rRight[2],fAlpha), + basegfx::utils::lerp(rLeft[3],rRight[3],fAlpha) + }; + } + + return {}; + } + + static cairo_pattern_t* patternFromParametricPolyPolygon( ::canvas::ParametricPolyPolygon const & rPolygon ) + { + cairo_pattern_t* pPattern = nullptr; + const ::canvas::ParametricPolyPolygon::Values aValues = rPolygon.getValues(); + double x0, x1, y0, y1, cx, cy, r0, r1; + + switch( aValues.meType ) + { + case ::canvas::ParametricPolyPolygon::GradientType::Linear: + x0 = 0; + y0 = 0; + x1 = 1; + y1 = 0; + pPattern = cairo_pattern_create_linear( x0, y0, x1, y1 ); + addColorStops( pPattern, aValues.maColors, aValues.maStops, false ); + break; + + case ::canvas::ParametricPolyPolygon::GradientType::Elliptical: + cx = 0; + cy = 0; + r0 = 0; + r1 = 1; + + pPattern = cairo_pattern_create_radial( cx, cy, r0, cy, cy, r1 ); + addColorStops( pPattern, aValues.maColors, aValues.maStops, true ); + break; + default: + break; + } + + return pPattern; + } + + static void doOperation( Operation aOperation, + cairo_t* pCairo, + const uno::Sequence< rendering::Texture >* pTextures, + const SurfaceProviderRef& pDevice, + const basegfx::B2DRange& rBounds ) + { + switch( aOperation ) + { + case Fill: + /* TODO: multitexturing */ + if( pTextures ) + { + const css::rendering::Texture& aTexture ( (*pTextures)[0] ); + if( aTexture.Bitmap.is() ) + { + unsigned char* data = nullptr; + bool bHasAlpha = false; + SurfaceSharedPtr pSurface = surfaceFromXBitmap( (*pTextures)[0].Bitmap, pDevice, data, bHasAlpha ); + + if( pSurface ) + { + cairo_pattern_t* pPattern; + + cairo_save( pCairo ); + + css::geometry::AffineMatrix2D aTransform( aTexture.AffineTransform ); + cairo_matrix_t aScaleMatrix, aTextureMatrix, aScaledTextureMatrix; + + cairo_matrix_init( &aTextureMatrix, + aTransform.m00, aTransform.m10, aTransform.m01, + aTransform.m11, aTransform.m02, aTransform.m12); + + geometry::IntegerSize2D aSize = aTexture.Bitmap->getSize(); + + cairo_matrix_init_scale( &aScaleMatrix, 1.0/aSize.Width, 1.0/aSize.Height ); + cairo_matrix_multiply( &aScaledTextureMatrix, &aTextureMatrix, &aScaleMatrix ); + cairo_matrix_invert( &aScaledTextureMatrix ); + + // we don't care about repeat mode yet, so the workaround is disabled for now + pPattern = cairo_pattern_create_for_surface( pSurface->getCairoSurface().get() ); + + if( aTexture.RepeatModeX == rendering::TexturingMode::REPEAT && + aTexture.RepeatModeY == rendering::TexturingMode::REPEAT ) + { + cairo_pattern_set_extend( pPattern, CAIRO_EXTEND_REPEAT ); + } + else if ( aTexture.RepeatModeX == rendering::TexturingMode::NONE && + aTexture.RepeatModeY == rendering::TexturingMode::NONE ) + { + cairo_pattern_set_extend( pPattern, CAIRO_EXTEND_NONE ); + } + else if ( aTexture.RepeatModeX == rendering::TexturingMode::CLAMP && + aTexture.RepeatModeY == rendering::TexturingMode::CLAMP ) + { + cairo_pattern_set_extend( pPattern, CAIRO_EXTEND_PAD ); + } + + aScaledTextureMatrix.x0 = basegfx::fround( aScaledTextureMatrix.x0 ); + aScaledTextureMatrix.y0 = basegfx::fround( aScaledTextureMatrix.y0 ); + + double x1, y1, x2, y2; + cairo_path_extents(pCairo, &x1, &y1, &x2, &y2); + aScaledTextureMatrix.x0 -= (x1 * aScaledTextureMatrix.xx); + aScaledTextureMatrix.y0 -= (y1 * aScaledTextureMatrix.yy); + + cairo_pattern_set_matrix( pPattern, &aScaledTextureMatrix ); + + cairo_set_source( pCairo, pPattern ); + if( !bHasAlpha ) + cairo_set_operator( pCairo, CAIRO_OPERATOR_SOURCE ); + cairo_fill( pCairo ); + + cairo_restore( pCairo ); + + cairo_pattern_destroy( pPattern ); + } + + if( data ) + free( data ); + } + else if( aTexture.Gradient.is() ) + { + uno::Reference< lang::XServiceInfo > xRef( aTexture.Gradient, uno::UNO_QUERY ); + + SAL_INFO( "canvas.cairo", "gradient fill" ); + if( xRef.is() && xRef->getImplementationName() == PARAMETRICPOLYPOLYGON_IMPLEMENTATION_NAME ) + { + // TODO(Q1): Maybe use dynamic_cast here + + // TODO(E1): Return value + // TODO(F1): FillRule + SAL_INFO( "canvas.cairo", "known implementation" ); + + ::canvas::ParametricPolyPolygon* pPolyImpl = static_cast< ::canvas::ParametricPolyPolygon* >( aTexture.Gradient.get() ); + css::geometry::AffineMatrix2D aTransform( aTexture.AffineTransform ); + cairo_matrix_t aTextureMatrix; + + cairo_matrix_init( &aTextureMatrix, + aTransform.m00, aTransform.m10, aTransform.m01, + aTransform.m11, aTransform.m02, aTransform.m12); + if( pPolyImpl->getValues().meType == canvas::ParametricPolyPolygon::GradientType::Rectangular ) + { + // no general path gradient yet in cairo; emulate then + cairo_save( pCairo ); + cairo_clip( pCairo ); + + // fill bound rect with start color + cairo_rectangle( pCairo, rBounds.getMinX(), rBounds.getMinY(), + rBounds.getWidth(), rBounds.getHeight() ); + setColor(pCairo,pPolyImpl->getValues().maColors[0]); + cairo_fill(pCairo); + + cairo_transform( pCairo, &aTextureMatrix ); + + // longest line in gradient bound rect + const unsigned int nGradientSize( + static_cast<unsigned int>( + ::basegfx::B2DVector(rBounds.getMinimum() - rBounds.getMaximum()).getLength() + 1.0 ) ); + + // typical number for pixel of the same color (strip size) + const unsigned int nStripSize( nGradientSize < 50 ? 2 : 4 ); + + // use at least three steps, and at utmost the number of color + // steps + const unsigned int nStepCount( + std::max( + 3U, + std::min( + nGradientSize / nStripSize, + 128U )) + 1 ); + + const uno::Sequence<double>* pColors=&pPolyImpl->getValues().maColors[0]; + basegfx::utils::KeyStopLerp aLerper(pPolyImpl->getValues().maStops); + for( unsigned int i=1; i<nStepCount; ++i ) + { + const double fT( i/double(nStepCount) ); + + std::ptrdiff_t nIndex; + double fAlpha; + std::tie(nIndex,fAlpha)=aLerper.lerp(fT); + + setColor(pCairo, lerp(pColors[nIndex], pColors[nIndex+1], fAlpha)); + cairo_rectangle( pCairo, -1+fT, -1+fT, 2-2*fT, 2-2*fT ); + cairo_fill(pCairo); + } + + cairo_restore( pCairo ); + } + else + { + cairo_pattern_t* pPattern = patternFromParametricPolyPolygon( *pPolyImpl ); + + if( pPattern ) + { + SAL_INFO( "canvas.cairo", "filling with pattern" ); + + cairo_save( pCairo ); + + cairo_transform( pCairo, &aTextureMatrix ); + cairo_set_source( pCairo, pPattern ); + cairo_fill( pCairo ); + cairo_restore( pCairo ); + + cairo_pattern_destroy( pPattern ); + } + } + } + } + } + else + cairo_fill( pCairo ); + SAL_INFO( "canvas.cairo", "fill"); + break; + case Stroke: + cairo_stroke( pCairo ); + SAL_INFO( "canvas.cairo", "stroke"); + break; + case Clip: + cairo_clip( pCairo ); + SAL_INFO( "canvas.cairo", "clip"); + break; + } + } + + static void clipNULL( cairo_t *pCairo ) + { + SAL_INFO( "canvas.cairo", "clipNULL"); + cairo_matrix_t aOrigMatrix, aIdentityMatrix; + + /* we set identity matrix here to overcome bug in cairo 0.9.2 + where XCreatePixmap is called with zero width and height. + + it also reaches faster path in cairo clipping code. + */ + cairo_matrix_init_identity( &aIdentityMatrix ); + cairo_get_matrix( pCairo, &aOrigMatrix ); + cairo_set_matrix( pCairo, &aIdentityMatrix ); + + cairo_reset_clip( pCairo ); + cairo_rectangle( pCairo, 0, 0, 1, 1 ); + cairo_clip( pCairo ); + cairo_rectangle( pCairo, 2, 0, 1, 1 ); + cairo_clip( pCairo ); + + /* restore the original matrix */ + cairo_set_matrix( pCairo, &aOrigMatrix ); + } + + void doPolyPolygonImplementation( const ::basegfx::B2DPolyPolygon& aPolyPolygon, + Operation aOperation, + cairo_t* pCairo, + const uno::Sequence< rendering::Texture >* pTextures, + const SurfaceProviderRef& pDevice, + rendering::FillRule eFillrule ) + { + if( pTextures ) + ENSURE_ARG_OR_THROW( pTextures->hasElements(), + "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence"); + + bool bOpToDo = false; + cairo_matrix_t aOrigMatrix, aIdentityMatrix; + double nX, nY, nBX, nBY, nAX, nAY, nLastX(0.0), nLastY(0.0); + + cairo_get_matrix( pCairo, &aOrigMatrix ); + cairo_matrix_init_identity( &aIdentityMatrix ); + cairo_set_matrix( pCairo, &aIdentityMatrix ); + + cairo_set_fill_rule( pCairo, + eFillrule == rendering::FillRule_EVEN_ODD ? + CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING ); + + for( sal_uInt32 nPolygonIndex = 0; nPolygonIndex < aPolyPolygon.count(); nPolygonIndex++ ) + { + const ::basegfx::B2DPolygon& aPolygon( aPolyPolygon.getB2DPolygon( nPolygonIndex ) ); + const sal_uInt32 nPointCount( aPolygon.count() ); + // to correctly render closed curves, need to output first + // point twice (so output one additional point) + const sal_uInt32 nExtendedPointCount( nPointCount + + int(aPolygon.isClosed() && aPolygon.areControlPointsUsed()) ); + + if( nPointCount > 1) + { + bool bIsBezier = aPolygon.areControlPointsUsed(); + ::basegfx::B2DPoint aA, aB, aP; + + for( sal_uInt32 j=0; j < nExtendedPointCount; j++ ) + { + aP = aPolygon.getB2DPoint( j % nPointCount ); + + nX = aP.getX(); + nY = aP.getY(); + cairo_matrix_transform_point( &aOrigMatrix, &nX, &nY ); + + if (!bIsBezier && aOperation == Clip) + { + nX = basegfx::fround( nX ); + nY = basegfx::fround( nY ); + } + + if( aOperation == Stroke ) + { + nX += 0.5; + nY += 0.5; + } + + if( j==0 ) + { + cairo_move_to( pCairo, nX, nY ); + SAL_INFO( "canvas.cairo", "move to " << nX << "," << nY ); + } + else + { + if( bIsBezier ) + { + aA = aPolygon.getNextControlPoint( (j-1) % nPointCount ); + aB = aPolygon.getPrevControlPoint( j % nPointCount ); + + nAX = aA.getX(); + nAY = aA.getY(); + nBX = aB.getX(); + nBY = aB.getY(); + + cairo_matrix_transform_point( &aOrigMatrix, &nAX, &nAY ); + cairo_matrix_transform_point( &aOrigMatrix, &nBX, &nBY ); + + if( aOperation == Stroke ) + { + nAX += 0.5; + nAY += 0.5; + nBX += 0.5; + nBY += 0.5; + } + + // tdf#99165 if the control points are 'empty', create the mathematical + // correct replacement ones to avoid problems with the graphical sub-system + // tdf#101026 The 1st attempt to create a mathematically correct replacement control + // vector was wrong. Best alternative is one as close as possible which means short. + if (basegfx::fTools::equal(nAX, nLastX) && basegfx::fTools::equal(nAY, nLastY)) + { + nAX = nLastX + ((nBX - nLastX) * 0.0005); + nAY = nLastY + ((nBY - nLastY) * 0.0005); + } + + if(basegfx::fTools::equal(nBX, nX) && basegfx::fTools::equal(nBY, nY)) + { + nBX = nX + ((nAX - nX) * 0.0005); + nBY = nY + ((nAY - nY) * 0.0005); + } + + cairo_curve_to( pCairo, nAX, nAY, nBX, nBY, nX, nY ); + } + else + { + cairo_line_to( pCairo, nX, nY ); + SAL_INFO( "canvas.cairo", "line to " << nX << "," << nY ); + } + bOpToDo = true; + } + + nLastX = nX; + nLastY = nY; + } + + if( aPolygon.isClosed() ) + cairo_close_path( pCairo ); + + } + else + { + SAL_INFO( "canvas.cairo", "empty polygon for op: " << aOperation ); + if( aOperation == Clip ) + { + clipNULL( pCairo ); + + return; + } + } + } + + if( aOperation == Fill && pTextures ) + { + cairo_set_matrix( pCairo, &aOrigMatrix ); + doOperation( aOperation, pCairo, pTextures, pDevice, aPolyPolygon.getB2DRange() ); + cairo_set_matrix( pCairo, &aIdentityMatrix ); + } + + if( bOpToDo && ( aOperation != Fill || !pTextures ) ) + doOperation( aOperation, pCairo, pTextures, pDevice, aPolyPolygon.getB2DRange() ); + + cairo_set_matrix( pCairo, &aOrigMatrix ); + + if( aPolyPolygon.count() == 0 && aOperation == Clip ) + clipNULL( pCairo ); + } + + void CanvasHelper::doPolyPolygonPath( const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + Operation aOperation, + bool bNoLineJoin, + const uno::Sequence< rendering::Texture >* pTextures ) const + { + const ::basegfx::B2DPolyPolygon& rPolyPoly( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon) ); + + cairo_t* pCairo = mpCairo.get(); + + if(bNoLineJoin && aOperation == Stroke) + { + // emulate rendering::PathJoinType::NONE by painting single edges + for(sal_uInt32 a(0); a < rPolyPoly.count(); a++) + { + const basegfx::B2DPolygon& aCandidate(rPolyPoly.getB2DPolygon(a)); + const sal_uInt32 nPointCount(aCandidate.count()); + + if(nPointCount) + { + const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount: nPointCount - 1); + basegfx::B2DPolygon aEdge; + aEdge.append(aCandidate.getB2DPoint(0)); + aEdge.append(basegfx::B2DPoint(0.0, 0.0)); + + for(sal_uInt32 b(0); b < nEdgeCount; b++) + { + const sal_uInt32 nNextIndex((b + 1) % nPointCount); + aEdge.setB2DPoint(1, aCandidate.getB2DPoint(nNextIndex)); + aEdge.setNextControlPoint(0, aCandidate.getNextControlPoint(b % nPointCount)); + aEdge.setPrevControlPoint(1, aCandidate.getPrevControlPoint(nNextIndex)); + + doPolyPolygonImplementation( basegfx::B2DPolyPolygon(aEdge), + aOperation, + pCairo, pTextures, + mpSurfaceProvider, + xPolyPolygon->getFillRule() ); + + // prepare next step + aEdge.setB2DPoint(0, aEdge.getB2DPoint(1)); + } + } + } + } + else + { + doPolyPolygonImplementation( rPolyPoly, aOperation, + pCairo, pTextures, + mpSurfaceProvider, + xPolyPolygon->getFillRule() ); + } + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + if( mpCairo ) + { + cairo_save( mpCairo.get() ); + + cairo_set_line_width( mpCairo.get(), 1 ); + + useStates( viewState, renderState, true ); + doPolyPolygonPath( xPolyPolygon, Stroke ); + + cairo_restore( mpCairo.get() ); + } + else + SAL_INFO( "canvas.cairo", "CanvasHelper called after it was disposed"); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "drawPolyPolygon" ); +#endif + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::StrokeAttributes& strokeAttributes ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + if( mpCairo ) + { + cairo_save( mpCairo.get() ); + + useStates( viewState, renderState, true ); + + cairo_matrix_t aMatrix; + cairo_get_matrix( mpCairo.get(), &aMatrix ); + double scaleFactorX = 1; + double scaleFactorY = 0; + cairo_matrix_transform_distance( &aMatrix, &scaleFactorX, &scaleFactorY ); + double scaleFactor = basegfx::B2DVector( scaleFactorX, scaleFactorY ).getLength(); + cairo_set_line_width( mpCairo.get(), strokeAttributes.StrokeWidth * scaleFactor ); + + cairo_set_miter_limit( mpCairo.get(), strokeAttributes.MiterLimit ); + + // FIXME: cairo doesn't handle end cap so far (rodo) + switch( strokeAttributes.StartCapType ) + { + case rendering::PathCapType::BUTT: + cairo_set_line_cap( mpCairo.get(), CAIRO_LINE_CAP_BUTT ); + break; + case rendering::PathCapType::ROUND: + cairo_set_line_cap( mpCairo.get(), CAIRO_LINE_CAP_ROUND ); + break; + case rendering::PathCapType::SQUARE: + cairo_set_line_cap( mpCairo.get(), CAIRO_LINE_CAP_SQUARE ); + break; + } + + bool bNoLineJoin(false); + + switch( strokeAttributes.JoinType ) + { + case rendering::PathJoinType::NONE: + bNoLineJoin = true; + [[fallthrough]]; // cairo doesn't have join type NONE so we use MITER as it's pretty close + case rendering::PathJoinType::MITER: + cairo_set_line_join( mpCairo.get(), CAIRO_LINE_JOIN_MITER ); + break; + case rendering::PathJoinType::ROUND: + cairo_set_line_join( mpCairo.get(), CAIRO_LINE_JOIN_ROUND ); + break; + case rendering::PathJoinType::BEVEL: + cairo_set_line_join( mpCairo.get(), CAIRO_LINE_JOIN_BEVEL ); + break; + } + + //tdf#103026 If the scaling is 0, then all dashes become zero so + //cairo will set the cairo_t status to CAIRO_STATUS_INVALID_DASH + //and no further drawing will occur + if (strokeAttributes.DashArray.hasElements() && scaleFactor > 0.0) + { + auto aDashArray(comphelper::sequenceToContainer<std::vector<double>>(strokeAttributes.DashArray)); + for (auto& rDash : aDashArray) + rDash *= scaleFactor; + cairo_set_dash(mpCairo.get(), aDashArray.data(), aDashArray.size(), 0); + } + + // TODO(rodo) use LineArray of strokeAttributes + + doPolyPolygonPath( xPolyPolygon, Stroke, bNoLineJoin ); + + cairo_restore( mpCairo.get() ); + } + else + SAL_INFO( "canvas.cairo", "CanvasHelper called after it was disposed"); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "strokePolyPolygon" ); +#endif + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas* , + 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* , + 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* , + 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* , + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + if( mpCairo ) + { + cairo_save( mpCairo.get() ); + + useStates( viewState, renderState, true ); + doPolyPolygonPath( xPolyPolygon, Fill ); + + cairo_restore( mpCairo.get() ); + } + else + SAL_INFO( "canvas.cairo", "CanvasHelper called after it was disposed"); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "fillPolyPolygon" ); +#endif + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const uno::Sequence< rendering::Texture >& textures ) + { + if( mpCairo ) + { + cairo_save( mpCairo.get() ); + + useStates( viewState, renderState, true ); + doPolyPolygonPath( xPolyPolygon, Fill, false, &textures ); + + cairo_restore( mpCairo.get() ); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas* , + 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::XCachedPrimitive > CanvasHelper::implDrawBitmapSurface( const rendering::XCanvas* pCanvas, + const SurfaceSharedPtr& pInputSurface, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const geometry::IntegerSize2D& rSize, + bool bModulateColors, + bool bHasAlpha ) + { + SurfaceSharedPtr pSurface=pInputSurface; + uno::Reference< rendering::XCachedPrimitive > rv; + geometry::IntegerSize2D aBitmapSize = rSize; + + if( mpCairo ) + { + cairo_save( mpCairo.get() ); + + cairo_rectangle( mpCairo.get(), 0, 0, maSize.getX(), maSize.getY() ); + cairo_clip( mpCairo.get() ); + + useStates( viewState, renderState, true ); + + cairo_matrix_t aMatrix; + + cairo_get_matrix( mpCairo.get(), &aMatrix ); + if( ! ::rtl::math::approxEqual( aMatrix.xx, 1 ) && + ! ::rtl::math::approxEqual( aMatrix.yy, 1 ) && + ::rtl::math::approxEqual( aMatrix.x0, 0 ) && + ::rtl::math::approxEqual( aMatrix.y0, 0 ) && + basegfx::fround( rSize.Width * aMatrix.xx ) > 8 && + basegfx::fround( rSize.Height* aMatrix.yy ) > 8 ) + { + double dWidth, dHeight; + + dWidth = basegfx::fround( rSize.Width * aMatrix.xx ); + dHeight = basegfx::fround( rSize.Height* aMatrix.yy ); + aBitmapSize.Width = static_cast<sal_Int32>( dWidth ); + aBitmapSize.Height = static_cast<sal_Int32>( dHeight ); + + SurfaceSharedPtr pScaledSurface = mpSurfaceProvider->createSurface( + ::basegfx::B2ISize( aBitmapSize.Width, aBitmapSize.Height ), + bHasAlpha ? CAIRO_CONTENT_COLOR_ALPHA : CAIRO_CONTENT_COLOR ); + CairoSharedPtr pCairo = pScaledSurface->getCairo(); + + cairo_set_operator( pCairo.get(), CAIRO_OPERATOR_SOURCE ); + // add 0.5px to size to avoid rounding errors in cairo, leading sometimes to random data on the image right/bottom borders + cairo_scale( pCairo.get(), (dWidth+0.5)/rSize.Width, (dHeight+0.5)/rSize.Height ); + cairo_set_source_surface( pCairo.get(), pSurface->getCairoSurface().get(), 0, 0 ); + cairo_paint( pCairo.get() ); + + pSurface = pScaledSurface; + + aMatrix.xx = aMatrix.yy = 1; + cairo_set_matrix( mpCairo.get(), &aMatrix ); + + rv.set( + new CachedBitmap( pSurface, viewState, renderState, + // cast away const, need to + // change refcount (as this is + // ~invisible to client code, + // still logically const) + const_cast< rendering::XCanvas* >(pCanvas)) ); + } + + if( !bHasAlpha && mbHaveAlpha ) + { + double x, y, width, height; + + x = y = 0; + width = aBitmapSize.Width; + height = aBitmapSize.Height; + cairo_matrix_transform_point( &aMatrix, &x, &y ); + cairo_matrix_transform_distance( &aMatrix, &width, &height ); + + // in case the bitmap doesn't have alpha and covers whole area + // we try to change surface to plain rgb + SAL_INFO( "canvas.cairo","chance to change surface to rgb, " << x << ", " << y << ", " << width << " x " << height << " (" << maSize.getX() << " x " << maSize.getY() << ")" ); + if( x <= 0 && y <= 0 && x + width >= maSize.getX() && y + height >= maSize.getY() ) + { + SAL_INFO( "canvas.cairo","trying to change surface to rgb"); + if( mpSurfaceProvider ) { + SurfaceSharedPtr pNewSurface = mpSurfaceProvider->changeSurface(); + + if( pNewSurface ) + setSurface( pNewSurface, false ); + + // set state to new mpCairo.get() + useStates( viewState, renderState, true ); + // use the possibly modified matrix + cairo_set_matrix( mpCairo.get(), &aMatrix ); + } + } + } + + cairo_set_source_surface( mpCairo.get(), pSurface->getCairoSurface().get(), 0, 0 ); + if( !bHasAlpha && + ::rtl::math::approxEqual( aMatrix.xx, 1 ) && + ::rtl::math::approxEqual( aMatrix.yy, 1 ) && + ::rtl::math::approxEqual( aMatrix.x0, 0 ) && + ::rtl::math::approxEqual( aMatrix.y0, 0 ) ) + cairo_set_operator( mpCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_pattern_set_extend( cairo_get_source(mpCairo.get()), CAIRO_EXTEND_PAD ); + cairo_rectangle( mpCairo.get(), 0, 0, aBitmapSize.Width, aBitmapSize.Height ); + cairo_clip( mpCairo.get() ); + + // Use cairo_matrix_transform_distance() to determine the scaling, as that works even if + // the matrix also has rotation. + double fPixelWidth = rSize.Width; + double fPixelHeight = rSize.Height; + cairo_matrix_transform_distance(&aMatrix, &fPixelWidth, &fPixelHeight); + int nPixelWidth = std::round(fPixelWidth); + int nPixelHeight = std::round(fPixelHeight); + if (std::abs(nPixelWidth) > 0 && std::abs(nPixelHeight) > 0) + { + // Only render the image if it's at least 1x1 px sized. + if (bModulateColors) + cairo_paint_with_alpha(mpCairo.get(), renderState.DeviceColor[3]); + else + { + cairo_paint(mpCairo.get()); + if (cairo_status(mpCairo.get()) != CAIRO_STATUS_SUCCESS) + { + SAL_WARN("canvas.cairo", "cairo_paint() failed: " << cairo_status_to_string( + cairo_status(mpCairo.get()))); + } + } + } + cairo_restore( mpCairo.get() ); + } + else + SAL_INFO( "canvas.cairo", "CanvasHelper called after it was disposed"); + + return rv; // uno::Reference< rendering::XCachedPrimitive >(NULL); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas* pCanvas, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + uno::Reference< rendering::XCachedPrimitive > rv; + unsigned char* data = nullptr; + bool bHasAlpha = false; + SurfaceSharedPtr pSurface = surfaceFromXBitmap( xBitmap, mpSurfaceProvider, data, bHasAlpha ); + geometry::IntegerSize2D aSize = xBitmap->getSize(); + + if( pSurface ) + { + rv = implDrawBitmapSurface( pCanvas, pSurface, viewState, renderState, aSize, false, bHasAlpha ); + + if( data ) + free( data ); + } + else + rv.set(nullptr); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "drawBitmap" ); +#endif + + return rv; + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas* pCanvas, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + uno::Reference< rendering::XCachedPrimitive > rv; + unsigned char* data = nullptr; + bool bHasAlpha = false; + SurfaceSharedPtr pSurface = surfaceFromXBitmap( xBitmap, mpSurfaceProvider, data, bHasAlpha ); + geometry::IntegerSize2D aSize = xBitmap->getSize(); + + if( pSurface ) + { + rv = implDrawBitmapSurface( pCanvas, pSurface, viewState, renderState, aSize, true, bHasAlpha ); + + if( data ) + free( data ); + } + else + rv.set(nullptr); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "drawBitmap" ); +#endif + + return rv; + } + + + geometry::IntegerSize2D CanvasHelper::getSize() const + { + if( !mpSurfaceProvider ) + return geometry::IntegerSize2D(1, 1); // we're disposed + + return ::basegfx::unotools::integerSize2DFromB2ISize( maSize ); + } + + uno::Reference< rendering::XBitmap > CanvasHelper::getScaledBitmap( const geometry::RealSize2D& newSize, + bool /*beFast*/ ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + if( mpCairo ) + { + return uno::Reference< rendering::XBitmap >( new CanvasBitmap( ::basegfx::B2ISize( ::canvas::tools::roundUp( newSize.Width ), + ::canvas::tools::roundUp( newSize.Height ) ), + mpSurfaceProvider, mpDevice, false ) ); + } + else + SAL_INFO( "canvas.cairo", "CanvasHelper called after it was disposed"); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "getScaledBitmap" ); +#endif + + return uno::Reference< rendering::XBitmap >(); + } + + uno::Sequence< sal_Int8 > CanvasHelper::getData( rendering::IntegerBitmapLayout& aLayout, + const geometry::IntegerRectangle2D& rect ) + { + if( mpCairo ) + { + const sal_Int32 nWidth( rect.X2 - rect.X1 ); + const sal_Int32 nHeight( rect.Y2 - rect.Y1 ); + const cairo_format_t eFormat( mbHaveAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24 ); + uno::Sequence< sal_Int8 > aRes( 4*nWidth*nHeight ); + sal_Int8* pData = aRes.getArray(); + cairo_surface_t* pImageSurface = cairo_image_surface_create_for_data( reinterpret_cast<unsigned char *>(pData), + eFormat, + nWidth, nHeight, 4*nWidth ); + cairo_t* pCairo = cairo_create( pImageSurface ); + cairo_set_source_surface( pCairo, mpSurface->getCairoSurface().get(), -rect.X1, -rect.Y1); + cairo_paint( pCairo ); + cairo_destroy( pCairo ); + cairo_surface_destroy( pImageSurface ); + + aLayout = impl_getMemoryLayout( nWidth, nHeight ); + + return aRes; + } + + return uno::Sequence< sal_Int8 >(); + } + + uno::Sequence< sal_Int8 > CanvasHelper::getPixel( rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerPoint2D& /*pos*/ ) + { + return uno::Sequence< sal_Int8 >(); + } + + namespace + { + class CairoColorSpace : public cppu::WeakImplHelper< css::rendering::XIntegerBitmapColorSpace > + { + private: + uno::Sequence< sal_Int8 > maComponentTags; + uno::Sequence< sal_Int32 > maBitCounts; + + virtual ::sal_Int8 SAL_CALL getType( ) override + { + return rendering::ColorSpaceType::RGB; + } + virtual uno::Sequence< ::sal_Int8 > SAL_CALL getComponentTags( ) override + { + return maComponentTags; + } + virtual ::sal_Int8 SAL_CALL getRenderingIntent( ) override + { + return rendering::RenderingIntent::PERCEPTUAL; + } + virtual uno::Sequence< beans::PropertyValue > SAL_CALL getProperties( ) override + { + return uno::Sequence< beans::PropertyValue >(); + } + virtual uno::Sequence< double > SAL_CALL convertColorSpace( const uno::Sequence< double >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> aIntermediate( + convertToARGB(deviceColor)); + return targetColorSpace->convertFromARGB(aIntermediate); + } + virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertToRGB( const uno::Sequence< double >& deviceColor ) override + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + const double fAlpha(pIn[3]); + if( fAlpha == 0.0 ) + *pOut++ = rendering::RGBColor(0.0, 0.0, 0.0); + else + *pOut++ = rendering::RGBColor(pIn[2]/fAlpha,pIn[1]/fAlpha,pIn[0]/fAlpha); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) override + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + const double fAlpha(pIn[3]); + if( fAlpha == 0.0 ) + *pOut++ = rendering::ARGBColor(0.0, 0.0, 0.0, 0.0); + else + *pOut++ = rendering::ARGBColor(fAlpha,pIn[2]/fAlpha,pIn[1]/fAlpha,pIn[0]/fAlpha); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToPARGB( const uno::Sequence< double >& deviceColor ) override + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor(pIn[3],pIn[2],pIn[1],pIn[1]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< double > SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override + { + const rendering::RGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( std::size_t i=0; i<nLen; ++i ) + { + *pColors++ = pIn->Blue; + *pColors++ = pIn->Green; + *pColors++ = pIn->Red; + *pColors++ = 1.0; + ++pIn; + } + return aRes; + } + virtual uno::Sequence< double > SAL_CALL convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( std::size_t i=0; i<nLen; ++i ) + { + *pColors++ = pIn->Alpha*pIn->Blue; + *pColors++ = pIn->Alpha*pIn->Green; + *pColors++ = pIn->Alpha*pIn->Red; + *pColors++ = pIn->Alpha; + ++pIn; + } + return aRes; + } + virtual uno::Sequence< double > SAL_CALL convertFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( std::size_t i=0; i<nLen; ++i ) + { + *pColors++ = pIn->Blue; + *pColors++ = pIn->Green; + *pColors++ = pIn->Red; + *pColors++ = pIn->Alpha; + ++pIn; + } + return aRes; + } + + // XIntegerBitmapColorSpace + virtual ::sal_Int32 SAL_CALL getBitsPerPixel( ) override + { + return 32; + } + virtual uno::Sequence< ::sal_Int32 > SAL_CALL getComponentBitCounts( ) override + { + return maBitCounts; + } + virtual ::sal_Int8 SAL_CALL getEndianness( ) override + { + return util::Endianness::LITTLE; + } + virtual uno::Sequence<double> SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + if( dynamic_cast<CairoColorSpace*>(targetColorSpace.get()) ) + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence<double> aRes(nLen); + double* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + } + return aRes; + } + else + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> aIntermediate( + convertIntegerToARGB(deviceColor)); + return targetColorSpace->convertFromARGB(aIntermediate); + } + } + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertToIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, + const uno::Reference< rendering::XIntegerBitmapColorSpace >& targetColorSpace ) override + { + if( dynamic_cast<CairoColorSpace*>(targetColorSpace.get()) ) + { + // it's us, so simply pass-through the data + return deviceColor; + } + else + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> aIntermediate( + convertIntegerToARGB(deviceColor)); + return targetColorSpace->convertIntegerFromARGB(aIntermediate); + } + } + virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertIntegerToRGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + const double fAlpha(static_cast<sal_uInt8>(pIn[3])); + if( fAlpha ) + *pOut++ = rendering::RGBColor( + pIn[2]/fAlpha, + pIn[1]/fAlpha, + pIn[0]/fAlpha); + else + *pOut++ = rendering::RGBColor(0,0,0); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + const double fAlpha(static_cast<sal_uInt8>(pIn[3])); + if( fAlpha ) + *pOut++ = rendering::ARGBColor( + fAlpha/255.0, + pIn[2]/fAlpha, + pIn[1]/fAlpha, + pIn[0]/fAlpha); + else + *pOut++ = rendering::ARGBColor(0,0,0,0); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToPARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor( + vcl::unotools::toDoubleColor(pIn[3]), + vcl::unotools::toDoubleColor(pIn[2]), + vcl::unotools::toDoubleColor(pIn[1]), + vcl::unotools::toDoubleColor(pIn[0])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override + { + const rendering::RGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( std::size_t i=0; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Blue); + *pColors++ = vcl::unotools::toByteColor(pIn->Green); + *pColors++ = vcl::unotools::toByteColor(pIn->Red); + *pColors++ = -1; + ++pIn; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( std::size_t i=0; i<nLen; ++i ) + { + const double fAlpha(pIn->Alpha); + *pColors++ = vcl::unotools::toByteColor(fAlpha*pIn->Blue); + *pColors++ = vcl::unotools::toByteColor(fAlpha*pIn->Green); + *pColors++ = vcl::unotools::toByteColor(fAlpha*pIn->Red); + *pColors++ = vcl::unotools::toByteColor(fAlpha); + ++pIn; + } + return aRes; + } + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( std::size_t i=0; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Blue); + *pColors++ = vcl::unotools::toByteColor(pIn->Green); + *pColors++ = vcl::unotools::toByteColor(pIn->Red); + *pColors++ = vcl::unotools::toByteColor(pIn->Alpha); + ++pIn; + } + return aRes; + } + + public: + CairoColorSpace() : + maComponentTags(4), + maBitCounts(4) + { + sal_Int8* pTags = maComponentTags.getArray(); + sal_Int32* pBitCounts = maBitCounts.getArray(); + pTags[0] = rendering::ColorComponentTag::RGB_BLUE; + pTags[1] = rendering::ColorComponentTag::RGB_GREEN; + pTags[2] = rendering::ColorComponentTag::RGB_RED; + pTags[3] = rendering::ColorComponentTag::PREMULTIPLIED_ALPHA; + + pBitCounts[0] = + pBitCounts[1] = + pBitCounts[2] = + pBitCounts[3] = 8; + } + }; + + class CairoNoAlphaColorSpace : public cppu::WeakImplHelper< css::rendering::XIntegerBitmapColorSpace > + { + private: + uno::Sequence< sal_Int8 > maComponentTags; + uno::Sequence< sal_Int32 > maBitCounts; + + virtual ::sal_Int8 SAL_CALL getType( ) override + { + return rendering::ColorSpaceType::RGB; + } + virtual uno::Sequence< ::sal_Int8 > SAL_CALL getComponentTags( ) override + { + return maComponentTags; + } + virtual ::sal_Int8 SAL_CALL getRenderingIntent( ) override + { + return rendering::RenderingIntent::PERCEPTUAL; + } + virtual uno::Sequence< beans::PropertyValue > SAL_CALL getProperties( ) override + { + return uno::Sequence< beans::PropertyValue >(); + } + virtual uno::Sequence< double > SAL_CALL convertColorSpace( const uno::Sequence< double >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> aIntermediate( + convertToARGB(deviceColor)); + return targetColorSpace->convertFromARGB(aIntermediate); + } + virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertToRGB( const uno::Sequence< double >& deviceColor ) override + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::RGBColor(pIn[2], pIn[1], pIn[0]); + pIn += 4; + } + return aRes; + } + uno::Sequence< rendering::ARGBColor > impl_convertToARGB( const uno::Sequence< double >& deviceColor ) + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor(1.0, pIn[2], pIn[1], pIn[0]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) override + { + return impl_convertToARGB( deviceColor ); + } + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToPARGB( const uno::Sequence< double >& deviceColor ) override + { + return impl_convertToARGB( deviceColor ); + } + virtual uno::Sequence< double > SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override + { + const rendering::RGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( std::size_t i=0; i<nLen; ++i ) + { + *pColors++ = pIn->Blue; + *pColors++ = pIn->Green; + *pColors++ = pIn->Red; + *pColors++ = 1.0; // the value does not matter + ++pIn; + } + return aRes; + } + uno::Sequence< double > impl_convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( std::size_t i=0; i<nLen; ++i ) + { + *pColors++ = pIn->Blue; + *pColors++ = pIn->Green; + *pColors++ = pIn->Red; + *pColors++ = 1.0; // the value does not matter + ++pIn; + } + return aRes; + } + virtual uno::Sequence< double > SAL_CALL convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + return impl_convertFromARGB( rgbColor ); + } + virtual uno::Sequence< double > SAL_CALL convertFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + return impl_convertFromARGB( rgbColor ); + } + + // XIntegerBitmapColorSpace + virtual ::sal_Int32 SAL_CALL getBitsPerPixel( ) override + { + return 32; + } + virtual uno::Sequence< ::sal_Int32 > SAL_CALL getComponentBitCounts( ) override + { + return maBitCounts; + } + virtual ::sal_Int8 SAL_CALL getEndianness( ) override + { + return util::Endianness::LITTLE; + } + virtual uno::Sequence<double> SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + if( dynamic_cast<CairoColorSpace*>(targetColorSpace.get()) ) + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence<double> aRes(nLen); + double* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = 1.0; pIn++; // the value does not matter + } + return aRes; + } + else + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> aIntermediate( + convertIntegerToARGB(deviceColor)); + return targetColorSpace->convertFromARGB(aIntermediate); + } + } + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertToIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, + const uno::Reference< rendering::XIntegerBitmapColorSpace >& targetColorSpace ) override + { + if( dynamic_cast<CairoNoAlphaColorSpace*>(targetColorSpace.get()) ) + { + // it's us, so simply pass-through the data + return deviceColor; + } + else + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> aIntermediate( + convertIntegerToARGB(deviceColor)); + return targetColorSpace->convertIntegerFromARGB(aIntermediate); + } + } + virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertIntegerToRGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::RGBColor( pIn[2], pIn[1], pIn[0] ); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + return impl_convertIntegerToARGB( deviceColor ); + } + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToPARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + return impl_convertIntegerToARGB( deviceColor ); + } + uno::Sequence< rendering::ARGBColor > impl_convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor( + 1.0, + vcl::unotools::toDoubleColor(pIn[2]), + vcl::unotools::toDoubleColor(pIn[1]), + vcl::unotools::toDoubleColor(pIn[0])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override + { + const rendering::RGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( std::size_t i=0; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Blue); + *pColors++ = vcl::unotools::toByteColor(pIn->Green); + *pColors++ = vcl::unotools::toByteColor(pIn->Red); + *pColors++ = -1; // the value does not matter + ++pIn; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + return impl_convertIntegerFromARGB( rgbColor ); + } + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + return impl_convertIntegerFromARGB( rgbColor ); + } + uno::Sequence< ::sal_Int8 > impl_convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( std::size_t i=0; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Blue); + *pColors++ = vcl::unotools::toByteColor(pIn->Green); + *pColors++ = vcl::unotools::toByteColor(pIn->Red); + *pColors++ = -1; // the value does not matter + ++pIn; + } + return aRes; + } + + public: + CairoNoAlphaColorSpace() : + maComponentTags(3), + maBitCounts(3) + { + sal_Int8* pTags = maComponentTags.getArray(); + sal_Int32* pBitCounts = maBitCounts.getArray(); + pTags[0] = rendering::ColorComponentTag::RGB_BLUE; + pTags[1] = rendering::ColorComponentTag::RGB_GREEN; + pTags[2] = rendering::ColorComponentTag::RGB_RED; + + pBitCounts[0] = + pBitCounts[1] = + pBitCounts[2] = 8; + } + }; + + uno::Reference<rendering::XIntegerBitmapColorSpace>& GetCairoNoAlphaColorSpace() + { + static uno::Reference<rendering::XIntegerBitmapColorSpace> SPACE = new CairoNoAlphaColorSpace(); + return SPACE; + }; + + uno::Reference<rendering::XIntegerBitmapColorSpace>& GetCairoColorSpace() + { + static uno::Reference<rendering::XIntegerBitmapColorSpace> SPACE = new CairoColorSpace(); + return SPACE; + }; + + } + + rendering::IntegerBitmapLayout CanvasHelper::getMemoryLayout() + { + if( !mpCairo ) + return rendering::IntegerBitmapLayout(); // we're disposed + + const geometry::IntegerSize2D aSize(getSize()); + + return impl_getMemoryLayout( aSize.Width, aSize.Height ); + } + + rendering::IntegerBitmapLayout + CanvasHelper::impl_getMemoryLayout( const sal_Int32 nWidth, const sal_Int32 nHeight ) + { + rendering::IntegerBitmapLayout aLayout; + + aLayout.ScanLines = nHeight; + aLayout.ScanLineBytes = nWidth*4; + aLayout.ScanLineStride = aLayout.ScanLineBytes; + aLayout.PlaneStride = 0; + aLayout.ColorSpace = mbHaveAlpha ? GetCairoColorSpace() : GetCairoNoAlphaColorSpace(); + aLayout.Palette.clear(); + aLayout.IsMsbFirst = false; + + return aLayout; + } + + + bool CanvasHelper::repaint( const SurfaceSharedPtr& pSurface, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + SAL_INFO( "canvas.cairo", "CanvasHelper::repaint"); + + if( !mpCairo ) + return true; + + cairo_save( mpCairo.get() ); + + cairo_rectangle( mpCairo.get(), 0, 0, maSize.getX(), maSize.getY() ); + cairo_clip( mpCairo.get() ); + + useStates( viewState, renderState, true ); + + cairo_matrix_t aMatrix; + + cairo_get_matrix( mpCairo.get(), &aMatrix ); + aMatrix.xx = aMatrix.yy = 1; + cairo_set_matrix( mpCairo.get(), &aMatrix ); + + cairo_set_source_surface( mpCairo.get(), pSurface->getCairoSurface().get(), 0, 0 ); + cairo_paint( mpCairo.get() ); + cairo_restore( mpCairo.get() ); + + return true; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvashelper.hxx b/canvas/source/cairo/cairo_canvashelper.hxx new file mode 100644 index 000000000..21dbf79d7 --- /dev/null +++ b/canvas/source/cairo/cairo_canvashelper.hxx @@ -0,0 +1,271 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/geometry/IntegerPoint2D.hpp> +#include <com/sun/star/geometry/IntegerRectangle2D.hpp> +#include <com/sun/star/rendering/IntegerBitmapLayout.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> + +#include <vcl/vclptr.hxx> +#include <vcl/virdev.hxx> + +#include <vcl/cairo.hxx> +#include "cairo_surfaceprovider.hxx" + +class VirtualDevice; + +namespace basegfx { + class B2DPolyPolygon; +} + +namespace cairocanvas +{ + class SpriteCanvas; + + enum Operation { + Stroke, + Fill, + Clip + }; + + class CanvasHelper + { + public: + /// make noncopyable + CanvasHelper(const CanvasHelper&) = delete; + const CanvasHelper& operator=(const CanvasHelper&) = delete; + + CanvasHelper(); + + /// Release all references + void disposing(); + + /** Initialize canvas helper + + This method late-initializes the canvas helper, providing + it with the necessary device and size. Note that the + CanvasHelper does <em>not</em> take ownership of the + passed rDevice reference, nor does it perform any + reference counting. Thus, to prevent the reference counted + SpriteCanvas object from deletion, the user of this class + is responsible for holding ref-counted references itself! + + @param rSizePixel + Size of the output surface in pixel. + + @param rDevice + Reference device this canvas is associated with + + */ + void init( const ::basegfx::B2ISize& rSizePixel, + SurfaceProvider& rSurfaceProvider, + css::rendering::XGraphicDevice* pDevice ); + + void setSize( const ::basegfx::B2ISize& rSize ); + void setSurface( const ::cairo::SurfaceSharedPtr& pSurface, bool bHasAlpha ); + + // CanvasHelper functionality + // ========================== + + // XCanvas (only providing, not implementing the + // interface. Also note subtle method parameter differences) + void clear(); + void drawLine( const css::rendering::XCanvas* pCanvas, + const css::geometry::RealPoint2D& aStartPoint, + const css::geometry::RealPoint2D& aEndPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + void drawBezier( const css::rendering::XCanvas* pCanvas, + const css::geometry::RealBezierSegment2D& aBezierSegment, + const css::geometry::RealPoint2D& aEndPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + drawPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokePolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokeTexturedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< css::rendering::Texture >& textures, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokeTextureMappedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< css::rendering::Texture >& textures, + const css::uno::Reference< css::geometry::XMapping2D >& xMapping, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XPolyPolygon2D > + queryStrokeShapes( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillTexturedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< css::rendering::Texture >& textures ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillTextureMappedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< css::rendering::Texture >& textures, + const css::uno::Reference< css::geometry::XMapping2D >& xMapping ); + + css::uno::Reference< css::rendering::XCanvasFont > + createFont( const css::rendering::XCanvas* pCanvas, + const css::rendering::FontRequest& fontRequest, + const css::uno::Sequence< css::beans::PropertyValue >& extraFontProperties, + const css::geometry::Matrix2D& fontMatrix ); + + css::uno::Sequence< css::rendering::FontInfo > + queryAvailableFonts( const css::rendering::XCanvas* pCanvas, + const css::rendering::FontInfo& aFilter, + const css::uno::Sequence< css::beans::PropertyValue >& aFontProperties ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawText( const css::rendering::XCanvas* pCanvas, + const css::rendering::StringContext& text, + const css::uno::Reference< css::rendering::XCanvasFont >& xFont, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + sal_Int8 textDirection ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawTextLayout( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XTextLayout >& laidOutText, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawBitmap( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + drawBitmapModulated( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XGraphicDevice > + getDevice() { return css::uno::Reference< css::rendering::XGraphicDevice >(mpDevice); } + + // BitmapCanvasHelper functionality + // ================================ + + css::geometry::IntegerSize2D getSize() const; + + css::uno::Reference< css::rendering::XBitmap > + getScaledBitmap( const css::geometry::RealSize2D& newSize, + bool beFast ); + + css::uno::Sequence< sal_Int8 > + getData( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ); + + css::uno::Sequence< sal_Int8 > + getPixel( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ); + + css::rendering::IntegerBitmapLayout getMemoryLayout(); + + void doPolyPolygonPath( const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + Operation aOperation, + bool bNoLineJoin = false, + const css::uno::Sequence< css::rendering::Texture >* pTextures=nullptr ) const; + + css::uno::Reference< css::rendering::XCachedPrimitive > implDrawBitmapSurface( + const css::rendering::XCanvas* pCanvas, + const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::geometry::IntegerSize2D& rSize, + bool bModulateColors, + bool bHasAlpha ); + + bool repaint( const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + protected: + /** Surface provider + + Deliberately not a refcounted reference, because of + potential circular references for canvas. Provides us with + our output surface and associated functionality. + */ + SurfaceProvider* mpSurfaceProvider; + + /** Phyical output device + + Deliberately not a refcounted reference, because of + potential circular references for spritecanvas. + */ + css::rendering::XGraphicDevice* mpDevice; + + private: + + VclPtr<VirtualDevice> mpVirtualDevice; + + void useStates( const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + bool setColor ); + + css::rendering::IntegerBitmapLayout impl_getMemoryLayout( sal_Int32 nWidth, sal_Int32 nHeight ); + + /// When true, content is able to represent alpha + bool mbHaveAlpha; + + ::cairo::CairoSharedPtr mpCairo; + ::cairo::SurfaceSharedPtr mpSurface; + ::basegfx::B2ISize maSize; + }; + + /// also needed from SpriteHelper + void doPolyPolygonImplementation( const ::basegfx::B2DPolyPolygon& aPolyPolygon, + Operation aOperation, + cairo_t* pCairo, + const css::uno::Sequence< css::rendering::Texture >* pTextures, + const SurfaceProviderRef& pDevice, + css::rendering::FillRule eFillrule ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvashelper_text.cxx b/canvas/source/cairo/cairo_canvashelper_text.cxx new file mode 100644 index 000000000..5f78bb71a --- /dev/null +++ b/canvas/source/cairo/cairo_canvashelper_text.cxx @@ -0,0 +1,300 @@ +/* -*- 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 <com/sun/star/rendering/TextDirection.hpp> + +#include <rtl/math.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/canvastools.hxx> +#include <vcl/metric.hxx> +#include <vcl/virdev.hxx> + +#include <canvas/canvastools.hxx> +#include <verifyinput.hxx> + +#include "cairo_canvasfont.hxx" +#include "cairo_canvashelper.hxx" +#include "cairo_textlayout.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* , + const rendering::FontRequest& fontRequest, + const uno::Sequence< beans::PropertyValue >& extraFontProperties, + const geometry::Matrix2D& fontMatrix ) + { + return uno::Reference< rendering::XCanvasFont >( new CanvasFont( fontRequest, extraFontProperties, fontMatrix, mpSurfaceProvider )); + } + + uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* , + const rendering::FontInfo& /*aFilter*/, + const uno::Sequence< beans::PropertyValue >& /*aFontProperties*/ ) + { + // TODO + return uno::Sequence< rendering::FontInfo >(); + } + + static bool + setupFontTransform( ::OutputDevice const & rOutDev, + ::Point& o_rPoint, + vcl::Font& io_rVCLFont, + const rendering::ViewState& rViewState, + const rendering::RenderState& rRenderState ) + { + ::basegfx::B2DHomMatrix aMatrix; + + ::canvas::tools::mergeViewAndRenderTransform(aMatrix, + rViewState, + rRenderState); + + ::basegfx::B2DTuple aScale; + ::basegfx::B2DTuple aTranslate; + double nRotate, nShearX; + + aMatrix.decompose( aScale, aTranslate, nRotate, nShearX ); + + // query font metric _before_ tampering with width and height + if( !::rtl::math::approxEqual(aScale.getX(), aScale.getY()) ) + { + // retrieve true font width + const sal_Int32 nFontWidth( rOutDev.GetFontMetric( io_rVCLFont ).GetAverageFontWidth() ); + + const sal_Int32 nScaledFontWidth( ::basegfx::fround(nFontWidth * aScale.getX()) ); + + if( !nScaledFontWidth ) + { + // scale is smaller than one pixel - disable text + // output altogether + return false; + } + + io_rVCLFont.SetAverageFontWidth( nScaledFontWidth ); + } + + if( !::rtl::math::approxEqual(aScale.getY(), 1.0) ) + { + const sal_Int32 nFontHeight( io_rVCLFont.GetFontHeight() ); + io_rVCLFont.SetFontHeight( ::basegfx::fround(nFontHeight * aScale.getY()) ); + } + + io_rVCLFont.SetOrientation( Degree10( ::basegfx::fround(-basegfx::rad2deg<10>(fmod(nRotate, 2*M_PI))) ) ); + + // TODO(F2): Missing functionality in VCL: shearing + o_rPoint.setX( ::basegfx::fround(aTranslate.getX()) ); + o_rPoint.setY( ::basegfx::fround(aTranslate.getY()) ); + + return true; + } + + static void + setupOutDevState( OutputDevice& rOutDev, + const rendering::XCanvas* pOwner, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ::canvas::tools::verifyInput( renderState, + __func__, + const_cast<rendering::XCanvas*>(pOwner), // only for refcount + 2, + 3 /* text */ ); + + // TODO(P2): Don't change clipping all the time, maintain current clip + // state and change only when update is necessary + ::canvas::tools::clipOutDev(viewState, renderState, rOutDev); + + Color aColor( COL_WHITE ); + + if( renderState.DeviceColor.getLength() > 2 ) + { + aColor = vcl::unotools::stdColorSpaceSequenceToColor( renderState.DeviceColor ); + } + + // extract alpha, and make color opaque + // afterwards. Otherwise, OutputDevice won't draw anything + aColor.SetAlpha(255); + + rOutDev.SetTextColor( aColor ); + } + + namespace { + + class DeviceSettingsGuard + { + private: + VclPtr<OutputDevice> mpVirtualDevice; + bool mbMappingWasEnabled; + public: + DeviceSettingsGuard(OutputDevice *pVirtualDevice) + : mpVirtualDevice(pVirtualDevice) + , mbMappingWasEnabled(mpVirtualDevice->IsMapModeEnabled()) + { + mpVirtualDevice->Push(); + mpVirtualDevice->EnableMapMode(false); + } + + ~DeviceSettingsGuard() + { + mpVirtualDevice->EnableMapMode(mbMappingWasEnabled); + mpVirtualDevice->Pop(); + } + }; + + } + + static bool setupTextOutput( OutputDevice& rOutDev, + const rendering::XCanvas* pOwner, + ::Point& o_rOutPos, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const uno::Reference< rendering::XCanvasFont >& xFont ) + { + setupOutDevState( rOutDev, pOwner, viewState, renderState ); + + CanvasFont* pFont = dynamic_cast< CanvasFont* >( xFont.get() ); + + ENSURE_ARG_OR_THROW( pFont, + "CanvasHelper::setupTextOutput(): Font not compatible with this canvas" ); + + vcl::Font aVCLFont = pFont->getVCLFont(); + + Color aColor( COL_BLACK ); + + if( renderState.DeviceColor.getLength() > 2 ) + { + aColor = vcl::unotools::stdColorSpaceSequenceToColor(renderState.DeviceColor ); + } + + // setup font color + aVCLFont.SetColor( aColor ); + aVCLFont.SetFillColor( aColor ); + + // no need to replicate this for mp2ndOutDev, we're modifying only aVCLFont here. + if( !setupFontTransform( rOutDev, o_rOutPos, aVCLFont, viewState, renderState ) ) + return false; + + rOutDev.SetFont( aVCLFont ); + + return true; + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* pOwner, + const rendering::StringContext& text, + const uno::Reference< rendering::XCanvasFont >& xFont, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + sal_Int8 textDirection ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + ENSURE_ARG_OR_THROW( xFont.is(), + "CanvasHelper::drawText(): font is NULL"); + + if( !mpVirtualDevice ) + mpVirtualDevice = mpSurface->createVirtualDevice(); + + if( mpVirtualDevice ) + { + DeviceSettingsGuard aGuard(mpVirtualDevice.get()); + + ::Point aOutpos; + if( !setupTextOutput( *mpVirtualDevice, pOwner, aOutpos, viewState, renderState, xFont ) ) + return uno::Reference< rendering::XCachedPrimitive >(nullptr); // no output necessary + + // change text direction and layout mode + vcl::text::ComplexTextLayoutFlags nLayoutMode(vcl::text::ComplexTextLayoutFlags::Default); + switch( textDirection ) + { + case rendering::TextDirection::WEAK_LEFT_TO_RIGHT: + case rendering::TextDirection::STRONG_LEFT_TO_RIGHT: + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiStrong; + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::TextOriginLeft; + break; + + case rendering::TextDirection::WEAK_RIGHT_TO_LEFT: + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl; + [[fallthrough]]; + case rendering::TextDirection::STRONG_RIGHT_TO_LEFT: + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong; + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::TextOriginRight; + break; + } + + // TODO(F2): alpha + mpVirtualDevice->SetLayoutMode( nLayoutMode ); + + rtl::Reference pTextLayout( new TextLayout(text, textDirection, 0, CanvasFont::Reference(dynamic_cast< CanvasFont* >( xFont.get() )), mpSurfaceProvider) ); + pTextLayout->draw(*mpVirtualDevice, aOutpos, viewState, renderState); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* pOwner, + const uno::Reference< rendering::XTextLayout >& xLayoutedText, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_ARG_OR_THROW( xLayoutedText.is(), + "CanvasHelper::drawTextLayout(): layout is NULL"); + + TextLayout* pTextLayout = dynamic_cast< TextLayout* >( xLayoutedText.get() ); + + if( pTextLayout ) + { + if( !mpVirtualDevice ) + mpVirtualDevice = mpSurface->createVirtualDevice(); + + if( mpVirtualDevice ) + { + DeviceSettingsGuard aGuard(mpVirtualDevice.get()); + + // TODO(T3): Race condition. We're taking the font + // from xLayoutedText, and then calling draw() at it, + // without exclusive access. Move setupTextOutput(), + // e.g. to impltools? + + ::Point aOutpos; + if( !setupTextOutput( *mpVirtualDevice, pOwner, aOutpos, viewState, renderState, xLayoutedText->getFont() ) ) + return uno::Reference< rendering::XCachedPrimitive >(nullptr); // no output necessary + + // TODO(F2): What about the offset scalings? + pTextLayout->draw(*mpVirtualDevice, aOutpos, viewState, renderState); + } + } + else + { + ENSURE_ARG_OR_THROW( false, + "CanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" ); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_devicehelper.cxx b/canvas/source/cairo/cairo_devicehelper.cxx new file mode 100644 index 000000000..5dacea7ae --- /dev/null +++ b/canvas/source/cairo/cairo_devicehelper.cxx @@ -0,0 +1,257 @@ +/* -*- 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 <basegfx/utils/canvastools.hxx> +#include <basegfx/utils/unopolypolygon.hxx> +#include <tools/stream.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/dibtools.hxx> + +#include <canvas/canvastools.hxx> + +#include "cairo_canvasbitmap.hxx" +#include "cairo_devicehelper.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + DeviceHelper::DeviceHelper() : + mpSurfaceProvider( nullptr ), + mpRefDevice( nullptr ) + { + } + + void DeviceHelper::implInit( SurfaceProvider& rSurfaceProvider, + OutputDevice& rRefDevice ) + { + mpSurfaceProvider = &rSurfaceProvider; + mpRefDevice = &rRefDevice; + + // no own surface, this is handled by derived classes + } + + void DeviceHelper::init( SurfaceProvider& rSurfaceProvider, + OutputDevice& rRefDevice ) + { + implInit(rSurfaceProvider, rRefDevice); + + OutputDevice* pOutDev = getOutputDevice(); + mpSurface = pOutDev->CreateSurface(pOutDev->GetOutOffXPixel(), + pOutDev->GetOutOffYPixel(), + pOutDev->GetOutputWidthPixel(), + pOutDev->GetOutputHeightPixel()); + } + + void DeviceHelper::disposing() + { + // release all references + mpSurface.reset(); + mpRefDevice = nullptr; + mpSurfaceProvider = nullptr; + } + + void DeviceHelper::setSize( const ::basegfx::B2ISize& rSize ) + { + SAL_INFO( + "canvas.cairo", + "device size " << rSize.getX() << " x " << rSize.getY()); + + if( !mpRefDevice ) + return; // disposed + + OutputDevice* pOutDev = getOutputDevice(); + + // X11 only + bool bReuseSurface = mpSurface && + mpSurface->Resize(rSize.getX() + pOutDev->GetOutOffXPixel(), + rSize.getY() + pOutDev->GetOutOffYPixel()); + + if (!bReuseSurface) + { + mpSurface = pOutDev->CreateSurface( + pOutDev->GetOutOffXPixel(), + pOutDev->GetOutOffYPixel(), + rSize.getX(), rSize.getY() ); + } + } + + geometry::RealSize2D DeviceHelper::getPhysicalResolution() + { + // Map a one-by-one millimeter box to pixel + const MapMode aOldMapMode( mpRefDevice->GetMapMode() ); + mpRefDevice->SetMapMode( MapMode(MapUnit::MapMM) ); + const Size aPixelSize( mpRefDevice->LogicToPixel(Size(1,1)) ); + mpRefDevice->SetMapMode( aOldMapMode ); + + return vcl::unotools::size2DFromSize( aPixelSize ); + } + + geometry::RealSize2D DeviceHelper::getPhysicalSize() + { + if( !mpRefDevice ) + return ::canvas::tools::createInfiniteSize2D(); // we're disposed + + // Map the pixel dimensions of the output window to millimeter + const MapMode aOldMapMode( mpRefDevice->GetMapMode() ); + mpRefDevice->SetMapMode( MapMode(MapUnit::MapMM) ); + const Size aLogSize( mpRefDevice->PixelToLogic(mpRefDevice->GetOutputSizePixel()) ); + mpRefDevice->SetMapMode( aOldMapMode ); + + return vcl::unotools::size2DFromSize( aLogSize ); + } + + uno::Reference< rendering::XLinePolyPolygon2D > DeviceHelper::createCompatibleLinePolyPolygon( + const uno::Reference< rendering::XGraphicDevice >& , + const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points ) + { + // disposed? + if( !mpSurfaceProvider ) + return uno::Reference< rendering::XLinePolyPolygon2D >(); // we're disposed + + return uno::Reference< rendering::XLinePolyPolygon2D >( + new ::basegfx::unotools::UnoPolyPolygon( + ::basegfx::unotools::polyPolygonFromPoint2DSequenceSequence( points ) ) ); + } + + uno::Reference< rendering::XBezierPolyPolygon2D > DeviceHelper::createCompatibleBezierPolyPolygon( + const uno::Reference< rendering::XGraphicDevice >& , + const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& points ) + { + // disposed? + if( !mpSurfaceProvider ) + return uno::Reference< rendering::XBezierPolyPolygon2D >(); // we're disposed + + return uno::Reference< rendering::XBezierPolyPolygon2D >( + new ::basegfx::unotools::UnoPolyPolygon( + ::basegfx::unotools::polyPolygonFromBezier2DSequenceSequence( points ) ) ); + } + + uno::Reference< rendering::XBitmap > DeviceHelper::createCompatibleBitmap( + const uno::Reference< rendering::XGraphicDevice >& rDevice, + const geometry::IntegerSize2D& size ) + { + // disposed? + if( !mpSurfaceProvider ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + return uno::Reference< rendering::XBitmap >( + new CanvasBitmap( + ::basegfx::unotools::b2ISizeFromIntegerSize2D( size ), + SurfaceProviderRef(mpSurfaceProvider), + rDevice.get(), + false )); + } + + uno::Reference< rendering::XVolatileBitmap > DeviceHelper::createVolatileBitmap( + const uno::Reference< rendering::XGraphicDevice >& , + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + uno::Reference< rendering::XBitmap > DeviceHelper::createCompatibleAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& rDevice, + const geometry::IntegerSize2D& size ) + { + // disposed? + if( !mpSurfaceProvider ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + return uno::Reference< rendering::XBitmap >( + new CanvasBitmap( + ::basegfx::unotools::b2ISizeFromIntegerSize2D( size ), + SurfaceProviderRef(mpSurfaceProvider), + rDevice.get(), + true )); + } + + uno::Reference< rendering::XVolatileBitmap > DeviceHelper::createVolatileAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& , + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + uno::Any DeviceHelper::isAccelerated() const + { + return css::uno::Any(false); + } + + uno::Any DeviceHelper::getDeviceHandle() const + { + return uno::Any( reinterpret_cast< sal_Int64 >(mpRefDevice.get()) ); + } + + uno::Any DeviceHelper::getSurfaceHandle() const + { + return uno::Any(); + } + + uno::Reference<rendering::XColorSpace> const & DeviceHelper::getColorSpace() const + { + static uno::Reference<rendering::XColorSpace> SPACE = vcl::unotools::createStandardColorSpace(); + // always the same + return SPACE; + } + + void DeviceHelper::dumpScreenContent() const + { + static sal_Int32 nFilePostfixCount(0); + + if( !mpRefDevice ) + return; + + OUString aFilename = "dbg_frontbuffer" + OUString::number(nFilePostfixCount) + ".bmp"; + + SvFileStream aStream( aFilename, StreamMode::STD_READWRITE ); + + const ::Point aEmptyPoint; + bool bOldMap( mpRefDevice->IsMapModeEnabled() ); + mpRefDevice->EnableMapMode( false ); + const ::BitmapEx aTempBitmap(mpRefDevice->GetBitmapEx(aEmptyPoint, mpRefDevice->GetOutputSizePixel())); + WriteDIB(aTempBitmap, aStream, false); + mpRefDevice->EnableMapMode( bOldMap ); + + ++nFilePostfixCount; + } + + SurfaceSharedPtr DeviceHelper::createSurface( const ::basegfx::B2ISize& rSize, int aContent ) + { + if( mpSurface ) + return mpSurface->getSimilar( aContent, rSize.getX(), rSize.getY() ); + + return SurfaceSharedPtr(); + } + + SurfaceSharedPtr DeviceHelper::createSurface( BitmapSystemData const & rData, const Size& rSize ) + { + if (mpRefDevice) + return mpRefDevice->CreateBitmapSurface(rData, rSize); + + return SurfaceSharedPtr(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_devicehelper.hxx b/canvas/source/cairo/cairo_devicehelper.hxx new file mode 100644 index 000000000..eede77844 --- /dev/null +++ b/canvas/source/cairo/cairo_devicehelper.hxx @@ -0,0 +1,122 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/XGraphicDevice.hpp> + +#include <vcl/outdev.hxx> + +#include "cairo_surfaceprovider.hxx" + +/* Definition of DeviceHelper class */ + +namespace cairocanvas +{ + class DeviceHelper + { + public: + /// make noncopyable + DeviceHelper(const DeviceHelper&) = delete; + const DeviceHelper& operator=(const DeviceHelper&) = delete; + + DeviceHelper(); + + /** init helper + + @param rCanvas + Owning canvas. + + @param rRefDevice + Reference output device. Needed for resolution + calculations etc. + */ + void init( SurfaceProvider& rSurfaceProvider, + OutputDevice& rRefDevice ); + + /// Dispose all internal references + void disposing(); + + // XWindowGraphicDevice + css::geometry::RealSize2D getPhysicalResolution(); + css::geometry::RealSize2D getPhysicalSize(); + css::uno::Reference< css::rendering::XLinePolyPolygon2D > createCompatibleLinePolyPolygon( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::uno::Sequence< css::uno::Sequence< css::geometry::RealPoint2D > >& points ); + css::uno::Reference< css::rendering::XBezierPolyPolygon2D > createCompatibleBezierPolyPolygon( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::uno::Sequence< css::uno::Sequence< css::geometry::RealBezierSegment2D > >& points ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + + css::uno::Any isAccelerated() const; + css::uno::Any getDeviceHandle() const; + css::uno::Any getSurfaceHandle() const; + css::uno::Reference< + css::rendering::XColorSpace > const & getColorSpace() const; + + /** called when DumpScreenContent property is enabled on + XGraphicDevice, and writes out bitmaps of current screen. + */ + void dumpScreenContent() const; + + OutputDevice* getOutputDevice() const { return mpRefDevice; } + const ::cairo::SurfaceSharedPtr& getSurface() const { return mpSurface; } + ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, int aContent ); + ::cairo::SurfaceSharedPtr createSurface( BitmapSystemData const & rData, const Size& rSize ); + + protected: + /** init helper + + @param rCanvas + Owning canvas. + + @param rRefDevice + Reference output device. Needed for resolution + calculations etc. + */ + void implInit( SurfaceProvider& rSurfaceProvider, + OutputDevice& rRefDevice ); + void setSize( const ::basegfx::B2ISize& rSize ); + + private: + /** Surface provider + + Deliberately not a refcounted reference, because of + potential circular references for canvas. Provides us with + our output surface and associated functionality. + */ + SurfaceProvider* mpSurfaceProvider; + + VclPtr<OutputDevice> mpRefDevice; + ::cairo::SurfaceSharedPtr mpSurface; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_repainttarget.hxx b/canvas/source/cairo/cairo_repainttarget.hxx new file mode 100644 index 000000000..558ec7921 --- /dev/null +++ b/canvas/source/cairo/cairo_repainttarget.hxx @@ -0,0 +1,48 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/RenderState.hpp> +#include <com/sun/star/rendering/ViewState.hpp> + +#include <vcl/cairo.hxx> + +namespace cairocanvas +{ + /* Definition of RepaintTarget interface */ + + /** Target interface for XCachedPrimitive implementations + + This interface must be implemented on all canvas + implementations that hand out XCachedPrimitives + */ + class RepaintTarget + { + public: + virtual ~RepaintTarget() {} + + // call this when a bitmap is repainted + virtual bool repaint( const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) = 0; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_sprite.hxx b/canvas/source/cairo/cairo_sprite.hxx new file mode 100644 index 000000000..513283e30 --- /dev/null +++ b/canvas/source/cairo/cairo_sprite.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#pragma once + +#include <base/sprite.hxx> + +#include <vcl/cairo.hxx> + +namespace cairocanvas +{ + /** Specialization of ::canvas::Sprite interface, to also provide + redraw methods. + */ + class Sprite : public ::canvas::Sprite + { + public: + + /** Redraw sprite at the stored position. + + @param bBufferedUpdate + When true, the redraw does <em>not</em> happen directly on + the front buffer, but within a VDev. Used to speed up + drawing. + */ + virtual void redraw( const ::cairo::CairoSharedPtr& pCairo, + bool bBufferedUpdate ) const = 0; + + /** Redraw sprite at the given position. + + @param rPos + Output position of the sprite. Overrides the sprite's own + output position. + + @param bBufferedUpdate + When true, the redraw does <em>not</em> happen directly on + the front buffer, but within a VDev. Used to speed up + drawing. + */ + virtual void redraw( const ::cairo::CairoSharedPtr& pCairo, + const ::basegfx::B2DPoint& rOrigOutputPos, + bool bBufferedUpdate ) const = 0; + + protected: + ~Sprite() {} + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritecanvas.cxx b/canvas/source/cairo/cairo_spritecanvas.cxx new file mode 100644 index 000000000..562157f52 --- /dev/null +++ b/canvas/source/cairo/cairo_spritecanvas.cxx @@ -0,0 +1,233 @@ +/* -*- 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 <basegfx/range/b2irange.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <osl/mutex.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/diagnose_ex.h> +#include <cppuhelper/supportsservice.hxx> + +#include "cairo_spritecanvas.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + SpriteCanvas::SpriteCanvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& /*rxContext*/ ) : + maArguments(aArguments) + { + } + + void SpriteCanvas::initialize() + { + SAL_INFO("canvas.cairo", "CairoSpriteCanvas created " << this); + + // #i64742# Only call initialize when not in probe mode + if( !maArguments.hasElements() ) + return; + + /* maArguments: + 0: ptr to creating instance (Window or VirtualDevice) + 1: current bounds of creating instance + 2: bool, denoting always on top state for Window (always false for VirtualDevice) + 3: XWindow for creating Window (or empty for VirtualDevice) + 4: SystemGraphicsData as a streamed Any + */ + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 4 && + maArguments[0].getValueTypeClass() == uno::TypeClass_HYPER && + maArguments[3].getValueTypeClass() == uno::TypeClass_INTERFACE, + "CairoSpriteCanvas::initialize: wrong number of arguments, or wrong types" ); + + awt::Rectangle aRect; + maArguments[1] >>= aRect; + + bool bIsFullscreen( false ); + maArguments[2] >>= bIsFullscreen; + + uno::Reference< awt::XWindow > xParentWindow; + maArguments[3] >>= xParentWindow; + + VclPtr<vcl::Window> pParentWindow = VCLUnoHelper::GetWindow(xParentWindow); + if( !pParentWindow ) + throw lang::NoSupportException( + "Parent window not VCL window, or canvas out-of-process!", nullptr); + + bool bHasCairo = pParentWindow->GetOutDev()->SupportsCairo(); + ENSURE_ARG_OR_THROW(bHasCairo, + "CairoSpriteCanvas::SpriteCanvas: No Cairo capability"); + + Size aPixelSize( pParentWindow->GetOutputSizePixel() ); + const ::basegfx::B2ISize aSize( aPixelSize.Width(), + aPixelSize.Height() ); + + // setup helper + maDeviceHelper.init( *pParentWindow, + *this, + aSize, + bIsFullscreen ); + + setWindow(uno::Reference<awt::XWindow2>(xParentWindow, uno::UNO_QUERY_THROW)); + + maCanvasHelper.init( maRedrawManager, + *this, + aSize ); + + maArguments.realloc(0); + } + + void SpriteCanvas::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // forward to parent + SpriteCanvasBaseT::disposeThis(); + } + + sal_Bool SAL_CALL SpriteCanvas::showBuffer( sal_Bool bUpdateAll ) + { + return updateScreen( bUpdateAll ); + } + + sal_Bool SAL_CALL SpriteCanvas::switchBuffer( sal_Bool bUpdateAll ) + { + return updateScreen( bUpdateAll ); + } + + sal_Bool SAL_CALL SpriteCanvas::updateScreen( sal_Bool bUpdateAll ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // avoid repaints on hidden window (hidden: not mapped to + // screen). Return failure, since the screen really has _not_ + // been updated (caller should try again later) + return mbIsVisible && maCanvasHelper.updateScreen( + ::basegfx::unotools::b2IRectangleFromAwtRectangle(maBounds), + bUpdateAll, + mbSurfaceDirty); + } + + OUString SAL_CALL SpriteCanvas::getServiceName( ) + { + return "com.sun.star.rendering.SpriteCanvas.Cairo"; + } + + // XServiceInfo + sal_Bool SpriteCanvas::supportsService(const OUString& sServiceName) + { + return cppu::supportsService(this, sServiceName); + + } + OUString SpriteCanvas::getImplementationName() + { + return "com.sun.star.comp.rendering.Canvas.Cairo"; + } + css::uno::Sequence< OUString > SpriteCanvas::getSupportedServiceNames() + { + return { getServiceName() }; + } + + SurfaceSharedPtr SpriteCanvas::getSurface() + { + return maDeviceHelper.getBufferSurface(); + } + + SurfaceSharedPtr SpriteCanvas::createSurface( const ::basegfx::B2ISize& rSize, int aContent ) + { + return maDeviceHelper.createSurface( rSize, aContent ); + } + + SurfaceSharedPtr SpriteCanvas::createSurface( ::Bitmap& rBitmap ) + { + BitmapSystemData aData; + if( rBitmap.GetSystemData( aData ) ) { + const Size& rSize = rBitmap.GetSizePixel(); + + return maDeviceHelper.createSurface( aData, rSize ); + } + + return SurfaceSharedPtr(); + } + + SurfaceSharedPtr SpriteCanvas::changeSurface() + { + // non-modifiable surface here + return SurfaceSharedPtr(); + } + + OutputDevice* SpriteCanvas::getOutputDevice() + { + return maDeviceHelper.getOutputDevice(); + } + + SurfaceSharedPtr const & SpriteCanvas::getBufferSurface() const + { + return maDeviceHelper.getBufferSurface(); + } + + SurfaceSharedPtr const & SpriteCanvas::getWindowSurface() const + { + return maDeviceHelper.getWindowSurface(); + } + + const ::basegfx::B2ISize& SpriteCanvas::getSizePixel() const + { + return maDeviceHelper.getSizePixel(); + } + + void SpriteCanvas::setSizePixel( const ::basegfx::B2ISize& rSize ) + { + maCanvasHelper.setSize( rSize ); + // re-set background surface, in case it needed recreation + maCanvasHelper.setSurface( maDeviceHelper.getBufferSurface(), + false ); + } + + void SpriteCanvas::flush() + { + maDeviceHelper.flush(); + } + + bool SpriteCanvas::repaint( const SurfaceSharedPtr& pSurface, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + return maCanvasHelper.repaint( pSurface, viewState, renderState ); + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_rendering_SpriteCanvas_Cairo_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + rtl::Reference<cairocanvas::SpriteCanvas> p = new cairocanvas::SpriteCanvas(args, context); + p->acquire(); + p->initialize(); + return static_cast<cppu::OWeakObject*>(p.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritecanvas.hxx b/canvas/source/cairo/cairo_spritecanvas.hxx new file mode 100644 index 000000000..7790e6890 --- /dev/null +++ b/canvas/source/cairo/cairo_spritecanvas.hxx @@ -0,0 +1,159 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBufferController.hpp> + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <base/spritecanvasbase.hxx> +#include <base/spritesurface.hxx> +#include <base/disambiguationhelper.hxx> +#include <base/bufferedgraphicdevicebase.hxx> + +#include "cairo_spritedevicehelper.hxx" +#include "cairo_repainttarget.hxx" +#include "cairo_surfaceprovider.hxx" +#include "cairo_spritecanvashelper.hxx" + +namespace cairocanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XSpriteCanvas, + css::rendering::XIntegerBitmap, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::rendering::XBufferController, + css::awt::XWindowListener, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName, + css::lang::XServiceInfo > WindowGraphicDeviceBase_Base; + typedef ::canvas::BufferedGraphicDeviceBase< ::canvas::DisambiguationHelper< WindowGraphicDeviceBase_Base >, + SpriteDeviceHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > SpriteCanvasBase_Base; + /** Mixin SpriteSurface + + Have to mixin the SpriteSurface before deriving from + ::canvas::SpriteCanvasBase, as this template should already + implement some of those interface methods. + + The reason why this appears kinda convoluted is the fact that + we cannot specify non-IDL types as WeakComponentImplHelper + template args, and furthermore, don't want to derive + ::canvas::SpriteCanvasBase directly from + ::canvas::SpriteSurface (because derivees of + ::canvas::SpriteCanvasBase have to explicitly forward the + XInterface methods (e.g. via DECLARE_UNO3_AGG_DEFAULTS) + anyway). Basically, ::canvas::CanvasCustomSpriteBase should + remain a base class that provides implementation, not to + enforce any specific interface on its derivees. + */ + class SpriteCanvasBaseSpriteSurface_Base : public SpriteCanvasBase_Base, + public ::canvas::SpriteSurface, + public SurfaceProvider + { + }; + + typedef ::canvas::SpriteCanvasBase< SpriteCanvasBaseSpriteSurface_Base, + SpriteCanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > SpriteCanvasBaseT; + + /** Product of this component's factory. + + The SpriteCanvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class SpriteCanvas : public SpriteCanvasBaseT, + public RepaintTarget + { + public: + SpriteCanvas( const css::uno::Sequence< css::uno::Any >& aArguments, + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( SpriteCanvas, WindowGraphicDeviceBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XBufferController (partial) + virtual sal_Bool SAL_CALL showBuffer( sal_Bool bUpdateAll ) override; + virtual sal_Bool SAL_CALL switchBuffer( sal_Bool bUpdateAll ) override; + + // XSpriteCanvas (partial) + virtual sal_Bool SAL_CALL updateScreen( sal_Bool bUpdateAll ) override; + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + // XServiceInfo + virtual sal_Bool SAL_CALL supportsService(const OUString& sServiceName) override; + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // SurfaceProvider + virtual ::cairo::SurfaceSharedPtr getSurface() override; + virtual ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, int aContent ) override; + virtual ::cairo::SurfaceSharedPtr createSurface( ::Bitmap& rBitmap ) override; + virtual ::cairo::SurfaceSharedPtr changeSurface() override; + virtual OutputDevice* getOutputDevice() override; + + // RepaintTarget + virtual bool repaint( const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) override; + + ::cairo::SurfaceSharedPtr const & getWindowSurface() const; + ::cairo::SurfaceSharedPtr const & getBufferSurface() const; + + const ::basegfx::B2ISize& getSizePixel() const; + void setSizePixel( const ::basegfx::B2ISize& rSize ); + void flush(); + + private: + css::uno::Sequence< css::uno::Any > maArguments; + }; + + typedef ::rtl::Reference< SpriteCanvas > SpriteCanvasRef; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritecanvashelper.cxx b/canvas/source/cairo/cairo_spritecanvashelper.cxx new file mode 100644 index 000000000..992e0a581 --- /dev/null +++ b/canvas/source/cairo/cairo_spritecanvashelper.cxx @@ -0,0 +1,518 @@ +/* -*- 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 <boost/cast.hpp> + +#include <basegfx/range/b2irange.hxx> +#include <tools/diagnose_ex.h> + +#include <canvas/canvastools.hxx> + +#include <cairo.h> + +#include "cairo_canvascustomsprite.hxx" +#include "cairo_spritecanvashelper.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + namespace + { + /** Sprite redraw at original position + + Used to repaint the whole canvas (background and all + sprites) + */ + void spriteRedraw( const CairoSharedPtr& pCairo, + const ::canvas::Sprite::Reference& rSprite ) + { + // downcast to derived cairocanvas::Sprite interface, which + // provides the actual redraw methods. + ::boost::polymorphic_downcast< Sprite* >(rSprite.get())->redraw( pCairo, true); + } + + void repaintBackground( const CairoSharedPtr& pCairo, + const SurfaceSharedPtr& pBackgroundSurface, + const ::basegfx::B2DRange& rArea ) + { + cairo_save( pCairo.get() ); + cairo_rectangle( pCairo.get(), ceil( rArea.getMinX() ), ceil( rArea.getMinY() ), + floor( rArea.getWidth() ), floor( rArea.getHeight() ) ); + cairo_clip( pCairo.get() ); + cairo_set_source_surface( pCairo.get(), pBackgroundSurface->getCairoSurface().get(), 0, 0 ); + cairo_set_operator( pCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pCairo.get() ); + cairo_restore( pCairo.get() ); + } + + void opaqueUpdateSpriteArea( const ::canvas::Sprite::Reference& rSprite, + const CairoSharedPtr& pCairo, + const ::basegfx::B2IRange& rArea ) + { + // clip output to actual update region (otherwise a) + // wouldn't save much render time, and b) will clutter + // scrolled sprite content outside this area) + cairo_save( pCairo.get() ); + cairo_rectangle( pCairo.get(), rArea.getMinX(), rArea.getMinY(), + sal::static_int_cast<sal_Int32>(rArea.getWidth()), + sal::static_int_cast<sal_Int32>(rArea.getHeight()) ); + cairo_clip( pCairo.get() ); + + // repaint affected sprite directly to output device (at + // the actual screen output position) + // rendering directly to device buffer + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw( pCairo, false ); + + cairo_restore( pCairo.get() ); + } + } + + SpriteCanvasHelper::SpriteCanvasHelper() : + mpRedrawManager( nullptr ), + mpOwningSpriteCanvas( nullptr ), + mbCompositingSurfaceDirty(true) + { + } + + void SpriteCanvasHelper::init( ::canvas::SpriteRedrawManager& rManager, + SpriteCanvas& rDevice, + const ::basegfx::B2ISize& rSize ) + { + mpRedrawManager = &rManager; + mpOwningSpriteCanvas = &rDevice; + + CanvasHelper::init( rSize, rDevice, &rDevice ); + } + + void SpriteCanvasHelper::disposing() + { + mpCompositingSurface.reset(); + mpOwningSpriteCanvas = nullptr; + mpRedrawManager = nullptr; + + // forward to base + CanvasHelper::disposing(); + } + + uno::Reference< rendering::XAnimatedSprite > SpriteCanvasHelper::createSpriteFromAnimation( + const uno::Reference< rendering::XAnimation >& ) + { + return uno::Reference< rendering::XAnimatedSprite >(); + } + + uno::Reference< rendering::XAnimatedSprite > SpriteCanvasHelper::createSpriteFromBitmaps( + const uno::Sequence< uno::Reference< rendering::XBitmap > >& /*animationBitmaps*/, + sal_Int8 /*interpolationMode*/ ) + { + return uno::Reference< rendering::XAnimatedSprite >(); + } + + uno::Reference< rendering::XCustomSprite > SpriteCanvasHelper::createCustomSprite( const geometry::RealSize2D& spriteSize ) + { + if( !mpRedrawManager ) + return uno::Reference< rendering::XCustomSprite >(); // we're disposed + + return uno::Reference< rendering::XCustomSprite >( + new CanvasCustomSprite( spriteSize, + mpOwningSpriteCanvas ) ); + } + + uno::Reference< rendering::XSprite > SpriteCanvasHelper::createClonedSprite( + const uno::Reference< rendering::XSprite >& ) + { + return uno::Reference< rendering::XSprite >(); + } + + bool SpriteCanvasHelper::updateScreen( const ::basegfx::B2IRange& /*rCurrArea*/, + bool bUpdateAll, + bool& io_bSurfaceDirty ) + { + if( !mpRedrawManager || + !mpOwningSpriteCanvas || + !mpOwningSpriteCanvas->getWindowSurface() || + !mpOwningSpriteCanvas->getBufferSurface() ) + { + return false; // disposed, or otherwise dysfunctional + } + + SAL_INFO("canvas.cairo", "SpriteCanvasHelper::updateScreen called"); + + const ::basegfx::B2ISize& rSize = mpOwningSpriteCanvas->getSizePixel(); + + // force compositing surface to be available before using it + // inside forEachSpriteArea + SurfaceSharedPtr pCompositingSurface = getCompositingSurface(rSize); + SurfaceSharedPtr pWindowSurface = mpOwningSpriteCanvas->getWindowSurface(); + CairoSharedPtr pCompositingCairo = pCompositingSurface->getCairo(); + CairoSharedPtr pWindowCairo = pWindowSurface->getCairo(); + + // TODO(P1): Might be worthwhile to track areas of background + // changes, too. + if( !bUpdateAll && !io_bSurfaceDirty && !mbCompositingSurfaceDirty ) + { + // background has not changed, so we're free to optimize + // repaint to areas where a sprite has changed + + // process each independent area of overlapping sprites + // separately. + mpRedrawManager->forEachSpriteArea( *this ); + } + else + { + SAL_INFO("canvas.cairo", "SpriteCanvasHelper::updateScreen update ALL"); + + // background has changed, so we currently have no choice + // but repaint everything (or caller requested that) + + cairo_rectangle( pCompositingCairo.get(), 0, 0, rSize.getX(), rSize.getY() ); + cairo_clip( pCompositingCairo.get() ); + cairo_save( pCompositingCairo.get() ); + cairo_set_source_surface( pCompositingCairo.get(), + mpOwningSpriteCanvas->getBufferSurface()->getCairoSurface().get(), + 0, 0 ); + cairo_set_operator( pCompositingCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pCompositingCairo.get() ); + cairo_restore( pCompositingCairo.get() ); + + // repaint all active sprites on top of background into + // VDev. + mpRedrawManager->forEachSprite( + [&pCompositingCairo]( const Sprite::Reference rSprite ) + { spriteRedraw( pCompositingCairo, rSprite ); } + ); + + // flush to screen + cairo_rectangle( pWindowCairo.get(), 0, 0, rSize.getX(), rSize.getY() ); + cairo_clip( pWindowCairo.get() ); + cairo_set_source_surface( pWindowCairo.get(), + pCompositingSurface->getCairoSurface().get(), + 0, 0 ); + cairo_set_operator( pWindowCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pWindowCairo.get() ); + } + + // change record vector must be cleared, for the next turn of + // rendering and sprite changing + mpRedrawManager->clearChangeRecords(); + + mbCompositingSurfaceDirty = false; + io_bSurfaceDirty = false; + + // commit to screen + mpOwningSpriteCanvas->flush(); + + return true; + } + + void SpriteCanvasHelper::backgroundPaint( const ::basegfx::B2DRange& rUpdateRect ) + { + if( mpOwningSpriteCanvas && mpCompositingSurface ) + repaintBackground( mpCompositingSurface->getCairo(), + mpOwningSpriteCanvas->getBufferSurface(), + rUpdateRect ); + } + + void SpriteCanvasHelper::scrollUpdate( const ::basegfx::B2DRange& rMoveStart, + const ::basegfx::B2DRange& rMoveEnd, + const ::canvas::SpriteRedrawManager::UpdateArea& rUpdateArea ) + { + ENSURE_OR_THROW( mpOwningSpriteCanvas && + mpOwningSpriteCanvas->getBufferSurface(), + "SpriteCanvasHelper::scrollUpdate(): NULL device pointer " ); + + SAL_INFO("canvas.cairo", "SpriteCanvasHelper::scrollUpdate called"); + + const ::basegfx::B2ISize& rSize = mpOwningSpriteCanvas->getSizePixel(); + const ::basegfx::B2IRange aOutputBounds( 0,0, + rSize.getX(), + rSize.getY() ); + + SurfaceSharedPtr pCompositingSurface = getCompositingSurface(rSize); + SurfaceSharedPtr pWindowSurface = mpOwningSpriteCanvas->getWindowSurface(); + CairoSharedPtr pCompositingCairo = pCompositingSurface->getCairo(); + CairoSharedPtr pWindowCairo = pWindowSurface->getCairo(); + + // round rectangles to integer pixel. Note: have to be + // extremely careful here, to avoid off-by-one errors for + // the destination area: otherwise, the next scroll update + // would copy pixel that are not supposed to be part of + // the sprite. + ::basegfx::B2IRange aSourceRect( + ::canvas::tools::spritePixelAreaFromB2DRange( rMoveStart ) ); + const ::basegfx::B2IRange& rDestRect( + ::canvas::tools::spritePixelAreaFromB2DRange( rMoveEnd ) ); + ::basegfx::B2IPoint aDestPos( rDestRect.getMinimum() ); + + std::vector< ::basegfx::B2IRange > aUnscrollableAreas; + + // TODO(E3): This is plain buggy (but copies the behaviour of + // the old Impress slideshow) - the scrolled area might + // actually lie _below_ another window! + + // clip to output bounds (cannot properly scroll stuff + // _outside_ our screen area) + if( !::canvas::tools::clipScrollArea( aSourceRect, + aDestPos, + aUnscrollableAreas, + aOutputBounds ) ) + { + // fully clipped scroll area: cannot simply scroll + // then. Perform normal opaque update (can use that, since + // one of the preconditions for scrollable update is + // opaque sprite content) + + // repaint all affected sprites directly to output device + for( const auto& rComponent : rUpdateArea.maComponentList ) + { + const ::canvas::Sprite::Reference& rSprite( rComponent.second.getSprite() ); + if( rSprite.is() ) + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw( + pCompositingCairo, true ); + } + } + else + { + const ::basegfx::B2IVector aSourceUpperLeftPos( aSourceRect.getMinimum() ); + + // clip dest area (which must be inside rDestBounds) + ::basegfx::B2IRange aDestRect( rDestRect ); + aDestRect.intersect( aOutputBounds ); + + ::basegfx::B2ISize aScrollSize( aDestRect.getWidth(), aDestRect.getHeight() ); + SurfaceSharedPtr pScrollSurface( getTemporarySurface() ); + CairoSharedPtr pScrollCairo( pScrollSurface->getCairo() ); + + cairo_save( pScrollCairo.get() ); + // scroll the current content of the compositing surface (and, + // thus, of the window) in temp. surface + cairo_set_source_surface( pScrollCairo.get(), + pCompositingSurface->getCairoSurface().get(), + aDestPos.getX() - aSourceUpperLeftPos.getX(), + aDestPos.getY() - aSourceUpperLeftPos.getY() ); + cairo_rectangle( pScrollCairo.get(), + aDestPos.getX(), aDestPos.getY(), + aScrollSize.getX(), aScrollSize.getY() ); + cairo_clip( pScrollCairo.get() ); + cairo_set_operator( pScrollCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pScrollCairo.get() ); + cairo_restore( pScrollCairo.get() ); + + cairo_save( pCompositingCairo.get() ); + // copy the scrolled area back onto the compositing surface + cairo_set_source_surface( pCompositingCairo.get(), + pScrollSurface->getCairoSurface().get(), + 0, 0 ); + cairo_rectangle( pCompositingCairo.get(), + aDestPos.getX(), aDestPos.getY(), + aScrollSize.getX(), aScrollSize.getY() ); + cairo_clip( pCompositingCairo.get() ); + cairo_set_operator( pCompositingCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pCompositingCairo.get() ); + cairo_restore( pCompositingCairo.get() ); + + const ::canvas::SpriteRedrawManager::SpriteConnectedRanges::ComponentListType::const_iterator + aFirst( rUpdateArea.maComponentList.begin() ); + ::canvas::SpriteRedrawManager::SpriteConnectedRanges::ComponentListType::const_iterator + aSecond( aFirst ); + ++aSecond; + + ENSURE_OR_THROW( aFirst->second.getSprite().is(), + "VCLCanvas::scrollUpdate(): no sprite" ); + + // repaint uncovered areas from sprite. Need to actually + // clip here, since we're only repainting _parts_ of the + // sprite + for( const auto& rArea : aUnscrollableAreas ) + opaqueUpdateSpriteArea( aFirst->second.getSprite(), + pCompositingCairo, rArea ); + } + + // repaint uncovered areas from backbuffer - take the + // _rounded_ rectangles from above, to have the update + // consistent with the scroll above. + std::vector< ::basegfx::B2DRange > aUncoveredAreas; + ::basegfx::computeSetDifference( aUncoveredAreas, + rUpdateArea.maTotalBounds, + ::basegfx::B2DRange( rDestRect ) ); + for( const auto& rArea : aUncoveredAreas ) + repaintBackground( pCompositingCairo, + mpOwningSpriteCanvas->getBufferSurface(), rArea ); + + cairo_rectangle( pWindowCairo.get(), 0, 0, rSize.getX(), rSize.getY() ); + cairo_clip( pWindowCairo.get() ); + cairo_set_source_surface( pWindowCairo.get(), + pCompositingSurface->getCairoSurface().get(), + 0, 0 ); + cairo_set_operator( pWindowCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pWindowCairo.get() ); + } + + void SpriteCanvasHelper::opaqueUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ) + { + ENSURE_OR_THROW( mpOwningSpriteCanvas && + mpOwningSpriteCanvas->getBufferSurface(), + "SpriteCanvasHelper::opaqueUpdate(): NULL device pointer " ); + + SAL_INFO("canvas.cairo", "SpriteCanvasHelper::opaqueUpdate called"); + + const ::basegfx::B2ISize& rDeviceSize = mpOwningSpriteCanvas->getSizePixel(); + + SurfaceSharedPtr pCompositingSurface = getCompositingSurface(rDeviceSize); + SurfaceSharedPtr pWindowSurface = mpOwningSpriteCanvas->getWindowSurface(); + CairoSharedPtr pCompositingCairo = pCompositingSurface->getCairo(); + CairoSharedPtr pWindowCairo = pWindowSurface->getCairo(); + + cairo_rectangle( pCompositingCairo.get(), 0, 0, rDeviceSize.getX(), rDeviceSize.getY() ); + cairo_clip( pCompositingCairo.get() ); + + ::basegfx::B2DVector aPos( ceil( rTotalArea.getMinX() ), ceil( rTotalArea.getMinY() ) ); + ::basegfx::B2DVector aSize( floor( rTotalArea.getMaxX() - aPos.getX() ), floor( rTotalArea.getMaxY() - aPos.getY() ) ); + + cairo_rectangle( pCompositingCairo.get(), aPos.getX(), aPos.getY(), aSize.getX(), aSize.getY() ); + cairo_clip( pCompositingCairo.get() ); + + // repaint all affected sprites directly to output device + for( const auto& rSprite : rSortedUpdateSprites ) + { + if( rSprite.is() ) + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw( + pCompositingCairo, false ); + } + + // flush to screen + cairo_rectangle( pWindowCairo.get(), 0, 0, rDeviceSize.getX(), rDeviceSize.getY() ); + cairo_clip( pWindowCairo.get() ); + cairo_rectangle( pWindowCairo.get(), aPos.getX(), aPos.getY(), aSize.getX(), aSize.getY() ); + cairo_clip( pWindowCairo.get() ); + cairo_set_source_surface( pWindowCairo.get(), + pCompositingSurface->getCairoSurface().get(), + 0, 0 ); + cairo_set_operator( pWindowCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pWindowCairo.get() ); + } + + void SpriteCanvasHelper::genericUpdate( const ::basegfx::B2DRange& rRequestedArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ) + { + // TODO + SAL_INFO("canvas.cairo", "SpriteCanvasHelper::genericUpdate called"); + + ENSURE_OR_THROW( mpOwningSpriteCanvas && + mpOwningSpriteCanvas->getBufferSurface(), + "SpriteCanvasHelper::genericUpdate(): NULL device pointer " ); + + // limit size of update VDev to target outdev's size + const ::basegfx::B2ISize& rSize = mpOwningSpriteCanvas->getSizePixel(); + + SurfaceSharedPtr pCompositingSurface = getCompositingSurface(rSize); + SurfaceSharedPtr pWindowSurface = mpOwningSpriteCanvas->getWindowSurface(); + CairoSharedPtr pCompositingCairo = pCompositingSurface->getCairo(); + CairoSharedPtr pWindowCairo = pWindowSurface->getCairo(); + + // round output position towards zero. Don't want to truncate + // a fraction of a sprite pixel... Clip position at origin, + // otherwise, truncation of size below might leave visible + // areas uncovered by VDev. + const Point aOutputPosition( + std::max( sal_Int32( 0 ), + static_cast< sal_Int32 >(rRequestedArea.getMinX()) ), + std::max( sal_Int32( 0 ), + static_cast< sal_Int32 >(rRequestedArea.getMinY()) ) ); + // round output size towards +infty. Don't want to truncate a + // fraction of a sprite pixel... Limit size of VDev to output + // device's area. + const Size aOutputSize( + std::min( rSize.getX(), + ::canvas::tools::roundUp( rRequestedArea.getMaxX() - aOutputPosition.X()) ), + std::min( rSize.getY(), + ::canvas::tools::roundUp( rRequestedArea.getMaxY() - aOutputPosition.Y()) ) ); + + cairo_rectangle( pCompositingCairo.get(), aOutputPosition.X(), aOutputPosition.Y(), aOutputSize.Width(), aOutputSize.Height() ); + cairo_clip( pCompositingCairo.get() ); + + // paint background + cairo_save( pCompositingCairo.get() ); + cairo_set_source_surface( pCompositingCairo.get(), + mpOwningSpriteCanvas->getBufferSurface()->getCairoSurface().get(), + 0, 0 ); + cairo_set_operator( pCompositingCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pCompositingCairo.get() ); + cairo_restore( pCompositingCairo.get() ); + + // repaint all affected sprites on top of background into + // VDev. + for( const auto& rSprite : rSortedUpdateSprites ) + { + if( rSprite.is() ) + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw( + pCompositingCairo, true ); + } + + // flush to screen + cairo_rectangle( pWindowCairo.get(), aOutputPosition.X(), aOutputPosition.Y(), aOutputSize.Width(), aOutputSize.Height() ); + cairo_clip( pWindowCairo.get() ); + cairo_set_source_surface( pWindowCairo.get(), + pCompositingSurface->getCairoSurface().get(), + 0, 0 ); + cairo_set_operator( pWindowCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pWindowCairo.get() ); + } + + ::cairo::SurfaceSharedPtr const & SpriteCanvasHelper::getCompositingSurface( const ::basegfx::B2ISize& rNeededSize ) + { + if( rNeededSize.getX() > maCompositingSurfaceSize.getX() || + rNeededSize.getY() > maCompositingSurfaceSize.getY() ) + { + // need to give buffer more size + mpCompositingSurface.reset(); + } + + if( !mpCompositingSurface ) + { + mpCompositingSurface = createSurface( rNeededSize ); + maCompositingSurfaceSize = rNeededSize; + mbCompositingSurfaceDirty = true; + mpTemporarySurface.reset(); + } + + return mpCompositingSurface; + } + + ::cairo::SurfaceSharedPtr const & SpriteCanvasHelper::getTemporarySurface() + { + if ( !mpTemporarySurface ) + mpTemporarySurface = createSurface( maCompositingSurfaceSize ); + return mpTemporarySurface; + } + + ::cairo::SurfaceSharedPtr SpriteCanvasHelper::createSurface( const ::basegfx::B2ISize& rNeededSize ) const + { + return mpOwningSpriteCanvas->getWindowSurface()->getSimilar( + CAIRO_CONTENT_COLOR, + rNeededSize.getX(), rNeededSize.getY() ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritecanvashelper.hxx b/canvas/source/cairo/cairo_spritecanvashelper.hxx new file mode 100644 index 000000000..2b11f1bcf --- /dev/null +++ b/canvas/source/cairo/cairo_spritecanvashelper.hxx @@ -0,0 +1,140 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/XAnimatedSprite.hpp> +#include <com/sun/star/rendering/XAnimation.hpp> +#include <com/sun/star/rendering/XCustomSprite.hpp> + +#include <spriteredrawmanager.hxx> + +#include <vcl/cairo.hxx> +#include "cairo_canvashelper.hxx" + +namespace basegfx +{ + class B2IRange; +} + +namespace cairocanvas +{ + class SpriteCanvas; + + class SpriteCanvasHelper : public CanvasHelper + { + public: + SpriteCanvasHelper(); + + void init( ::canvas::SpriteRedrawManager& rManager, + SpriteCanvas& rOwningSpriteCanvas, + const ::basegfx::B2ISize& rSize ); + + /// Dispose all internal references + void disposing(); + + // XSpriteCanvas + css::uno::Reference< css::rendering::XAnimatedSprite > createSpriteFromAnimation( + const css::uno::Reference< css::rendering::XAnimation >& animation ); + + css::uno::Reference< css::rendering::XAnimatedSprite > createSpriteFromBitmaps( + const css::uno::Sequence< + css::uno::Reference< + css::rendering::XBitmap > >& animationBitmaps, + sal_Int8 interpolationMode ); + + css::uno::Reference< css::rendering::XCustomSprite > createCustomSprite( + const css::geometry::RealSize2D& spriteSize ); + + css::uno::Reference< css::rendering::XSprite > createClonedSprite( + const css::uno::Reference< css::rendering::XSprite >& original ); + + /** Actually perform the screen update + + @param rCurrArea + Current window area in absolute screen coordinates + + @param bUpdateAll + sal_True, if everything must be updated, not only changed + sprites + + @param io_bSurfaceDirty + In/out parameter, whether backbuffer surface is dirty (if + yes, we're performing a full update, anyway) + */ + bool updateScreen( const ::basegfx::B2IRange& rCurrArea, + bool bUpdateAll, + bool& io_bSurfaceDirty ); + + + // SpriteRedrawManager functor calls + + + /** Gets called for simple background repaints + */ + void backgroundPaint( const ::basegfx::B2DRange& rUpdateRect ); + + /** Gets called when area can be handled by scrolling. + + Called method must copy screen content from rMoveStart to + rMoveEnd, and restore the background in the uncovered + areas. + + @param rMoveStart + Source rect of the scroll + + @param rMoveEnd + Dest rect of the scroll + + @param rUpdateArea + All info necessary, should rMoveStart be partially or + fully outside the outdev + */ + void scrollUpdate( const ::basegfx::B2DRange& rMoveStart, + const ::basegfx::B2DRange& rMoveEnd, + const ::canvas::SpriteRedrawManager::UpdateArea& rUpdateArea ); + + void opaqueUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ); + + void genericUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ); + + private: + ::cairo::SurfaceSharedPtr const & getCompositingSurface( const ::basegfx::B2ISize& rNeededSize ); + ::cairo::SurfaceSharedPtr const & getTemporarySurface(); + ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rNeededSize ) const; + + /// Set from the SpriteCanvas: instance coordinating sprite redraw + ::canvas::SpriteRedrawManager* mpRedrawManager; + + /// Set from the init method. used to generate sprites + SpriteCanvas* mpOwningSpriteCanvas; + + /// a surface used to composite the frontbuffer image + ::cairo::SurfaceSharedPtr mpCompositingSurface; + ::basegfx::B2ISize maCompositingSurfaceSize; + bool mbCompositingSurfaceDirty; + /// a temporary surface that is guaranteed to be the same size + //as the compositing surface + ::cairo::SurfaceSharedPtr mpTemporarySurface; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritedevicehelper.cxx b/canvas/source/cairo/cairo_spritedevicehelper.cxx new file mode 100644 index 000000000..6539cf329 --- /dev/null +++ b/canvas/source/cairo/cairo_spritedevicehelper.cxx @@ -0,0 +1,153 @@ +/* -*- 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 <vcl/cairo.hxx> + +#include <cairo.h> + +#include "cairo_devicehelper.hxx" +#include "cairo_spritecanvas.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + + SpriteDeviceHelper::SpriteDeviceHelper() : + mpSpriteCanvas( nullptr ), + mbFullScreen( false ) + {} + + void SpriteDeviceHelper::init( vcl::Window& rOutputWindow, + SpriteCanvas& rSpriteCanvas, + const ::basegfx::B2ISize& rSize, + bool bFullscreen ) + { + DeviceHelper::init(rSpriteCanvas, + *rOutputWindow.GetOutDev()); + + mpSpriteCanvas = &rSpriteCanvas; + mbFullScreen = bFullscreen; + + setSize( rSize ); + } + + void SpriteDeviceHelper::disposing() + { + // release all references + mpBufferSurface.reset(); + mpSpriteCanvas = nullptr; + } + + bool SpriteDeviceHelper::showBuffer( bool, bool ) + { + SAL_WARN("canvas.cairo", "showBuffer Not supposed to be called, handled by SpriteCanvas"); + return false; + } + + bool SpriteDeviceHelper::switchBuffer( bool, bool ) + { + SAL_WARN("canvas.cairo", "showBuffer Not supposed to be called, handled by SpriteCanvas"); + return false; + } + + uno::Any SpriteDeviceHelper::isAccelerated() const + { + return css::uno::Any(true); + } + + uno::Any SpriteDeviceHelper::getDeviceHandle() const + { + return DeviceHelper::getDeviceHandle(); + } + + uno::Any SpriteDeviceHelper::getSurfaceHandle() const + { + return DeviceHelper::getSurfaceHandle(); + } + + void SpriteDeviceHelper::setSize( const ::basegfx::B2ISize& rSize ) + { + SAL_INFO( + "canvas.cairo", + "device size " << rSize.getX() << " x " << rSize.getY()); + + if( !mpSpriteCanvas ) + return; // disposed + + DeviceHelper::setSize(rSize); + + if( mpBufferSurface && maSize != rSize ) + mpBufferSurface.reset(); + if( !mpBufferSurface ) + mpBufferSurface = getWindowSurface()->getSimilar( + CAIRO_CONTENT_COLOR, + rSize.getX(), rSize.getY() ); + + if( maSize != rSize ) + maSize = rSize; + + mpSpriteCanvas->setSizePixel( maSize ); + } + + + void SpriteDeviceHelper::notifySizeUpdate( const awt::Rectangle& rBounds ) + { + setSize( ::basegfx::B2ISize(rBounds.Width, rBounds.Height) ); + } + + SurfaceSharedPtr const & SpriteDeviceHelper::getWindowSurface() const + { + return DeviceHelper::getSurface(); + } + + SurfaceSharedPtr SpriteDeviceHelper::createSurface( const ::basegfx::B2ISize& rSize, int aContent ) + { + if( mpBufferSurface ) + return mpBufferSurface->getSimilar( aContent, rSize.getX(), rSize.getY() ); + + return SurfaceSharedPtr(); + } + + SurfaceSharedPtr SpriteDeviceHelper::createSurface( BitmapSystemData const & rData, const Size& rSize ) + { + OutputDevice *pDevice = getOutputDevice(); + if (pDevice) + return pDevice->CreateBitmapSurface(rData, rSize); + return SurfaceSharedPtr(); + } + + /** SpriteDeviceHelper::flush Flush the platform native window + * + * Flushes the window by using the internally stored mpSysData. + * + **/ + void SpriteDeviceHelper::flush() + { + SurfaceSharedPtr pWinSurface=getWindowSurface(); + if( pWinSurface ) + pWinSurface->flush(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritedevicehelper.hxx b/canvas/source/cairo/cairo_spritedevicehelper.hxx new file mode 100644 index 000000000..63e9d1303 --- /dev/null +++ b/canvas/source/cairo/cairo_spritedevicehelper.hxx @@ -0,0 +1,77 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/awt/Rectangle.hpp> + +#include <vcl/window.hxx> +#include <vcl/cairo.hxx> + +#include "cairo_devicehelper.hxx" + +/* Definition of DeviceHelper class */ + +namespace cairocanvas +{ + class SpriteCanvas; + + class SpriteDeviceHelper : public DeviceHelper + { + public: + SpriteDeviceHelper(); + + void init( vcl::Window& rOutputWindow, + SpriteCanvas& rSpriteCanvas, + const ::basegfx::B2ISize& rSize, + bool bFullscreen ); + + /// Dispose all internal references + void disposing(); + + // XWindowGraphicDevice + bool showBuffer( bool, bool ); + bool switchBuffer( bool, bool bUpdateAll ); + + css::uno::Any isAccelerated() const; + css::uno::Any getDeviceHandle() const; + css::uno::Any getSurfaceHandle() const; + + void notifySizeUpdate( const css::awt::Rectangle& rBounds ); + void setSize( const ::basegfx::B2ISize& rSize ); + + const ::cairo::SurfaceSharedPtr& getBufferSurface() const { return mpBufferSurface; } + ::cairo::SurfaceSharedPtr const & getWindowSurface() const; + ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, int aContent ); + ::cairo::SurfaceSharedPtr createSurface( BitmapSystemData const & rData, const Size& rSize ); + const ::basegfx::B2ISize& getSizePixel() const { return maSize; } + void flush(); + + private: + /// Pointer to sprite canvas (owner of this helper), needed to create bitmaps + SpriteCanvas* mpSpriteCanvas; + + ::cairo::SurfaceSharedPtr mpBufferSurface; + + ::basegfx::B2ISize maSize; + bool mbFullScreen; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritehelper.cxx b/canvas/source/cairo/cairo_spritehelper.cxx new file mode 100644 index 000000000..b3806f0e9 --- /dev/null +++ b/canvas/source/cairo/cairo_spritehelper.cxx @@ -0,0 +1,195 @@ +/* -*- 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 <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <rtl/math.hxx> +#include <tools/diagnose_ex.h> + +#include <cairo.h> +#include <pixman.h> + +#include "cairo_spritehelper.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + SpriteHelper::SpriteHelper() : + mbTextureDirty(true) + {} + + void SpriteHelper::init( const geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rSpriteCanvas ) + { + ENSURE_OR_THROW( rSpriteCanvas, + "SpriteHelper::init(): Invalid device, sprite canvas or surface" ); + + mpSpriteCanvas = rSpriteCanvas; + mbTextureDirty = true; + + // also init base class + CanvasCustomSpriteHelper::init( rSpriteSize, rSpriteCanvas ); + } + + void SpriteHelper::setSurface( const SurfaceSharedPtr& pBufferSurface ) + { + mpBufferSurface = pBufferSurface; + } + + void SpriteHelper::disposing() + { + mpBufferSurface.reset(); + mpSpriteCanvas.clear(); + + // forward to parent + CanvasCustomSpriteHelper::disposing(); + } + + void SpriteHelper::redraw( const CairoSharedPtr& pCairo, + const ::basegfx::B2DPoint& rPos, + bool& /*io_bSurfacesDirty*/, + bool /*bBufferedUpdate*/ ) const + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + const double fAlpha( getAlpha() ); + const ::basegfx::B2DHomMatrix aTransform( getTransformation() ); + + if( !isActive() || ::basegfx::fTools::equalZero( fAlpha ) ) + return; + + SAL_INFO( "canvas.cairo", "CanvasCustomSprite::redraw called"); + if( !pCairo ) + return; + + basegfx::B2DVector aSize = getSizePixel(); + cairo_save( pCairo.get() ); + + double fX, fY; + + fX = rPos.getX(); + fY = rPos.getY(); + + if( !aTransform.isIdentity() ) + { + cairo_matrix_t aMatrix, aInverseMatrix; + cairo_matrix_init( &aMatrix, + aTransform.get( 0, 0 ), aTransform.get( 1, 0 ), aTransform.get( 0, 1 ), + aTransform.get( 1, 1 ), aTransform.get( 0, 2 ), aTransform.get( 1, 2 ) ); + + aMatrix.x0 = basegfx::fround( aMatrix.x0 ); + aMatrix.y0 = basegfx::fround( aMatrix.y0 ); + + cairo_matrix_init( &aInverseMatrix, aMatrix.xx, aMatrix.yx, aMatrix.xy, aMatrix.yy, aMatrix.x0, aMatrix.y0 ); + cairo_matrix_invert( &aInverseMatrix ); + cairo_matrix_transform_distance( &aInverseMatrix, &fX, &fY ); + + cairo_set_matrix( pCairo.get(), &aMatrix ); + } + + fX = basegfx::fround( fX ); + fY = basegfx::fround( fY ); + + cairo_matrix_t aOrigMatrix; + cairo_get_matrix( pCairo.get(), &aOrigMatrix ); + cairo_translate( pCairo.get(), fX, fY ); + + if( getClip().is() ) + { + const uno::Reference<rendering::XPolyPolygon2D>& rClip( getClip() ); + + ::basegfx::B2DPolyPolygon aClipPoly( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( + rClip )); + + doPolyPolygonImplementation( aClipPoly, Clip, pCairo.get(), + nullptr, SurfaceProviderRef(mpSpriteCanvas), + rClip->getFillRule() ); + } + + SAL_INFO( "canvas.cairo","aSize " << aSize.getX() << " x " << aSize.getY() << " position: " << fX << "," << fY ); + cairo_rectangle( pCairo.get(), 0, 0, floor( aSize.getX() ), floor( aSize.getY() ) ); + cairo_clip( pCairo.get() ); + cairo_set_matrix( pCairo.get(), &aOrigMatrix ); + + cairo_matrix_t aInverseMatrix = aOrigMatrix; + bool matrixProblem = false; + // tdf#125949: Cairo internally uses the pixman library, and _cairo_matrix_to_pixman_matrix() + // checks all matrix components to fix PIXMAN_MAX_INT, which is about 32k. Which means that + // if our transformation is large, such as an initial step of some zooming animations, + // the conversion will fail. To make things worse, once something in cairo fails, it's treated + // as a fatal error, the error status of that cairo_t gets set, and there's no way to reset it + // besides recreating the whole cairo_t + // (https://lists.cairographics.org/archives/cairo/2006-September/007892.html). + // So copy&paste PIXMAN_MAX_INT here, and if our matrix could fail, bail out. +#define PIXMAN_MAX_INT ((pixman_fixed_1 >> 1) - pixman_fixed_e) /* need to ensure deltas also fit */ + if(cairo_matrix_invert(&aInverseMatrix) == CAIRO_STATUS_SUCCESS) + { + if(abs(aOrigMatrix.xx) > PIXMAN_MAX_INT || abs(aOrigMatrix.xx) > PIXMAN_MAX_INT + || abs(aOrigMatrix.xy) > PIXMAN_MAX_INT || abs(aOrigMatrix.yx) > PIXMAN_MAX_INT + || abs(aOrigMatrix.x0) > PIXMAN_MAX_INT || abs(aOrigMatrix.y0) > PIXMAN_MAX_INT + || abs(aInverseMatrix.xx) > PIXMAN_MAX_INT || abs(aInverseMatrix.xx) > PIXMAN_MAX_INT + || abs(aInverseMatrix.xy) > PIXMAN_MAX_INT || abs(aInverseMatrix.yx) > PIXMAN_MAX_INT + || abs(aInverseMatrix.x0) > PIXMAN_MAX_INT || abs(aInverseMatrix.y0) > PIXMAN_MAX_INT) + matrixProblem = true; +#undef PIXMAN_MAX_INT + } + else + matrixProblem = true; + if(matrixProblem) + { + SAL_WARN( "canvas.cairo", "matrix would overflow PIXMAN_MAX_INT, avoiding drawing" ); + cairo_restore( pCairo.get() ); + return; + } + + if( isContentFullyOpaque() ) + cairo_set_operator( pCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_set_source_surface( pCairo.get(), + mpBufferSurface->getCairoSurface().get(), + fX, fY ); + if( ::rtl::math::approxEqual( fAlpha, 1.0 ) ) + cairo_paint( pCairo.get() ); + else + cairo_paint_with_alpha( pCairo.get(), fAlpha ); + + cairo_restore( pCairo.get() ); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "sprite redraw" ); +#endif + } + + ::basegfx::B2DPolyPolygon SpriteHelper::polyPolygonFromXPolyPolygon2D( uno::Reference< rendering::XPolyPolygon2D >& xPoly ) const + { + return ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPoly); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritehelper.hxx b/canvas/source/cairo/cairo_spritehelper.hxx new file mode 100644 index 000000000..df0919e7f --- /dev/null +++ b/canvas/source/cairo/cairo_spritehelper.hxx @@ -0,0 +1,102 @@ +/* -*- 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 . + */ + +#pragma once + +#include <base/canvascustomspritehelper.hxx> + +#include <basegfx/point/b2dpoint.hxx> + +#include "cairo_spritecanvas.hxx" + + +namespace cairocanvas +{ + /* Definition of SpriteHelper class */ + + /** Helper class for canvas sprites. + + This class implements all sprite-related functionality, like + that available on the XSprite interface. + */ + class SpriteHelper : public ::canvas::CanvasCustomSpriteHelper + { + public: + /** Create sprite helper + */ + SpriteHelper(); + + /** Late-init the sprite helper + + @param rSpriteSize + Size of the sprite + + @param rSpriteCanvas + Sprite canvas this sprite is part of. Object stores + ref-counted reference to it, thus, don't forget to pass on + disposing()! + + @param rDevice + DX device to use + + @param rSpriteSurface + The surface of the sprite (not the DX texture, but the + persistent target of content rendering) + + @param bShowSpriteBounds + When true, little debug bound rects for sprites are shown + */ + void init( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rSpriteCanvas ); + + void disposing(); + + void setSurface( const ::cairo::SurfaceSharedPtr& pBufferSurface ); + + /** Repaint sprite content to associated sprite canvas + + @param rPos + Output position (sprite's own position is disregarded) + + @param io_bSurfacesDirty + When true, the referenced sprite surfaces (backBuffer and + backBufferMask) have been modified since last call. + + @param bBufferedUpdate + When true, the redraw does <em>not</em> happen directly on + the front buffer, but within a VDev. Used to speed up + drawing. + */ + void redraw( const ::cairo::CairoSharedPtr& pCairo, + const ::basegfx::B2DPoint& rPos, + bool& bSurfacesDirty, + bool bBufferedUpdate ) const; + + private: + virtual ::basegfx::B2DPolyPolygon polyPolygonFromXPolyPolygon2D( + css::uno::Reference< css::rendering::XPolyPolygon2D >& xPoly ) const override; + + + SpriteCanvasRef mpSpriteCanvas; + ::cairo::SurfaceSharedPtr mpBufferSurface; + mutable bool mbTextureDirty; // when true, texture needs update + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_surfaceprovider.hxx b/canvas/source/cairo/cairo_surfaceprovider.hxx new file mode 100644 index 000000000..a5115a507 --- /dev/null +++ b/canvas/source/cairo/cairo_surfaceprovider.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ref.hxx> +#include <com/sun/star/uno/XInterface.hpp> + +#include <basegfx/vector/b2isize.hxx> +#include <vcl/cairo.hxx> + +class OutputDevice; +class Bitmap; + +namespace cairocanvas +{ + /* Definition of RepaintTarget interface */ + + /** Target interface for XCachedPrimitive implementations + + This interface must be implemented on all canvas + implementations that hand out XCachedPrimitives + */ + class SurfaceProvider : public css::uno::XInterface + { + public: + virtual ~SurfaceProvider() {} + + /** Query surface from this provider + + This should return the default surface to render on. + */ + virtual ::cairo::SurfaceSharedPtr getSurface() = 0; + + /// create new surface in given size + virtual ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, + int aContent ) = 0; + /// create new surface from given bitmap + virtual ::cairo::SurfaceSharedPtr createSurface( ::Bitmap& rBitmap ) = 0; + + /** convert surface from alpha to non-alpha, does not copy content + channel. returns new surface on success, NULL otherwise + */ + virtual ::cairo::SurfaceSharedPtr changeSurface() = 0; + + /** Provides the underlying vcl outputdevice this surface renders on + */ + virtual OutputDevice* getOutputDevice() = 0; + }; + + typedef ::rtl::Reference< SurfaceProvider > SurfaceProviderRef; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_textlayout.cxx b/canvas/source/cairo/cairo_textlayout.cxx new file mode 100644 index 000000000..cbbf02c56 --- /dev/null +++ b/canvas/source/cairo/cairo_textlayout.cxx @@ -0,0 +1,354 @@ +/* -*- 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 <math.h> + +#include <com/sun/star/rendering/TextDirection.hpp> +#include <canvas/canvastools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <tools/diagnose_ex.h> +#include <utility> +#include <vcl/metric.hxx> +#include <vcl/virdev.hxx> + + +#include "cairo_textlayout.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + namespace + { + void setupLayoutMode( OutputDevice& rOutDev, + sal_Int8 nTextDirection ) + { + // TODO(P3): avoid if already correctly set + vcl::text::ComplexTextLayoutFlags nLayoutMode = vcl::text::ComplexTextLayoutFlags::Default; + switch( nTextDirection ) + { + case rendering::TextDirection::WEAK_LEFT_TO_RIGHT: + break; + case rendering::TextDirection::STRONG_LEFT_TO_RIGHT: + nLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiStrong; + break; + case rendering::TextDirection::WEAK_RIGHT_TO_LEFT: + nLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl; + break; + case rendering::TextDirection::STRONG_RIGHT_TO_LEFT: + nLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong; + break; + default: + break; + } + + // set calculated layout mode. Origin is always the left edge, + // as required at the API spec + rOutDev.SetLayoutMode( nLayoutMode | vcl::text::ComplexTextLayoutFlags::TextOriginLeft ); + } + } + + TextLayout::TextLayout( rendering::StringContext aText, + sal_Int8 nDirection, + sal_Int64 /*nRandomSeed*/, + CanvasFont::Reference rFont, + SurfaceProviderRef rRefDevice ) : + TextLayout_Base( m_aMutex ), + maText(std::move( aText )), + mpFont(std::move( rFont )), + mpRefDevice(std::move( rRefDevice )), + mnTextDirection( nDirection ) + { + } + + TextLayout::~TextLayout() + { + } + + void SAL_CALL TextLayout::disposing() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mpFont.clear(); + mpRefDevice.clear(); + } + + // XTextLayout + uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > > SAL_CALL TextLayout::queryTextShapes( ) + { + // TODO + return uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > >(); + } + + uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryInkMeasures( ) + { + // TODO + return uno::Sequence< geometry::RealRectangle2D >(); + } + + uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryMeasures( ) + { + // TODO + return uno::Sequence< geometry::RealRectangle2D >(); + } + + uno::Sequence< double > SAL_CALL TextLayout::queryLogicalAdvancements( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maLogicalAdvancements; + } + + void SAL_CALL TextLayout::applyLogicalAdvancements( const uno::Sequence< double >& aAdvancements ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if( aAdvancements.getLength() != maText.Length ) + { + SAL_WARN("canvas.cairo", "TextLayout::applyLogicalAdvancements(): mismatching number of advancements" ); + throw lang::IllegalArgumentException("mismatching number of advancements", static_cast<cppu::OWeakObject*>(this), 1); + } + + maLogicalAdvancements = aAdvancements; + } + + geometry::RealRectangle2D SAL_CALL TextLayout::queryTextBounds( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + OutputDevice* pOutDev = mpRefDevice->getOutputDevice(); + if( !pOutDev ) + return geometry::RealRectangle2D(); + + ScopedVclPtrInstance< VirtualDevice > pVDev( *pOutDev ); + pVDev->SetFont( mpFont->getVCLFont() ); + + // need metrics for Y offset, the XCanvas always renders + // relative to baseline + const ::FontMetric& aMetric( pVDev->GetFontMetric() ); + + setupLayoutMode( *pVDev, mnTextDirection ); + + const sal_Int32 nAboveBaseline( -aMetric.GetAscent() ); + const sal_Int32 nBelowBaseline( aMetric.GetDescent() ); + + if( maLogicalAdvancements.hasElements() ) + { + return geometry::RealRectangle2D( 0, nAboveBaseline, + maLogicalAdvancements[ maLogicalAdvancements.getLength()-1 ], + nBelowBaseline ); + } + else + { + return geometry::RealRectangle2D( 0, nAboveBaseline, + pVDev->GetTextWidth( + maText.Text, + ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ), + nBelowBaseline ); + } + } + + double SAL_CALL TextLayout::justify( double /*nSize*/ ) + { + // TODO + return 0.0; + } + + double SAL_CALL TextLayout::combinedJustify( const uno::Sequence< uno::Reference< rendering::XTextLayout > >& /*aNextLayouts*/, + double /*nSize*/ ) + { + // TODO + return 0.0; + } + + rendering::TextHit SAL_CALL TextLayout::getTextHit( const geometry::RealPoint2D& /*aHitPoint*/ ) + { + // TODO + return rendering::TextHit(); + } + + rendering::Caret SAL_CALL TextLayout::getCaret( sal_Int32 /*nInsertionIndex*/, + sal_Bool /*bExcludeLigatures*/ ) + { + // TODO + return rendering::Caret(); + } + + sal_Int32 SAL_CALL TextLayout::getNextInsertionIndex( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nCaretAdvancement*/, + sal_Bool /*bExcludeLigatures*/ ) + { + // TODO + return 0; + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryVisualHighlighting( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nEndIndex*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryLogicalHighlighting( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nEndIndex*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + double SAL_CALL TextLayout::getBaselineOffset( ) + { + // TODO + return 0.0; + } + + sal_Int8 SAL_CALL TextLayout::getMainTextDirection( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mnTextDirection; + } + + uno::Reference< rendering::XCanvasFont > SAL_CALL TextLayout::getFont( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mpFont; + } + + rendering::StringContext SAL_CALL TextLayout::getText( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maText; + } + + /** + * TextLayout::draw + * + * Cairo-based text rendering. Draw text directly on the cairo surface with cairo fonts. + * Avoid using VCL VirtualDevices for that, bypassing VCL DrawText functions, when possible + * + * Note: some text effects are not rendered due to lacking generic canvas or cairo canvas + * implementation. See issues 92657, 92658, 92659, 92660, 97529 + **/ + void TextLayout::draw( OutputDevice& rOutDev, + const Point& rOutpos, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + setupLayoutMode( rOutDev, mnTextDirection ); + + std::vector<sal_Int32> aOffsets(maLogicalAdvancements.getLength()); + + if( maLogicalAdvancements.hasElements() ) + setupTextOffsets( aOffsets.data(), maLogicalAdvancements, viewState, renderState ); + + if (maLogicalAdvancements.hasElements()) + { + rOutDev.DrawTextArray( rOutpos, maText.Text, aOffsets, + ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ); + } + else + { + rOutDev.DrawText( rOutpos, maText.Text, + ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ); + } + } + + namespace + { + class OffsetTransformer + { + public: + explicit OffsetTransformer( ::basegfx::B2DHomMatrix aMat ) : + maMatrix(std::move( aMat )) + { + } + + sal_Int32 operator()( const double& rOffset ) + { + // This is an optimization of the normal rMat*[x,0] + // transformation of the advancement vector (in x + // direction), followed by a length calculation of the + // resulting vector: advancement' = + // ||rMat*[x,0]||. Since advancements are vectors, we + // can ignore translational components, thus if [x,0], + // it follows that rMat*[x,0]=[x',0] holds. Thus, we + // just have to calc the transformation of the x + // component. + + // TODO(F2): Handle non-horizontal advancements! + return ::basegfx::fround( hypot(maMatrix.get(0,0)*rOffset, + maMatrix.get(1,0)*rOffset) ); + } + + private: + ::basegfx::B2DHomMatrix maMatrix; + }; + } + + void TextLayout::setupTextOffsets( sal_Int32* outputOffsets, + const uno::Sequence< double >& inputOffsets, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) const + { + ENSURE_OR_THROW( outputOffsets!=nullptr, + "TextLayout::setupTextOffsets offsets NULL" ); + + ::basegfx::B2DHomMatrix aMatrix; + + ::canvas::tools::mergeViewAndRenderTransform(aMatrix, + viewState, + renderState); + + // fill integer offsets + std::transform( inputOffsets.begin(), + inputOffsets.end(), + outputOffsets, + OffsetTransformer( aMatrix ) ); + } + + OUString SAL_CALL TextLayout::getImplementationName() + { + return "CairoCanvas::TextLayout"; + } + + sal_Bool SAL_CALL TextLayout::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL TextLayout::getSupportedServiceNames() + { + return { "com.sun.star.rendering.TextLayout" }; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_textlayout.hxx b/canvas/source/cairo/cairo_textlayout.hxx new file mode 100644 index 000000000..fb14e620c --- /dev/null +++ b/canvas/source/cairo/cairo_textlayout.hxx @@ -0,0 +1,106 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/RenderState.hpp> +#include <com/sun/star/rendering/ViewState.hpp> +#include <com/sun/star/rendering/XTextLayout.hpp> + +#include <vcl/outdev.hxx> + +#include "cairo_canvasfont.hxx" + + +/* Definition of TextLayout class */ + +namespace cairocanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XTextLayout, + css::lang::XServiceInfo > TextLayout_Base; + + class TextLayout : public ::cppu::BaseMutex, + public TextLayout_Base + { + public: + /// make noncopyable + TextLayout(const TextLayout&) = delete; + const TextLayout& operator=(const TextLayout&) = delete; + + TextLayout( css::rendering::StringContext aText, + sal_Int8 nDirection, + sal_Int64 nRandomSeed, + CanvasFont::Reference rFont, + SurfaceProviderRef rRefDevice ); + + /// Dispose all internal references + virtual void SAL_CALL disposing() override; + + // XTextLayout + virtual css::uno::Sequence< css::uno::Reference< css::rendering::XPolyPolygon2D > > SAL_CALL queryTextShapes( ) override; + virtual css::uno::Sequence< css::geometry::RealRectangle2D > SAL_CALL queryInkMeasures( ) override; + virtual css::uno::Sequence< css::geometry::RealRectangle2D > SAL_CALL queryMeasures( ) override; + virtual css::uno::Sequence< double > SAL_CALL queryLogicalAdvancements( ) override; + virtual void SAL_CALL applyLogicalAdvancements( const css::uno::Sequence< double >& aAdvancements ) override; + virtual css::geometry::RealRectangle2D SAL_CALL queryTextBounds( ) override; + virtual double SAL_CALL justify( double nSize ) override; + virtual double SAL_CALL combinedJustify( const css::uno::Sequence< css::uno::Reference< css::rendering::XTextLayout > >& aNextLayouts, double nSize ) override; + virtual css::rendering::TextHit SAL_CALL getTextHit( const css::geometry::RealPoint2D& aHitPoint ) override; + virtual css::rendering::Caret SAL_CALL getCaret( sal_Int32 nInsertionIndex, sal_Bool bExcludeLigatures ) override; + virtual sal_Int32 SAL_CALL getNextInsertionIndex( sal_Int32 nStartIndex, sal_Int32 nCaretAdvancement, sal_Bool bExcludeLigatures ) override; + virtual css::uno::Reference< css::rendering::XPolyPolygon2D > SAL_CALL queryVisualHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual css::uno::Reference< css::rendering::XPolyPolygon2D > SAL_CALL queryLogicalHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual double SAL_CALL getBaselineOffset( ) override; + virtual sal_Int8 SAL_CALL getMainTextDirection( ) override; + virtual css::uno::Reference< css::rendering::XCanvasFont > SAL_CALL getFont( ) override; + virtual css::rendering::StringContext SAL_CALL getText( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + void draw( OutputDevice& rOutDev, + const Point& rOutpos, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) const; + + void setupTextOffsets( sal_Int32* outputOffsets, + const css::uno::Sequence< double >& inputOffsets, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) const; + + protected: + virtual ~TextLayout() override; // we're a ref-counted UNO class. _We_ destroy ourselves. + + private: + css::rendering::StringContext maText; + css::uno::Sequence< double > maLogicalAdvancements; + CanvasFont::Reference mpFont; + SurfaceProviderRef mpRefDevice; + sal_Int8 mnTextDirection; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairocanvas.component b/canvas/source/cairo/cairocanvas.component new file mode 100644 index 000000000..7a201f582 --- /dev/null +++ b/canvas/source/cairo/cairocanvas.component @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.rendering.Canvas.Cairo" + constructor="com_sun_star_comp_rendering_Canvas_Cairo_get_implementation"> + <service name="com.sun.star.rendering.Canvas.Cairo"/> + </implementation> + <implementation name="com.sun.star.comp.rendering.SpriteCanvas.Cairo" + constructor="com_sun_star_comp_rendering_SpriteCanvas_Cairo_get_implementation"> + <service name="com.sun.star.rendering.SpriteCanvas.Cairo"/> + </implementation> +</component> |