summaryrefslogtreecommitdiffstats
path: root/canvas/source/tools
diff options
context:
space:
mode:
Diffstat (limited to 'canvas/source/tools')
-rw-r--r--canvas/source/tools/cachedprimitivebase.cxx93
-rw-r--r--canvas/source/tools/canvascustomspritehelper.cxx439
-rw-r--r--canvas/source/tools/canvastools.cxx1308
-rw-r--r--canvas/source/tools/elapsedtime.cxx130
-rw-r--r--canvas/source/tools/page.cxx133
-rw-r--r--canvas/source/tools/page.hxx148
-rw-r--r--canvas/source/tools/pagemanager.cxx150
-rw-r--r--canvas/source/tools/pagemanager.hxx76
-rw-r--r--canvas/source/tools/parametricpolypolygon.cxx236
-rw-r--r--canvas/source/tools/propertysethelper.cxx156
-rw-r--r--canvas/source/tools/spriteredrawmanager.cxx483
-rw-r--r--canvas/source/tools/surface.cxx437
-rw-r--r--canvas/source/tools/surface.hxx143
-rw-r--r--canvas/source/tools/surfaceproxy.cxx141
-rw-r--r--canvas/source/tools/surfaceproxy.hxx118
-rw-r--r--canvas/source/tools/surfaceproxymanager.cxx73
-rw-r--r--canvas/source/tools/surfacerect.hxx90
-rw-r--r--canvas/source/tools/verifyinput.cxx709
18 files changed, 5063 insertions, 0 deletions
diff --git a/canvas/source/tools/cachedprimitivebase.cxx b/canvas/source/tools/cachedprimitivebase.cxx
new file mode 100644
index 0000000000..7aaf3d58ac
--- /dev/null
+++ b/canvas/source/tools/cachedprimitivebase.cxx
@@ -0,0 +1,93 @@
+/* -*- 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/matrix/b2dhommatrix.hxx>
+#include <basegfx/utils/canvastools.hxx>
+#include <com/sun/star/rendering/RepaintResult.hpp>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <base/cachedprimitivebase.hxx>
+#include <utility>
+
+
+using namespace ::com::sun::star;
+
+namespace canvas
+{
+ CachedPrimitiveBase::CachedPrimitiveBase( rendering::ViewState aUsedViewState,
+ uno::Reference< rendering::XCanvas > xTarget ) :
+ maUsedViewState(std::move( aUsedViewState )),
+ mxTarget(std::move( xTarget ))
+ {
+ }
+
+ CachedPrimitiveBase::~CachedPrimitiveBase()
+ {
+ }
+
+ void CachedPrimitiveBase::disposing(std::unique_lock<std::mutex>& /*rGuard*/)
+ {
+ maUsedViewState.Clip.clear();
+ mxTarget.clear();
+ }
+
+ sal_Int8 SAL_CALL CachedPrimitiveBase::redraw( const rendering::ViewState& aState )
+ {
+ ::basegfx::B2DHomMatrix aUsedTransformation;
+ ::basegfx::B2DHomMatrix aNewTransformation;
+
+ ::basegfx::unotools::homMatrixFromAffineMatrix( aUsedTransformation,
+ maUsedViewState.AffineTransform );
+ ::basegfx::unotools::homMatrixFromAffineMatrix( aNewTransformation,
+ aState.AffineTransform );
+
+ const bool bSameViewTransforms( aUsedTransformation == aNewTransformation );
+
+ if( !bSameViewTransforms )
+ {
+ // differing transformations, don't try to draft the
+ // output, just plain fail here.
+ return rendering::RepaintResult::FAILED;
+ }
+
+ return doRedraw( aState,
+ maUsedViewState,
+ mxTarget,
+ bSameViewTransforms );
+ }
+
+ OUString SAL_CALL CachedPrimitiveBase::getImplementationName( )
+ {
+ return "canvas::CachedPrimitiveBase";
+ }
+
+ sal_Bool SAL_CALL CachedPrimitiveBase::supportsService( const OUString& ServiceName )
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ uno::Sequence< OUString > SAL_CALL CachedPrimitiveBase::getSupportedServiceNames( )
+ {
+ return { "com.sun.star.rendering.CachedBitmap" };
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/canvascustomspritehelper.cxx b/canvas/source/tools/canvascustomspritehelper.cxx
new file mode 100644
index 0000000000..975d62325d
--- /dev/null
+++ b/canvas/source/tools/canvascustomspritehelper.cxx
@@ -0,0 +1,439 @@
+/* -*- 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/XPolyPolygon2D.hpp>
+#include <com/sun/star/geometry/RealSize2D.hpp>
+#include <com/sun/star/rendering/XBitmap.hpp>
+#include <com/sun/star/geometry/IntegerSize2D.hpp>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <basegfx/utils/canvastools.hxx>
+#include <basegfx/vector/b2dsize.hxx>
+#include <rtl/math.hxx>
+#include <comphelper/diagnose_ex.hxx>
+
+#include <base/canvascustomspritehelper.hxx>
+#include <canvas/canvastools.hxx>
+
+using namespace ::com::sun::star;
+
+
+namespace canvas
+{
+ bool CanvasCustomSpriteHelper::updateClipState( const Sprite::Reference& rSprite )
+ {
+ if( !mxClipPoly.is() )
+ {
+ // empty clip polygon -> everything is visible now
+ maCurrClipBounds.reset();
+ mbIsCurrClipRectangle = true;
+ }
+ else
+ {
+ const sal_Int32 nNumClipPolygons( mxClipPoly->getNumberOfPolygons() );
+
+ // clip is not empty - determine actual update area
+ ::basegfx::B2DPolyPolygon aClipPath(
+ polyPolygonFromXPolyPolygon2D( mxClipPoly ) );
+
+ // apply sprite transformation also to clip!
+ aClipPath.transform( maTransform );
+
+ // clip which is about to be set, expressed as a
+ // b2drectangle
+ const ::basegfx::B2DRectangle& rClipBounds(
+ ::basegfx::utils::getRange( aClipPath ) );
+
+ const ::basegfx::B2DRectangle aBounds( 0.0, 0.0,
+ maSize.getX(),
+ maSize.getY() );
+
+ // rectangular area which is actually covered by the sprite.
+ // coordinates are relative to the sprite origin.
+ ::basegfx::B2DRectangle aSpriteRectPixel;
+ ::canvas::tools::calcTransformedRectBounds( aSpriteRectPixel,
+ aBounds,
+ maTransform );
+
+ // aClipBoundsA = new clip bound rect, intersected
+ // with sprite area
+ ::basegfx::B2DRectangle aClipBoundsA(rClipBounds);
+ aClipBoundsA.intersect( aSpriteRectPixel );
+
+ if( nNumClipPolygons != 1 )
+ {
+ // clip cannot be a single rectangle -> cannot
+ // optimize update
+ mbIsCurrClipRectangle = false;
+ maCurrClipBounds = aClipBoundsA;
+ }
+ else
+ {
+ // new clip could be a single rectangle - check
+ // that now:
+ const bool bNewClipIsRect(
+ ::basegfx::utils::isRectangle( aClipPath.getB2DPolygon(0) ) );
+
+ // both new and old clip are truly rectangles
+ // - can now take the optimized path
+ const bool bUseOptimizedUpdate( bNewClipIsRect &&
+ mbIsCurrClipRectangle );
+
+ const ::basegfx::B2DRectangle aOldBounds( maCurrClipBounds );
+
+ // store new current clip type
+ maCurrClipBounds = aClipBoundsA;
+ mbIsCurrClipRectangle = bNewClipIsRect;
+
+ if( mbActive &&
+ bUseOptimizedUpdate )
+ {
+ // aClipBoundsB = maCurrClipBounds, i.e. last
+ // clip, intersected with sprite area
+ std::vector< ::basegfx::B2DRectangle > aClipDifferences;
+
+ // get all rectangles covered by exactly one
+ // of the polygons (aka XOR)
+ ::basegfx::computeSetDifference(aClipDifferences,
+ aClipBoundsA,
+ aOldBounds);
+
+ // aClipDifferences now contains the final
+ // update areas, coordinates are still relative
+ // to the sprite origin. before submitting
+ // this area to 'updateSprite()' we need to
+ // translate this area to the final position,
+ // coordinates need to be relative to the
+ // spritecanvas.
+ for( const auto& rClipDiff : aClipDifferences )
+ {
+ mpSpriteCanvas->updateSprite(
+ rSprite,
+ maPosition,
+ ::basegfx::B2DRectangle(
+ maPosition + rClipDiff.getMinimum(),
+ maPosition + rClipDiff.getMaximum() ) );
+ }
+
+ // update calls all done
+ return true;
+ }
+ }
+ }
+
+ // caller needs to perform update calls
+ return false;
+ }
+
+ CanvasCustomSpriteHelper::CanvasCustomSpriteHelper() :
+ mfPriority(0.0),
+ mfAlpha(0.0),
+ mbActive(false),
+ mbIsCurrClipRectangle(true),
+ mbIsContentFullyOpaque( false ),
+ mbTransformDirty( true )
+ {
+ }
+
+ void CanvasCustomSpriteHelper::init( const geometry::RealSize2D& rSpriteSize,
+ const SpriteSurface::Reference& rOwningSpriteCanvas )
+ {
+ ENSURE_OR_THROW( rOwningSpriteCanvas,
+ "CanvasCustomSpriteHelper::init(): Invalid owning sprite canvas" );
+
+ mpSpriteCanvas = rOwningSpriteCanvas;
+ maSize.setX( std::max( 1.0,
+ ceil( rSpriteSize.Width ) ) ); // round up to nearest int,
+ // enforce sprite to have at
+ // least (1,1) pixel size
+ maSize.setY( std::max( 1.0,
+ ceil( rSpriteSize.Height ) ) );
+ }
+
+ void CanvasCustomSpriteHelper::disposing()
+ {
+ mpSpriteCanvas.clear();
+ }
+
+ void CanvasCustomSpriteHelper::clearingContent( const Sprite::Reference& /*rSprite*/ )
+ {
+ // about to clear content to fully transparent
+ mbIsContentFullyOpaque = false;
+ }
+
+ void CanvasCustomSpriteHelper::checkDrawBitmap( const Sprite::Reference& rSprite,
+ const uno::Reference< rendering::XBitmap >& xBitmap,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ // check whether bitmap is non-alpha, and whether its
+ // transformed size covers the whole sprite.
+ if( xBitmap->hasAlpha() )
+ return;
+
+ const geometry::IntegerSize2D& rInputSize(xBitmap->getSize());
+ basegfx::B2DSize rOurSize(rSprite->getSizePixel().getX(), rSprite->getSizePixel().getY());
+
+ ::basegfx::B2DHomMatrix aTransform;
+ if( tools::isInside(
+ ::basegfx::B2DRectangle( 0.0,0.0,
+ rOurSize.getWidth(),
+ rOurSize.getHeight() ),
+ ::basegfx::B2DRectangle( 0.0,0.0,
+ rInputSize.Width,
+ rInputSize.Height ),
+ ::canvas::tools::mergeViewAndRenderTransform(aTransform,
+ viewState,
+ renderState) ) )
+ {
+ // bitmap is opaque and will fully cover the sprite,
+ // set flag appropriately
+ mbIsContentFullyOpaque = true;
+ }
+ }
+
+ void CanvasCustomSpriteHelper::setAlpha( const Sprite::Reference& rSprite,
+ double alpha )
+ {
+ if( !mpSpriteCanvas )
+ return; // we're disposed
+
+ if( alpha != mfAlpha )
+ {
+ mfAlpha = alpha;
+
+ if( mbActive )
+ {
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ getUpdateArea() );
+ }
+ }
+ }
+
+ void CanvasCustomSpriteHelper::move( const Sprite::Reference& rSprite,
+ const geometry::RealPoint2D& aNewPos,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ if( !mpSpriteCanvas )
+ return; // we're disposed
+
+ ::basegfx::B2DHomMatrix aTransform;
+ ::canvas::tools::mergeViewAndRenderTransform(aTransform,
+ viewState,
+ renderState);
+
+ // convert position to device pixel
+ ::basegfx::B2DPoint aPoint(
+ ::basegfx::unotools::b2DPointFromRealPoint2D(aNewPos) );
+ aPoint *= aTransform;
+
+ if( aPoint == maPosition )
+ return;
+
+ const ::basegfx::B2DRectangle& rBounds
+ = getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0,
+ maSize.getX(),
+ maSize.getY() ) );
+
+ if( mbActive )
+ {
+ mpSpriteCanvas->moveSprite( rSprite,
+ rBounds.getMinimum(),
+ rBounds.getMinimum() - maPosition + aPoint,
+ rBounds.getRange() );
+ }
+
+ maPosition = aPoint;
+ }
+
+ void CanvasCustomSpriteHelper::transform( const Sprite::Reference& rSprite,
+ const geometry::AffineMatrix2D& aTransformation )
+ {
+ ::basegfx::B2DHomMatrix aMatrix;
+ ::basegfx::unotools::homMatrixFromAffineMatrix(aMatrix,
+ aTransformation);
+
+ if( maTransform == aMatrix )
+ return;
+
+ // retrieve bounds before and after transformation change.
+ const ::basegfx::B2DRectangle& rPrevBounds( getUpdateArea() );
+
+ maTransform = aMatrix;
+
+ if( !updateClipState( rSprite ) &&
+ mbActive )
+ {
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ rPrevBounds );
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ getUpdateArea() );
+ }
+
+ mbTransformDirty = true;
+ }
+
+ void CanvasCustomSpriteHelper::clip( const Sprite::Reference& rSprite,
+ const uno::Reference< rendering::XPolyPolygon2D >& xClip )
+ {
+ // NULL xClip explicitly allowed here (to clear clipping)
+
+ // retrieve bounds before and after clip change.
+ const ::basegfx::B2DRectangle& rPrevBounds( getUpdateArea() );
+
+ mxClipPoly = xClip;
+
+ if( !updateClipState( rSprite ) &&
+ mbActive )
+ {
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ rPrevBounds );
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ getUpdateArea() );
+ }
+ }
+
+ void CanvasCustomSpriteHelper::setPriority( const Sprite::Reference& rSprite,
+ double nPriority )
+ {
+ if( !mpSpriteCanvas )
+ return; // we're disposed
+
+ if( nPriority != mfPriority )
+ {
+ mfPriority = nPriority;
+
+ if( mbActive )
+ {
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ getUpdateArea() );
+ }
+ }
+ }
+
+ void CanvasCustomSpriteHelper::show( const Sprite::Reference& rSprite )
+ {
+ if( !mpSpriteCanvas )
+ return; // we're disposed
+
+ if( mbActive )
+ return;
+
+ mpSpriteCanvas->showSprite( rSprite );
+ mbActive = true;
+
+ // TODO(P1): if clip is the NULL clip (nothing visible),
+ // also save us the update call.
+
+ if( mfAlpha != 0.0 )
+ {
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ getUpdateArea() );
+ }
+ }
+
+ void CanvasCustomSpriteHelper::hide( const Sprite::Reference& rSprite )
+ {
+ if( !mpSpriteCanvas )
+ return; // we're disposed
+
+ if( !mbActive )
+ return;
+
+ mpSpriteCanvas->hideSprite( rSprite );
+ mbActive = false;
+
+ // TODO(P1): if clip is the NULL clip (nothing visible),
+ // also save us the update call.
+
+ if( mfAlpha != 0.0 )
+ {
+ mpSpriteCanvas->updateSprite( rSprite,
+ maPosition,
+ getUpdateArea() );
+ }
+ }
+
+ bool CanvasCustomSpriteHelper::isAreaUpdateOpaque( const ::basegfx::B2DRange& rUpdateArea ) const
+ {
+ if( !mbIsCurrClipRectangle ||
+ !mbIsContentFullyOpaque ||
+ !::rtl::math::approxEqual(mfAlpha, 1.0) )
+ {
+ // sprite either transparent, or clip rect does not
+ // represent exact bounds -> update might not be fully
+ // opaque
+ return false;
+ }
+ else
+ {
+ // make sure sprite rect fully covers update area -
+ // although the update area originates from the sprite,
+ // it's by no means guaranteed that it's limited to this
+ // sprite's update area - after all, other sprites might
+ // have been merged, or this sprite is moving.
+ return getUpdateArea().isInside( rUpdateArea );
+ }
+ }
+
+ ::basegfx::B2DRange CanvasCustomSpriteHelper::getUpdateArea( const ::basegfx::B2DRange& rBounds ) const
+ {
+ // Internal! Only call with locked object mutex!
+ ::basegfx::B2DHomMatrix aTransform( maTransform );
+ aTransform.translate( maPosition.getX(),
+ maPosition.getY() );
+
+ // transform bounds at origin, as the sprite transformation is
+ // formulated that way
+ ::basegfx::B2DRectangle aTransformedBounds;
+ return ::canvas::tools::calcTransformedRectBounds( aTransformedBounds,
+ rBounds,
+ aTransform );
+ }
+
+ ::basegfx::B2DRange CanvasCustomSpriteHelper::getUpdateArea() const
+ {
+ // Internal! Only call with locked object mutex!
+
+ // return effective sprite rect, i.e. take active clip into
+ // account
+ if( maCurrClipBounds.isEmpty() )
+ return getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0,
+ maSize.getX(),
+ maSize.getY() ) );
+ else
+ return ::basegfx::B2DRectangle(
+ maPosition + maCurrClipBounds.getMinimum(),
+ maPosition + maCurrClipBounds.getMaximum() );
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/canvastools.cxx b/canvas/source/tools/canvastools.cxx
new file mode 100644
index 0000000000..1ff3930057
--- /dev/null
+++ b/canvas/source/tools/canvastools.cxx
@@ -0,0 +1,1308 @@
+/* -*- 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 <limits>
+
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <basegfx/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/numeric/ftools.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/point/b2ipoint.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/range/b2drange.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/range/b2irange.hxx>
+#include <basegfx/utils/canvastools.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+#include <com/sun/star/awt/Rectangle.hpp>
+#include <com/sun/star/awt/XWindow2.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/geometry/AffineMatrix2D.hpp>
+#include <com/sun/star/geometry/Matrix2D.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#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/RenderState.hpp>
+#include <com/sun/star/rendering/RenderingIntent.hpp>
+#include <com/sun/star/rendering/ViewState.hpp>
+#include <com/sun/star/rendering/XCanvas.hpp>
+#include <com/sun/star/rendering/XColorSpace.hpp>
+#include <com/sun/star/rendering/XIntegerBitmapColorSpace.hpp>
+#include <com/sun/star/util/Endianness.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <sal/log.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <vcl/canvastools.hxx>
+#include <vcl/window.hxx>
+
+#include <canvas/canvastools.hxx>
+
+
+using namespace ::com::sun::star;
+
+namespace canvas::tools
+{
+ geometry::RealSize2D createInfiniteSize2D()
+ {
+ return geometry::RealSize2D(
+ std::numeric_limits<double>::infinity(),
+ std::numeric_limits<double>::infinity() );
+ }
+
+ rendering::RenderState& initRenderState( rendering::RenderState& renderState )
+ {
+ // setup identity transform
+ setIdentityAffineMatrix2D( renderState.AffineTransform );
+ renderState.Clip.clear();
+ renderState.DeviceColor = uno::Sequence< double >();
+ renderState.CompositeOperation = rendering::CompositeOperation::OVER;
+
+ return renderState;
+ }
+
+ rendering::ViewState& initViewState( rendering::ViewState& viewState )
+ {
+ // setup identity transform
+ setIdentityAffineMatrix2D( viewState.AffineTransform );
+ viewState.Clip.clear();
+
+ return viewState;
+ }
+
+ ::basegfx::B2DHomMatrix& getViewStateTransform( ::basegfx::B2DHomMatrix& transform,
+ const rendering::ViewState& viewState )
+ {
+ return ::basegfx::unotools::homMatrixFromAffineMatrix( transform, viewState.AffineTransform );
+ }
+
+ rendering::ViewState& setViewStateTransform( rendering::ViewState& viewState,
+ const ::basegfx::B2DHomMatrix& transform )
+ {
+ ::basegfx::unotools::affineMatrixFromHomMatrix( viewState.AffineTransform, transform );
+
+ return viewState;
+ }
+
+ ::basegfx::B2DHomMatrix& getRenderStateTransform( ::basegfx::B2DHomMatrix& transform,
+ const rendering::RenderState& renderState )
+ {
+ return ::basegfx::unotools::homMatrixFromAffineMatrix( transform, renderState.AffineTransform );
+ }
+
+ rendering::RenderState& setRenderStateTransform( rendering::RenderState& renderState,
+ const ::basegfx::B2DHomMatrix& transform )
+ {
+ ::basegfx::unotools::affineMatrixFromHomMatrix( renderState.AffineTransform, transform );
+
+ return renderState;
+ }
+
+ rendering::RenderState& appendToRenderState( rendering::RenderState& renderState,
+ const ::basegfx::B2DHomMatrix& rTransform )
+ {
+ ::basegfx::B2DHomMatrix transform;
+
+ getRenderStateTransform( transform, renderState );
+ return setRenderStateTransform( renderState, transform * rTransform );
+ }
+
+ rendering::RenderState& prependToRenderState( rendering::RenderState& renderState,
+ const ::basegfx::B2DHomMatrix& rTransform )
+ {
+ ::basegfx::B2DHomMatrix transform;
+
+ getRenderStateTransform( transform, renderState );
+ return setRenderStateTransform( renderState, rTransform * transform );
+ }
+
+ ::basegfx::B2DHomMatrix& mergeViewAndRenderTransform( ::basegfx::B2DHomMatrix& combinedTransform,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState )
+ {
+ ::basegfx::B2DHomMatrix viewTransform;
+
+ ::basegfx::unotools::homMatrixFromAffineMatrix( combinedTransform, renderState.AffineTransform );
+ ::basegfx::unotools::homMatrixFromAffineMatrix( viewTransform, viewState.AffineTransform );
+
+ // this statement performs combinedTransform = viewTransform * combinedTransform
+ combinedTransform *= viewTransform;
+
+ return combinedTransform;
+ }
+
+ geometry::AffineMatrix2D& setIdentityAffineMatrix2D( geometry::AffineMatrix2D& matrix )
+ {
+ matrix.m00 = 1.0;
+ matrix.m01 = 0.0;
+ matrix.m02 = 0.0;
+ matrix.m10 = 0.0;
+ matrix.m11 = 1.0;
+ matrix.m12 = 0.0;
+
+ return matrix;
+ }
+
+ geometry::Matrix2D& setIdentityMatrix2D( geometry::Matrix2D& matrix )
+ {
+ matrix.m00 = 1.0;
+ matrix.m01 = 0.0;
+ matrix.m10 = 0.0;
+ matrix.m11 = 1.0;
+
+ return matrix;
+ }
+
+ namespace
+ {
+ class StandardColorSpace : 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[0],pIn[1],pIn[2]);
+ pIn += 4;
+ }
+ return aRes;
+ }
+ virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) override
+ {
+ SAL_WARN_IF(!deviceColor.hasElements(), "canvas", "empty deviceColor argument");
+ 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[0],pIn[1],pIn[2]);
+ 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[3]*pIn[0],pIn[3]*pIn[1],pIn[3]*pIn[2]);
+ 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->Red;
+ *pColors++ = pIn->Green;
+ *pColors++ = pIn->Blue;
+ *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->Red;
+ *pColors++ = pIn->Green;
+ *pColors++ = pIn->Blue;
+ *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->Red/pIn->Alpha;
+ *pColors++ = pIn->Green/pIn->Alpha;
+ *pColors++ = pIn->Blue/pIn->Alpha;
+ *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<StandardColorSpace*>(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<StandardColorSpace*>(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(
+ vcl::unotools::toDoubleColor(pIn[0]),
+ vcl::unotools::toDoubleColor(pIn[1]),
+ vcl::unotools::toDoubleColor(pIn[2]));
+ 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 )
+ {
+ *pOut++ = rendering::ARGBColor(
+ vcl::unotools::toDoubleColor(pIn[3]),
+ vcl::unotools::toDoubleColor(pIn[0]),
+ vcl::unotools::toDoubleColor(pIn[1]),
+ vcl::unotools::toDoubleColor(pIn[2]));
+ 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 )
+ {
+ const sal_Int8 nAlpha( pIn[3] );
+ *pOut++ = rendering::ARGBColor(
+ vcl::unotools::toDoubleColor(nAlpha),
+ vcl::unotools::toDoubleColor(nAlpha*pIn[0]),
+ vcl::unotools::toDoubleColor(nAlpha*pIn[1]),
+ vcl::unotools::toDoubleColor(nAlpha*pIn[2]));
+ 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->Red);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Green);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Blue);
+ *pColors++ = 0;
+ ++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 )
+ {
+ *pColors++ = vcl::unotools::toByteColor(pIn->Red);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Green);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Blue);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Alpha);
+ ++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->Red/pIn->Alpha);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Green/pIn->Alpha);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Blue/pIn->Alpha);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Alpha);
+ ++pIn;
+ }
+ return aRes;
+ }
+
+ public:
+ StandardColorSpace() :
+ maComponentTags(4),
+ maBitCounts(4)
+ {
+ sal_Int8* pTags = maComponentTags.getArray();
+ sal_Int32* pBitCounts = maBitCounts.getArray();
+ pTags[0] = rendering::ColorComponentTag::RGB_RED;
+ pTags[1] = rendering::ColorComponentTag::RGB_GREEN;
+ pTags[2] = rendering::ColorComponentTag::RGB_BLUE;
+ pTags[3] = rendering::ColorComponentTag::ALPHA;
+
+ pBitCounts[0] =
+ pBitCounts[1] =
+ pBitCounts[2] =
+ pBitCounts[3] = 8;
+ }
+ };
+
+ class StandardNoAlphaColorSpace : 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[0],pIn[1],pIn[2]);
+ 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 )
+ {
+ *pOut++ = rendering::ARGBColor(1.0,pIn[0],pIn[1],pIn[2]);
+ 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(1.0,pIn[0],pIn[1],pIn[2]);
+ 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->Red;
+ *pColors++ = pIn->Green;
+ *pColors++ = pIn->Blue;
+ *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
+ {
+ 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->Red;
+ *pColors++ = pIn->Green;
+ *pColors++ = pIn->Blue;
+ *pColors++ = 1.0; // the value does not matter
+ ++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->Red/pIn->Alpha;
+ *pColors++ = pIn->Green/pIn->Alpha;
+ *pColors++ = pIn->Blue/pIn->Alpha;
+ *pColors++ = 1.0; // the value does not matter
+ ++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<StandardNoAlphaColorSpace*>(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++;
+ }
+ 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<StandardNoAlphaColorSpace*>(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(
+ vcl::unotools::toDoubleColor(pIn[0]),
+ vcl::unotools::toDoubleColor(pIn[1]),
+ vcl::unotools::toDoubleColor(pIn[2]));
+ 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 )
+ {
+ *pOut++ = rendering::ARGBColor(
+ 1.0,
+ vcl::unotools::toDoubleColor(pIn[0]),
+ vcl::unotools::toDoubleColor(pIn[1]),
+ vcl::unotools::toDoubleColor(pIn[2]));
+ 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(
+ 1.0,
+ vcl::unotools::toDoubleColor(pIn[0]),
+ vcl::unotools::toDoubleColor(pIn[1]),
+ vcl::unotools::toDoubleColor(pIn[2]));
+ 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->Red);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Green);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Blue);
+ *pColors++ = 1.0;
+ ++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 )
+ {
+ *pColors++ = vcl::unotools::toByteColor(pIn->Red);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Green);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Blue);
+ *pColors++ = -1;
+ ++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->Red/pIn->Alpha);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Green/pIn->Alpha);
+ *pColors++ = vcl::unotools::toByteColor(pIn->Blue/pIn->Alpha);
+ *pColors++ = -1;
+ ++pIn;
+ }
+ return aRes;
+ }
+
+ public:
+ StandardNoAlphaColorSpace() :
+ maComponentTags(3),
+ maBitCounts(3)
+ {
+ sal_Int8* pTags = maComponentTags.getArray();
+ sal_Int32* pBitCounts = maBitCounts.getArray();
+ pTags[0] = rendering::ColorComponentTag::RGB_RED;
+ pTags[1] = rendering::ColorComponentTag::RGB_GREEN;
+ pTags[2] = rendering::ColorComponentTag::RGB_BLUE;
+
+ pBitCounts[0] =
+ pBitCounts[1] =
+ pBitCounts[2] = 8;
+ }
+ };
+
+ }
+
+ uno::Reference<rendering::XIntegerBitmapColorSpace> const & getStdColorSpace()
+ {
+ static uno::Reference<rendering::XIntegerBitmapColorSpace> SPACE = new StandardColorSpace();
+ return SPACE;
+ }
+
+ uno::Reference<rendering::XIntegerBitmapColorSpace> const & getStdColorSpaceWithoutAlpha()
+ {
+ static uno::Reference<rendering::XIntegerBitmapColorSpace> SPACE = new StandardNoAlphaColorSpace();
+ return SPACE;
+ }
+
+ rendering::IntegerBitmapLayout getStdMemoryLayout( const geometry::IntegerSize2D& rBmpSize )
+ {
+ rendering::IntegerBitmapLayout aLayout;
+
+ aLayout.ScanLines = rBmpSize.Height;
+ aLayout.ScanLineBytes = rBmpSize.Width*4;
+ aLayout.ScanLineStride = aLayout.ScanLineBytes;
+ aLayout.PlaneStride = 0;
+ aLayout.ColorSpace = getStdColorSpace();
+ aLayout.Palette.clear();
+ aLayout.IsMsbFirst = false;
+
+ return aLayout;
+ }
+
+ uno::Sequence<sal_Int8> colorToStdIntSequence( const ::Color& rColor )
+ {
+ uno::Sequence<sal_Int8> aRet(4);
+ sal_Int8* pCols( aRet.getArray() );
+#ifdef OSL_BIGENDIAN
+ pCols[0] = rColor.GetRed();
+ pCols[1] = rColor.GetGreen();
+ pCols[2] = rColor.GetBlue();
+ pCols[3] = rColor.GetAlpha();
+#else
+ *reinterpret_cast<sal_Int32*>(pCols) = sal_Int32(rColor);
+#endif
+ return aRet;
+ }
+
+ // Create a corrected view transformation out of the give one,
+ // which ensures that the rectangle given by (0,0) and
+ // rSpriteSize is mapped with its left,top corner to (0,0)
+ // again. This is required to properly render sprite
+ // animations to buffer bitmaps.
+ ::basegfx::B2DHomMatrix& calcRectToOriginTransform( ::basegfx::B2DHomMatrix& o_transform,
+ const ::basegfx::B2DRange& i_srcRect,
+ const ::basegfx::B2DHomMatrix& i_transformation )
+ {
+ if( i_srcRect.isEmpty() )
+ {
+ o_transform = i_transformation;
+ return o_transform;
+ }
+
+ // transform by given transformation
+ ::basegfx::B2DRectangle aTransformedRect;
+
+ calcTransformedRectBounds( aTransformedRect,
+ i_srcRect,
+ i_transformation );
+
+ // now move resulting left,top point of bounds to (0,0)
+ const basegfx::B2DHomMatrix aCorrectedTransform(basegfx::utils::createTranslateB2DHomMatrix(
+ -aTransformedRect.getMinX(), -aTransformedRect.getMinY()));
+
+ // prepend to original transformation
+ o_transform = aCorrectedTransform * i_transformation;
+
+ return o_transform;
+ }
+
+ ::basegfx::B2DRange& calcTransformedRectBounds( ::basegfx::B2DRange& outRect,
+ const ::basegfx::B2DRange& inRect,
+ const ::basegfx::B2DHomMatrix& transformation )
+ {
+ outRect.reset();
+
+ if( inRect.isEmpty() )
+ return outRect;
+
+ // transform all four extremal points of the rectangle,
+ // take bounding rect of those.
+
+ // transform left-top point
+ outRect.expand( transformation * inRect.getMinimum() );
+
+ // transform bottom-right point
+ outRect.expand( transformation * inRect.getMaximum() );
+
+ ::basegfx::B2DPoint aPoint;
+
+ // transform top-right point
+ aPoint.setX( inRect.getMaxX() );
+ aPoint.setY( inRect.getMinY() );
+
+ aPoint *= transformation;
+ outRect.expand( aPoint );
+
+ // transform bottom-left point
+ aPoint.setX( inRect.getMinX() );
+ aPoint.setY( inRect.getMaxY() );
+
+ aPoint *= transformation;
+ outRect.expand( aPoint );
+
+ // over and out.
+ return outRect;
+ }
+
+ bool isInside( const ::basegfx::B2DRange& rContainedRect,
+ const ::basegfx::B2DRange& rTransformRect,
+ const ::basegfx::B2DHomMatrix& rTransformation )
+ {
+ if( rContainedRect.isEmpty() || rTransformRect.isEmpty() )
+ return false;
+
+ ::basegfx::B2DPolygon aPoly(
+ ::basegfx::utils::createPolygonFromRect( rTransformRect ) );
+ aPoly.transform( rTransformation );
+
+ return ::basegfx::utils::isInside( aPoly,
+ ::basegfx::utils::createPolygonFromRect(
+ rContainedRect ),
+ true );
+ }
+
+ namespace
+ {
+ bool clipAreaImpl( ::basegfx::B2IRange* o_pDestArea,
+ ::basegfx::B2IRange& io_rSourceArea,
+ ::basegfx::B2IPoint& io_rDestPoint,
+ const ::basegfx::B2IRange& rSourceBounds,
+ const ::basegfx::B2IRange& rDestBounds )
+ {
+ const ::basegfx::B2IPoint aSourceTopLeft(
+ io_rSourceArea.getMinimum() );
+
+ ::basegfx::B2IRange aLocalSourceArea( io_rSourceArea );
+
+ // clip source area (which must be inside rSourceBounds)
+ aLocalSourceArea.intersect( rSourceBounds );
+
+ if( aLocalSourceArea.isEmpty() )
+ return false;
+
+ // calc relative new source area points (relative to orig
+ // source area)
+ const ::basegfx::B2IVector aUpperLeftOffset(
+ aLocalSourceArea.getMinimum()-aSourceTopLeft );
+ const ::basegfx::B2IVector aLowerRightOffset(
+ aLocalSourceArea.getMaximum()-aSourceTopLeft );
+
+ ::basegfx::B2IRange aLocalDestArea( io_rDestPoint + aUpperLeftOffset,
+ io_rDestPoint + aLowerRightOffset );
+
+ // clip dest area (which must be inside rDestBounds)
+ aLocalDestArea.intersect( rDestBounds );
+
+ if( aLocalDestArea.isEmpty() )
+ return false;
+
+ // calc relative new dest area points (relative to orig
+ // source area)
+ const ::basegfx::B2IVector aDestUpperLeftOffset(
+ aLocalDestArea.getMinimum()-io_rDestPoint );
+ const ::basegfx::B2IVector aDestLowerRightOffset(
+ aLocalDestArea.getMaximum()-io_rDestPoint );
+
+ io_rSourceArea = ::basegfx::B2IRange( aSourceTopLeft + aDestUpperLeftOffset,
+ aSourceTopLeft + aDestLowerRightOffset );
+ io_rDestPoint = aLocalDestArea.getMinimum();
+
+ if( o_pDestArea )
+ *o_pDestArea = aLocalDestArea;
+
+ return true;
+ }
+ }
+
+ bool clipScrollArea( ::basegfx::B2IRange& io_rSourceArea,
+ ::basegfx::B2IPoint& io_rDestPoint,
+ std::vector< ::basegfx::B2IRange >& o_ClippedAreas,
+ const ::basegfx::B2IRange& rBounds )
+ {
+ ::basegfx::B2IRange aResultingDestArea;
+
+ // compute full destination area (to determine uninitialized
+ // areas below)
+ const ::basegfx::B2I64Tuple& rRange( io_rSourceArea.getRange() );
+ ::basegfx::B2IRange aInputDestArea( io_rDestPoint.getX(),
+ io_rDestPoint.getY(),
+ (io_rDestPoint.getX()
+ + static_cast<sal_Int32>(rRange.getX())),
+ (io_rDestPoint.getY()
+ + static_cast<sal_Int32>(rRange.getY())) );
+ // limit to output area (no point updating outside of it)
+ aInputDestArea.intersect( rBounds );
+
+ // clip to rBounds
+ if( !clipAreaImpl( &aResultingDestArea,
+ io_rSourceArea,
+ io_rDestPoint,
+ rBounds,
+ rBounds ) )
+ return false;
+
+ // finally, compute all areas clipped off the total
+ // destination area.
+ ::basegfx::computeSetDifference( o_ClippedAreas,
+ aInputDestArea,
+ aResultingDestArea );
+
+ return true;
+ }
+
+ ::basegfx::B2IRange spritePixelAreaFromB2DRange( const ::basegfx::B2DRange& rRange )
+ {
+ if( rRange.isEmpty() )
+ return ::basegfx::B2IRange();
+
+ const ::basegfx::B2IPoint aTopLeft( ::basegfx::fround( rRange.getMinX() ),
+ ::basegfx::fround( rRange.getMinY() ) );
+ return ::basegfx::B2IRange( aTopLeft,
+ aTopLeft + ::basegfx::B2IPoint(
+ ::basegfx::fround( rRange.getWidth() ),
+ ::basegfx::fround( rRange.getHeight() ) ) );
+ }
+
+ uno::Sequence< uno::Any >& getDeviceInfo( const uno::Reference< rendering::XCanvas >& i_rxCanvas,
+ uno::Sequence< uno::Any >& o_rxParams )
+ {
+ o_rxParams.realloc( 0 );
+
+ if( !i_rxCanvas.is() )
+ return o_rxParams;
+
+ try
+ {
+ uno::Reference< rendering::XGraphicDevice > xDevice( i_rxCanvas->getDevice(),
+ uno::UNO_SET_THROW );
+
+ uno::Reference< lang::XServiceInfo > xServiceInfo( xDevice,
+ uno::UNO_QUERY_THROW );
+ uno::Reference< beans::XPropertySet > xPropSet( xDevice,
+ uno::UNO_QUERY_THROW );
+
+ o_rxParams = { uno::Any(xServiceInfo->getImplementationName()),
+ xPropSet->getPropertyValue( "DeviceHandle" ) };
+ }
+ catch( const uno::Exception& )
+ {
+ // ignore, but return empty sequence
+ }
+
+ return o_rxParams;
+ }
+
+ awt::Rectangle getAbsoluteWindowRect( const awt::Rectangle& rRect,
+ const uno::Reference< awt::XWindow2 >& xWin )
+ {
+ awt::Rectangle aRetVal( rRect );
+
+ VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWin);
+ if( pWindow )
+ {
+ ::Point aPoint( aRetVal.X,
+ aRetVal.Y );
+
+ aPoint = pWindow->OutputToScreenPixel( aPoint );
+
+ aRetVal.X = aPoint.X();
+ aRetVal.Y = aPoint.Y();
+ }
+
+ return aRetVal;
+ }
+
+ ::basegfx::B2DPolyPolygon getBoundMarksPolyPolygon( const ::basegfx::B2DRange& rRange )
+ {
+ ::basegfx::B2DPolyPolygon aPolyPoly;
+ ::basegfx::B2DPolygon aPoly;
+
+ const double nX0( rRange.getMinX() );
+ const double nY0( rRange.getMinY() );
+ const double nX1( rRange.getMaxX() );
+ const double nY1( rRange.getMaxY() );
+
+ aPoly.append( ::basegfx::B2DPoint( nX0+4,
+ nY0 ) );
+ aPoly.append( ::basegfx::B2DPoint( nX0,
+ nY0 ) );
+ aPoly.append( ::basegfx::B2DPoint( nX0,
+ nY0+4 ) );
+ aPolyPoly.append( aPoly ); aPoly.clear();
+
+ aPoly.append( ::basegfx::B2DPoint( nX1-4,
+ nY0 ) );
+ aPoly.append( ::basegfx::B2DPoint( nX1,
+ nY0 ) );
+ aPoly.append( ::basegfx::B2DPoint( nX1,
+ nY0+4 ) );
+ aPolyPoly.append( aPoly ); aPoly.clear();
+
+ aPoly.append( ::basegfx::B2DPoint( nX0+4,
+ nY1 ) );
+ aPoly.append( ::basegfx::B2DPoint( nX0,
+ nY1 ) );
+ aPoly.append( ::basegfx::B2DPoint( nX0,
+ nY1-4 ) );
+ aPolyPoly.append( aPoly ); aPoly.clear();
+
+ aPoly.append( ::basegfx::B2DPoint( nX1-4,
+ nY1 ) );
+ aPoly.append( ::basegfx::B2DPoint( nX1,
+ nY1 ) );
+ aPoly.append( ::basegfx::B2DPoint( nX1,
+ nY1-4 ) );
+ aPolyPoly.append( aPoly );
+
+ return aPolyPoly;
+ }
+
+ int calcGradientStepCount( ::basegfx::B2DHomMatrix& rTotalTransform,
+ const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState,
+ const rendering::Texture& texture,
+ int nColorSteps )
+ {
+ // calculate overall texture transformation (directly from
+ // texture to device space).
+ ::basegfx::B2DHomMatrix aMatrix;
+
+ rTotalTransform.identity();
+ ::basegfx::unotools::homMatrixFromAffineMatrix( rTotalTransform,
+ texture.AffineTransform );
+ ::canvas::tools::mergeViewAndRenderTransform(aMatrix,
+ viewState,
+ renderState);
+ rTotalTransform *= aMatrix; // prepend total view/render transformation
+
+ // determine size of gradient in device coordinate system
+ // (to e.g. determine sensible number of gradient steps)
+ ::basegfx::B2DPoint aLeftTop( 0.0, 0.0 );
+ ::basegfx::B2DPoint aLeftBottom( 0.0, 1.0 );
+ ::basegfx::B2DPoint aRightTop( 1.0, 0.0 );
+ ::basegfx::B2DPoint aRightBottom( 1.0, 1.0 );
+
+ aLeftTop *= rTotalTransform;
+ aLeftBottom *= rTotalTransform;
+ aRightTop *= rTotalTransform;
+ aRightBottom*= rTotalTransform;
+
+ // longest line in gradient bound rect
+ const int nGradientSize(
+ static_cast<int>(
+ std::max(
+ ::basegfx::B2DVector(aRightBottom-aLeftTop).getLength(),
+ ::basegfx::B2DVector(aRightTop-aLeftBottom).getLength() ) + 1.0 ) );
+
+ // typical number for pixel of the same color (strip size)
+ const int nStripSize( nGradientSize < 50 ? 2 : 4 );
+
+ // use at least three steps, and at utmost the number of color
+ // steps
+ return std::max( 3,
+ std::min(
+ nGradientSize / nStripSize,
+ nColorSteps ) );
+ }
+
+ void clipOutDev(const rendering::ViewState& viewState,
+ const rendering::RenderState& renderState,
+ OutputDevice& rOutDev,
+ OutputDevice* p2ndOutDev)
+ {
+ // accumulate non-empty clips into one region
+ vcl::Region aClipRegion(true);
+
+ if( viewState.Clip.is() )
+ {
+ ::basegfx::B2DPolyPolygon aClipPoly(
+ ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(viewState.Clip) );
+
+ if( aClipPoly.count() )
+ {
+ // setup non-empty clipping
+ ::basegfx::B2DHomMatrix aMatrix;
+ aClipPoly.transform(
+ ::basegfx::unotools::homMatrixFromAffineMatrix( aMatrix,
+ viewState.AffineTransform ) );
+
+ aClipRegion = vcl::Region::GetRegionFromPolyPolygon( ::tools::PolyPolygon( aClipPoly ) );
+ }
+ else
+ {
+ // clip polygon is empty
+ aClipRegion.SetEmpty();
+ }
+ }
+
+ if( renderState.Clip.is() )
+ {
+ ::basegfx::B2DPolyPolygon aClipPoly(
+ ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(renderState.Clip) );
+
+ ::basegfx::B2DHomMatrix aMatrix;
+ aClipPoly.transform(
+ ::canvas::tools::mergeViewAndRenderTransform( aMatrix,
+ viewState,
+ renderState ) );
+
+ if( aClipPoly.count() )
+ {
+ // setup non-empty clipping
+ vcl::Region aRegion = vcl::Region::GetRegionFromPolyPolygon( ::tools::PolyPolygon( aClipPoly ) );
+ aClipRegion.Intersect( aRegion );
+ }
+ else
+ {
+ // clip polygon is empty
+ aClipRegion.SetEmpty();
+ }
+ }
+
+ // setup accumulated clip region. Note that setting an
+ // empty clip region denotes "clip everything" on the
+ // OutputDevice (which is why we translate that into
+ // SetClipRegion() here). When both view and render clip
+ // are empty, aClipRegion remains default-constructed,
+ // i.e. empty, too.
+ if( aClipRegion.IsNull() )
+ {
+ rOutDev.SetClipRegion();
+
+ if( p2ndOutDev )
+ p2ndOutDev->SetClipRegion();
+ }
+ else
+ {
+ rOutDev.SetClipRegion( aClipRegion );
+
+ if( p2ndOutDev )
+ p2ndOutDev->SetClipRegion( aClipRegion );
+ }
+ }
+
+ void extractExtraFontProperties(const uno::Sequence<beans::PropertyValue>& rExtraFontProperties,
+ sal_uInt32 &rEmphasisMark)
+ {
+ for(const beans::PropertyValue& rPropVal : rExtraFontProperties)
+ {
+ if (rPropVal.Name == "EmphasisMark")
+ rPropVal.Value >>= rEmphasisMark;
+ }
+ }
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/elapsedtime.cxx b/canvas/source/tools/elapsedtime.cxx
new file mode 100644
index 0000000000..46167dd9a8
--- /dev/null
+++ b/canvas/source/tools/elapsedtime.cxx
@@ -0,0 +1,130 @@
+/* -*- 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 <canvas/elapsedtime.hxx>
+
+#include <tools/time.hxx>
+#include <utility>
+
+namespace canvas::tools {
+
+double ElapsedTime::getSystemTime()
+{
+ return ::tools::Time::GetMonotonicTicks() / 1.0E6;
+}
+
+ElapsedTime::ElapsedTime()
+ : m_pTimeBase(),
+ m_fLastQueriedTime( 0.0 ),
+ m_fStartTime( getSystemTime() ),
+ m_fFrozenTime( 0.0 ),
+ m_bInPauseMode( false ),
+ m_bInHoldMode( false )
+{
+}
+
+ElapsedTime::ElapsedTime(
+ std::shared_ptr<ElapsedTime> pTimeBase )
+ : m_pTimeBase(std::move( pTimeBase )),
+ m_fLastQueriedTime( 0.0 ),
+ m_fStartTime( getCurrentTime() ),
+ m_fFrozenTime( 0.0 ),
+ m_bInPauseMode( false ),
+ m_bInHoldMode( false )
+{
+}
+
+void ElapsedTime::reset()
+{
+ m_fLastQueriedTime = 0.0;
+ m_fStartTime = getCurrentTime();
+ m_fFrozenTime = 0.0;
+ m_bInPauseMode = false;
+ m_bInHoldMode = false;
+}
+
+void ElapsedTime::adjustTimer( double fOffset )
+{
+ // to make getElapsedTime() become _larger_, have to reduce
+ // m_fStartTime.
+ m_fStartTime -= fOffset;
+
+ // also adjust frozen time, this method must _always_ affect the
+ // value returned by getElapsedTime()!
+ if (m_bInHoldMode || m_bInPauseMode)
+ m_fFrozenTime += fOffset;
+}
+
+double ElapsedTime::getCurrentTime() const
+{
+ return m_pTimeBase == nullptr ? getSystemTime() : m_pTimeBase->getElapsedTimeImpl();
+}
+
+double ElapsedTime::getElapsedTime() const
+{
+ m_fLastQueriedTime = getElapsedTimeImpl();
+ return m_fLastQueriedTime;
+}
+
+double ElapsedTime::getElapsedTimeImpl() const
+{
+ if (m_bInHoldMode || m_bInPauseMode)
+ return m_fFrozenTime;
+
+ return getCurrentTime() - m_fStartTime;
+}
+
+void ElapsedTime::pauseTimer()
+{
+ m_fFrozenTime = getElapsedTimeImpl();
+ m_bInPauseMode = true;
+}
+
+void ElapsedTime::continueTimer()
+{
+ m_bInPauseMode = false;
+
+ // stop pausing, time runs again. Note that
+ // getElapsedTimeImpl() honors hold mode, i.e. a
+ // continueTimer() in hold mode will preserve the latter
+ const double fPauseDuration( getElapsedTimeImpl() - m_fFrozenTime );
+
+ // adjust start time, such that subsequent getElapsedTime() calls
+ // will virtually start from m_fFrozenTime.
+ m_fStartTime += fPauseDuration;
+}
+
+void ElapsedTime::holdTimer()
+{
+ // when called during hold mode (e.g. more than once per time
+ // object), the original hold time will be maintained.
+ m_fFrozenTime = getElapsedTimeImpl();
+ m_bInHoldMode = true;
+}
+
+void ElapsedTime::releaseTimer()
+{
+ m_bInHoldMode = false;
+}
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/page.cxx b/canvas/source/tools/page.cxx
new file mode 100644
index 0000000000..32eedb71b2
--- /dev/null
+++ b/canvas/source/tools/page.cxx
@@ -0,0 +1,133 @@
+/* -*- 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/vector/b2ivector.hxx>
+#include "page.hxx"
+
+namespace canvas
+{
+ Page::Page( const std::shared_ptr<IRenderModule> &rRenderModule ) :
+ mpRenderModule(rRenderModule),
+ mpSurface(rRenderModule->createSurface(::basegfx::B2IVector()))
+ {
+ }
+
+ void Page::validate()
+ {
+ if(!(isValid()))
+ {
+ for( const auto& rFragmentPtr : mpFragments )
+ rFragmentPtr->refresh();
+ }
+ }
+
+ bool Page::isValid() const
+ {
+ return mpSurface && mpSurface->isValid();
+ }
+
+ FragmentSharedPtr Page::allocateSpace( const ::basegfx::B2ISize& rSize )
+ {
+ SurfaceRect rect(rSize);
+ if(insert(rect))
+ {
+ FragmentSharedPtr pFragment = std::make_shared<PageFragment>(rect,this);
+ mpFragments.push_back(pFragment);
+ return pFragment;
+ }
+
+ return FragmentSharedPtr();
+ }
+
+ bool Page::nakedFragment( const FragmentSharedPtr& pFragment )
+ {
+ SurfaceRect rect(pFragment->getSize());
+ if(insert(rect))
+ {
+ pFragment->setPage(this);
+ mpFragments.push_back(pFragment);
+ return true;
+ }
+
+ return false;
+ }
+
+ void Page::free( const FragmentSharedPtr& pFragment )
+ {
+ // the fragment passes as argument is no longer
+ // dedicated to this page. either it is about to
+ // be relocated to some other page or it will
+ // currently be deleted. in either case, simply
+ // remove the reference from our internal storage.
+ std::erase(mpFragments, pFragment);
+ }
+
+ bool Page::insert( SurfaceRect& r )
+ {
+ for( const auto& pFragment : mpFragments )
+ {
+ const SurfaceRect &rect = pFragment->getRect();
+ const sal_Int32 x = rect.maPos.getX();
+ const sal_Int32 y = rect.maPos.getY();
+ // to avoid interpolation artifacts from other textures,
+ // one pixel gap between them
+ const sal_Int32 w = rect.maSize.getWidth() + 1;
+ const sal_Int32 h = rect.maSize.getHeight() + 1;
+
+ // probe location to the right
+ r.maPos.setX(x+w);
+ r.maPos.setY(y);
+ if(isValidLocation(r))
+ return true;
+
+ // probe location at bottom
+ r.maPos.setX(x);
+ r.maPos.setY(y+h);
+ if(isValidLocation(r))
+ return true;
+ }
+
+ r.maPos.setX(0);
+ r.maPos.setY(0);
+
+ return isValidLocation(r);
+ }
+
+ bool Page::isValidLocation( const SurfaceRect& r ) const
+ {
+ // the rectangle passed as argument has a valid
+ // location if and only if there's no intersection
+ // with existing areas.
+ basegfx::B2ISize aSize(mpRenderModule->getPageSize().getX(), mpRenderModule->getPageSize().getY());
+ SurfaceRect aBoundary(aSize);
+ if( !r.inside(aBoundary) )
+ return false;
+
+ for( const auto& pFragment : mpFragments )
+ {
+ if( r.intersection( pFragment->getRect() ) )
+ return false;
+ }
+
+ return true;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/page.hxx b/canvas/source/tools/page.hxx
new file mode 100644
index 0000000000..6af4bf0f6b
--- /dev/null
+++ b/canvas/source/tools/page.hxx
@@ -0,0 +1,148 @@
+/* -*- 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 <basegfx/vector/b2isize.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+#include <basegfx/range/b2irectangle.hxx>
+#include <rendering/icolorbuffer.hxx>
+#include <rendering/irendermodule.hxx>
+#include <rendering/isurface.hxx>
+
+#include <memory>
+#include <vector>
+#include "surfacerect.hxx"
+
+namespace canvas
+{
+ class PageFragment;
+
+ typedef std::shared_ptr< PageFragment > FragmentSharedPtr;
+
+ /** One page of IRenderModule-provided texture space
+ */
+ class Page
+ {
+ public:
+ explicit Page( const std::shared_ptr<IRenderModule>& rRenderModule );
+
+ FragmentSharedPtr allocateSpace( const ::basegfx::B2ISize& rSize );
+ bool nakedFragment( const FragmentSharedPtr& pFragment );
+ void free( const FragmentSharedPtr& pFragment );
+ const std::shared_ptr<ISurface>& getSurface() const { return mpSurface; }
+ bool isValid() const;
+ void validate();
+
+ private:
+ typedef std::vector<FragmentSharedPtr> FragmentContainer_t;
+
+ std::shared_ptr<IRenderModule> mpRenderModule;
+ std::shared_ptr<ISurface> mpSurface;
+ FragmentContainer_t mpFragments;
+
+ bool insert( SurfaceRect& r );
+ bool isValidLocation( const SurfaceRect& r ) const;
+ };
+
+ typedef std::shared_ptr< Page > PageSharedPtr;
+
+
+ /** A part of a page, which gets allocated to a surface
+ */
+ class PageFragment
+ {
+ public:
+ PageFragment( const SurfaceRect& r,
+ Page* pPage ) :
+ mpPage(pPage),
+ maRect(r),
+ mpBuffer(),
+ maSourceOffset()
+ {
+ }
+
+ /// Creates a 'naked' fragment.
+ explicit PageFragment( const ::basegfx::B2ISize& rSize ) :
+ mpPage(nullptr),
+ maRect(rSize),
+ mpBuffer(),
+ maSourceOffset()
+ {
+ }
+
+ bool isNaked() const { return (mpPage == nullptr); }
+ const SurfaceRect& getRect() const { return maRect; }
+ const ::basegfx::B2IPoint& getPos() const { return maRect.maPos; }
+ const ::basegfx::B2ISize& getSize() const { return maRect.maSize; }
+ void setColorBuffer( const std::shared_ptr<IColorBuffer>& pColorBuffer ) { mpBuffer=pColorBuffer; }
+ void setSourceOffset( const ::basegfx::B2IPoint& rOffset ) { maSourceOffset=rOffset; }
+ void setPage( Page* pPage ) { mpPage=pPage; }
+
+ void free( const FragmentSharedPtr& pFragment )
+ {
+ if(mpPage)
+ mpPage->free(pFragment);
+
+ mpPage=nullptr;
+ }
+
+ bool select( bool bRefresh )
+ {
+ // request was made to select this fragment,
+ // but this fragment has not been located on any
+ // of the available pages, we need to hurry now.
+ if(!mpPage)
+ return false;
+
+ std::shared_ptr<ISurface> pSurface(mpPage->getSurface());
+
+ // select this surface before wiping the contents
+ // since a specific implementation could trigger
+ // a rendering operation here...
+ if(!(pSurface->selectTexture()))
+ return false;
+
+ // call refresh() if requested, otherwise we're up to date...
+ return !bRefresh || refresh();
+ }
+
+ bool refresh()
+ {
+ if(!mpPage)
+ return false;
+
+ std::shared_ptr<ISurface> pSurface(mpPage->getSurface());
+
+ return pSurface->update( maRect.maPos,
+ ::basegfx::B2IRectangle(
+ maSourceOffset,
+ maSourceOffset + basegfx::B2IVector(maRect.maSize.getWidth(), maRect.maSize.getHeight())),
+ *mpBuffer );
+ }
+
+ private:
+ Page* mpPage;
+ SurfaceRect maRect;
+ std::shared_ptr<IColorBuffer> mpBuffer;
+ ::basegfx::B2IPoint maSourceOffset;
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/pagemanager.cxx b/canvas/source/tools/pagemanager.cxx
new file mode 100644
index 0000000000..4ee7df76ed
--- /dev/null
+++ b/canvas/source/tools/pagemanager.cxx
@@ -0,0 +1,150 @@
+/* -*- 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/vector/b2ivector.hxx>
+#include "pagemanager.hxx"
+
+namespace canvas
+{
+ FragmentSharedPtr PageManager::allocateSpace( const ::basegfx::B2ISize& rSize )
+ {
+ // we are asked to find a location for the requested size.
+ // first we try to satisfy the request from the
+ // remaining space in the existing pages.
+ for( const auto& pPage : maPages )
+ {
+ FragmentSharedPtr pFragment( pPage->allocateSpace(rSize) );
+ if(pFragment)
+ {
+ // the page created a new fragment, since we maybe want
+ // to consolidate sparse pages we keep a reference to
+ // the fragment.
+ maFragments.push_back(pFragment);
+ return pFragment;
+ }
+ }
+
+ // otherwise try to create a new page and allocate space there...
+ PageSharedPtr pPage = std::make_shared<Page>(mpRenderModule);
+ if(pPage->isValid())
+ {
+ maPages.push_back(pPage);
+ FragmentSharedPtr pFragment(pPage->allocateSpace(rSize));
+ if (pFragment)
+ maFragments.push_back(pFragment);
+ return pFragment;
+ }
+
+ // the rendermodule failed to create a new page [maybe out
+ // of videomemory], and all other pages could not take
+ // the new request. we decide to create a 'naked' fragment
+ // which will receive its location later.
+ FragmentSharedPtr pFragment = std::make_shared<PageFragment>(rSize);
+ maFragments.push_back(pFragment);
+ return pFragment;
+ }
+
+ void PageManager::free( const FragmentSharedPtr& pFragment )
+ {
+ // erase the reference to the given fragment from our
+ // internal container.
+ std::erase(maFragments, pFragment);
+
+ // let the fragment itself know about it...
+ // we need to pass 'this' as argument since the fragment
+ // needs to pass this to the page and can't create
+ // shared_ptr from itself...
+ pFragment->free(pFragment);
+ }
+
+ void PageManager::nakedFragment( const FragmentSharedPtr& pFragment )
+ {
+ if(maPages.empty())
+ return;
+
+ // okay, one last chance is left, we try all available
+ // pages again. maybe some other fragment was deleted
+ // and we can exploit the space.
+ while( !( relocate( pFragment ) ) )
+ {
+ // no way, we need to free up some space...
+ // TODO(F1): this is a heuristic, could
+ // be designed as a policy.
+ auto aEnd( maFragments.cend() );
+ auto aCurrMax( aEnd );
+ sal_uInt32 nCurrMaxArea = 0;
+ for( auto aCurr = maFragments.begin(); aCurr != aEnd; ++aCurr )
+ {
+ if( *aCurr && !( ( *aCurr )->isNaked() ) )
+ {
+ const ::basegfx::B2ISize& rSize( ( *aCurr )->getSize() );
+ sal_uInt32 nArea( rSize.getWidth() * rSize.getHeight() );
+
+ if( nCurrMaxArea < nArea )
+ {
+ aCurrMax = aCurr;
+ nCurrMaxArea = nArea;
+ }
+ }
+ }
+ // this does not erase the candidate,
+ // but makes it 'naked'...
+ if( aCurrMax != aEnd )
+ ( *aCurrMax )->free( *aCurrMax );
+ else
+ break;
+ }
+ }
+
+ bool PageManager::relocate( const FragmentSharedPtr& pFragment )
+ {
+ // the fragment passed as argument is assumed to
+ // be naked, that is it is not located on any page.
+ // we try all available pages again, maybe some
+ // other fragment was deleted and we can exploit the space.
+ for( const auto& pPage : maPages )
+ {
+ // if the page at hand takes the fragment, we immediately
+ // call select() to pull the information from the associated
+ // image to the hardware surface.
+ if( pPage->nakedFragment( pFragment ) )
+ {
+ // dirty, since newly allocated.
+ pFragment->select(true);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void PageManager::validatePages()
+ {
+ for( const auto& rPagePtr : maPages )
+ rPagePtr->validate();
+ }
+
+ ::basegfx::B2ISize PageManager::getPageSize() const
+ {
+ return { mpRenderModule->getPageSize().getX(),
+ mpRenderModule->getPageSize().getY() };
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/pagemanager.hxx b/canvas/source/tools/pagemanager.hxx
new file mode 100644
index 0000000000..57de912a12
--- /dev/null
+++ b/canvas/source/tools/pagemanager.hxx
@@ -0,0 +1,76 @@
+/* -*- 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 <basegfx/vector/b2isize.hxx>
+#include <rendering/irendermodule.hxx>
+#include <utility>
+
+#include "page.hxx"
+
+namespace canvas
+{
+ // PageManager
+ class PageManager
+ {
+ public:
+ explicit PageManager(std::shared_ptr<canvas::IRenderModule> xRenderModule)
+ : mpRenderModule(std::move(xRenderModule))
+ {
+ }
+
+ // returns the maximum size of a hardware
+ // accelerated page, e.g. OpenGL texture.
+ ::basegfx::B2ISize getPageSize() const;
+
+ const std::shared_ptr<canvas::IRenderModule>& getRenderModule() const { return mpRenderModule; }
+
+ FragmentSharedPtr allocateSpace( const ::basegfx::B2ISize& rSize );
+ void free( const FragmentSharedPtr& pFragment );
+
+ void nakedFragment( const FragmentSharedPtr& pFragment );
+
+ void validatePages();
+
+ private:
+ // the pagemanager needs access to the rendermodule
+ // since we query for system resources from it.
+ std::shared_ptr<canvas::IRenderModule> mpRenderModule;
+
+ // here we collect all fragments that will be created
+ // since we need them for relocation purposes.
+ typedef std::vector<FragmentSharedPtr> FragmentContainer_t;
+ FragmentContainer_t maFragments;
+
+ // this is the container holding all created pages,
+ // behind the scenes these are real hardware surfaces.
+ std::vector<PageSharedPtr> maPages;
+
+ bool relocate( const FragmentSharedPtr& pFragment );
+ };
+
+
+ // PageManagerSharedPtr
+
+
+ typedef std::shared_ptr< PageManager > PageManagerSharedPtr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/parametricpolypolygon.cxx b/canvas/source/tools/parametricpolypolygon.cxx
new file mode 100644
index 0000000000..8c660a5e53
--- /dev/null
+++ b/canvas/source/tools/parametricpolypolygon.cxx
@@ -0,0 +1,236 @@
+/* -*- 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/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+#include <com/sun/star/rendering/XGraphicDevice.hpp>
+
+#include <parametricpolypolygon.hxx>
+#include <utility>
+
+using namespace ::com::sun::star;
+
+namespace canvas
+{
+ uno::Sequence<OUString> ParametricPolyPolygon::getAvailableServiceNames()
+ {
+ return {"LinearGradient",
+ "EllipticalGradient",
+ "RectangularGradient"};
+ }
+
+ rtl::Reference<ParametricPolyPolygon> ParametricPolyPolygon::create(
+ const uno::Reference< rendering::XGraphicDevice >& rDevice,
+ std::u16string_view rServiceName,
+ const uno::Sequence< uno::Any >& rArgs )
+ {
+ double fAspectRatio=1.0;
+
+ // defaults
+ uno::Sequence< uno::Sequence< double > > colorSequence{
+ rDevice->getDeviceColorSpace()->convertFromRGB({ rendering::RGBColor(0,0,0) }),
+ rDevice->getDeviceColorSpace()->convertFromRGB({ rendering::RGBColor(1,1,1) })
+ };
+ uno::Sequence< double > colorStops{ 0, 1 };
+
+ // extract args
+ for( const uno::Any& rArg : rArgs )
+ {
+ beans::PropertyValue aProp;
+ if( rArg >>= aProp )
+ {
+ if ( aProp.Name == "Colors" )
+ {
+ aProp.Value >>= colorSequence;
+ }
+ else if ( aProp.Name == "Stops" )
+ {
+ aProp.Value >>= colorStops;
+ }
+ else if ( aProp.Name == "AspectRatio" )
+ {
+ aProp.Value >>= fAspectRatio;
+ }
+ }
+ }
+
+ if ( rServiceName == u"LinearGradient" )
+ {
+ return createLinearHorizontalGradient(rDevice, colorSequence, colorStops);
+ }
+ else if ( rServiceName == u"EllipticalGradient" )
+ {
+ return createEllipticalGradient(rDevice, colorSequence, colorStops, fAspectRatio);
+ }
+ else if ( rServiceName == u"RectangularGradient" )
+ {
+ return createRectangularGradient(rDevice, colorSequence, colorStops, fAspectRatio);
+ }
+ else if ( rServiceName == u"VerticalLineHatch" )
+ {
+ // TODO: NYI
+ }
+ else if ( rServiceName == u"OrthogonalLinesHatch" )
+ {
+ // TODO: NYI
+ }
+ else if ( rServiceName == u"ThreeCrossingLinesHatch" )
+ {
+ // TODO: NYI
+ }
+ else if ( rServiceName == u"FourCrossingLinesHatch" )
+ {
+ // TODO: NYI
+ }
+
+ return nullptr;
+ }
+
+ rtl::Reference<ParametricPolyPolygon> ParametricPolyPolygon::createLinearHorizontalGradient(
+ const uno::Reference< rendering::XGraphicDevice >& rDevice,
+ const uno::Sequence< uno::Sequence< double > >& colors,
+ const uno::Sequence< double >& stops )
+ {
+ // TODO(P2): hold gradient brush statically, and only setup
+ // the colors
+ return new ParametricPolyPolygon( rDevice, GradientType::Linear, colors, stops );
+ }
+
+ rtl::Reference<ParametricPolyPolygon> ParametricPolyPolygon::createEllipticalGradient(
+ const uno::Reference< rendering::XGraphicDevice >& rDevice,
+ const uno::Sequence< uno::Sequence< double > >& colors,
+ const uno::Sequence< double >& stops,
+ double fAspectRatio )
+ {
+ // TODO(P2): hold gradient polygon statically, and only setup
+ // the colors
+ return new ParametricPolyPolygon(
+ rDevice,
+ ::basegfx::utils::createPolygonFromCircle(
+ ::basegfx::B2DPoint(0,0), 1 ),
+ GradientType::Elliptical,
+ colors, stops, fAspectRatio );
+ }
+
+ rtl::Reference<ParametricPolyPolygon> ParametricPolyPolygon::createRectangularGradient( const uno::Reference< rendering::XGraphicDevice >& rDevice,
+ const uno::Sequence< uno::Sequence< double > >& colors,
+ const uno::Sequence< double >& stops,
+ double fAspectRatio )
+ {
+ // TODO(P2): hold gradient polygon statically, and only setup
+ // the colors
+ return new ParametricPolyPolygon(
+ rDevice,
+ ::basegfx::utils::createPolygonFromRect(
+ ::basegfx::B2DRectangle( -1, -1, 1, 1 ) ),
+ GradientType::Rectangular,
+ colors, stops, fAspectRatio );
+ }
+
+ void ParametricPolyPolygon::disposing(std::unique_lock<std::mutex>&)
+ {
+ mxDevice.clear();
+ }
+
+ uno::Reference< rendering::XPolyPolygon2D > SAL_CALL ParametricPolyPolygon::getOutline( double /*t*/ )
+ {
+ // TODO(F1): outline NYI
+ return uno::Reference< rendering::XPolyPolygon2D >();
+ }
+
+ uno::Sequence< double > SAL_CALL ParametricPolyPolygon::getColor( double /*t*/ )
+ {
+ // TODO(F1): color NYI
+ return uno::Sequence< double >();
+ }
+
+ uno::Sequence< double > SAL_CALL ParametricPolyPolygon::getPointColor( const geometry::RealPoint2D& /*point*/ )
+ {
+ // TODO(F1): point color NYI
+ return uno::Sequence< double >();
+ }
+
+ uno::Reference< rendering::XColorSpace > SAL_CALL ParametricPolyPolygon::getColorSpace()
+ {
+ std::unique_lock aGuard( m_aMutex );
+
+ return mxDevice.is() ? mxDevice->getDeviceColorSpace() : uno::Reference< rendering::XColorSpace >();
+ }
+
+
+ OUString SAL_CALL ParametricPolyPolygon::getImplementationName( )
+ {
+ return "Canvas::ParametricPolyPolygon";
+ }
+
+ sal_Bool SAL_CALL ParametricPolyPolygon::supportsService( const OUString& ServiceName )
+ {
+ return cppu::supportsService(this, ServiceName);
+ }
+
+ uno::Sequence< OUString > SAL_CALL ParametricPolyPolygon::getSupportedServiceNames( )
+ {
+ return { "com.sun.star.rendering.ParametricPolyPolygon" };
+ }
+
+ ParametricPolyPolygon::~ParametricPolyPolygon()
+ {
+ }
+
+ ParametricPolyPolygon::ParametricPolyPolygon( uno::Reference< rendering::XGraphicDevice > xDevice,
+ const ::basegfx::B2DPolygon& rGradientPoly,
+ GradientType eType,
+ const uno::Sequence< uno::Sequence< double > >& rColors,
+ const uno::Sequence< double >& rStops,
+ double nAspectRatio ) :
+ mxDevice(std::move( xDevice )),
+ maValues( rGradientPoly,
+ rColors,
+ rStops,
+ nAspectRatio,
+ eType )
+ {
+ }
+
+ ParametricPolyPolygon::ParametricPolyPolygon( uno::Reference< rendering::XGraphicDevice > xDevice,
+ GradientType eType,
+ const uno::Sequence< uno::Sequence< double > >& rColors,
+ const uno::Sequence< double >& rStops ) :
+ mxDevice(std::move( xDevice )),
+ maValues( ::basegfx::B2DPolygon(),
+ rColors,
+ rStops,
+ 1.0,
+ eType )
+ {
+ }
+
+ ParametricPolyPolygon::Values ParametricPolyPolygon::getValues() const
+ {
+ return maValues;
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/propertysethelper.cxx b/canvas/source/tools/propertysethelper.cxx
new file mode 100644
index 0000000000..24a07cb7dc
--- /dev/null
+++ b/canvas/source/tools/propertysethelper.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 <string_view>
+
+#include <propertysethelper.hxx>
+#include <com/sun/star/beans/PropertyVetoException.hpp>
+#include <com/sun/star/beans/UnknownPropertyException.hpp>
+
+using namespace ::com::sun::star;
+
+namespace canvas
+{
+ namespace
+ {
+ void throwUnknown( std::u16string_view aPropertyName )
+ {
+ throw beans::UnknownPropertyException(
+ OUString::Concat("PropertySetHelper: property ") +
+ aPropertyName + " not found."
+ );
+ }
+
+ void throwVeto( std::u16string_view aPropertyName )
+ {
+ throw beans::PropertyVetoException(
+ OUString::Concat("PropertySetHelper: property ") +
+ aPropertyName + " access was vetoed." );
+ }
+
+ struct EntryComparator
+ {
+ bool operator()( const PropertySetHelper::MapType::MapEntry& rLHS,
+ const PropertySetHelper::MapType::MapEntry& rRHS )
+ {
+ return strcmp( rLHS.maKey,
+ rRHS.maKey ) < 0;
+ }
+ };
+ }
+
+ PropertySetHelper::PropertySetHelper()
+ {
+ }
+
+ void PropertySetHelper::initProperties( InputMap&& rMap )
+ {
+ mpMap.reset();
+ maMapEntries = std::move(rMap);
+
+ std::sort( maMapEntries.begin(),
+ maMapEntries.end(),
+ EntryComparator() );
+
+ if( !maMapEntries.empty() )
+ mpMap.reset( new MapType(maMapEntries.data(),
+ maMapEntries.size(),
+ true) );
+ }
+
+ void PropertySetHelper::addProperties( const InputMap& rMap )
+ {
+ InputMap aMerged( maMapEntries );
+ aMerged.insert( aMerged.end(),
+ rMap.begin(),
+ rMap.end() );
+
+ initProperties( std::move(aMerged) );
+ }
+
+ bool PropertySetHelper::isPropertyName( const OUString& aPropertyName ) const
+ {
+ if (!mpMap)
+ return false;
+
+ Callbacks aDummy;
+ return mpMap->lookup( aPropertyName,
+ aDummy );
+ }
+
+ uno::Reference< beans::XPropertySetInfo > PropertySetHelper::getPropertySetInfo() const
+ {
+ // we're a stealth property set
+ return uno::Reference< beans::XPropertySetInfo >();
+ }
+
+ void PropertySetHelper::setPropertyValue( const OUString& aPropertyName,
+ const uno::Any& aValue )
+ {
+ Callbacks aCallbacks;
+ if (!mpMap || !mpMap->lookup(aPropertyName, aCallbacks))
+ {
+ throwUnknown( aPropertyName );
+ }
+
+ if (!aCallbacks.setter)
+ throwVeto( aPropertyName );
+
+ aCallbacks.setter(aValue);
+ }
+
+ uno::Any PropertySetHelper::getPropertyValue( const OUString& aPropertyName ) const
+ {
+ Callbacks aCallbacks;
+ if (!mpMap || !mpMap->lookup(aPropertyName, aCallbacks))
+ {
+ throwUnknown( aPropertyName );
+ }
+
+ if (aCallbacks.getter)
+ return aCallbacks.getter();
+
+ // TODO(Q1): subtlety, empty getter method silently returns
+ // the empty any
+ return uno::Any();
+ }
+
+ void PropertySetHelper::addPropertyChangeListener( const OUString& aPropertyName,
+ const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ )
+ {
+ // check validity of property, but otherwise ignore the
+ // request
+ if( !isPropertyName( aPropertyName ) )
+ throwUnknown( aPropertyName );
+ }
+
+ void PropertySetHelper::addVetoableChangeListener( const OUString& aPropertyName,
+ const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/ )
+ {
+ // check validity of property, but otherwise ignore the
+ // request
+ if( !isPropertyName( aPropertyName ) )
+ throwUnknown( aPropertyName );
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/spriteredrawmanager.cxx b/canvas/source/tools/spriteredrawmanager.cxx
new file mode 100644
index 0000000000..06eb6d1de5
--- /dev/null
+++ b/canvas/source/tools/spriteredrawmanager.cxx
@@ -0,0 +1,483 @@
+/* -*- 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 <algorithm>
+
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/utils/canvastools.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <sal/log.hxx>
+
+#include <spriteredrawmanager.hxx>
+#include <boost/range/adaptor/reversed.hpp>
+#include <utility>
+
+namespace canvas
+{
+ namespace
+ {
+ /** Helper class to condense sprite updates into a single action
+
+ This class tracks the sprite changes over the recorded
+ change list, and generates a single update action from
+ that (note that per screen update, several moves,
+ visibility changes and content updates might happen)
+ */
+ class SpriteTracer
+ {
+ public:
+ explicit SpriteTracer( Sprite::Reference rAffectedSprite ) :
+ mpAffectedSprite(std::move(rAffectedSprite)),
+ mbIsMove( false ),
+ mbIsGenericUpdate( false )
+ {
+ }
+
+ void operator()( const SpriteRedrawManager::SpriteChangeRecord& rSpriteRecord )
+ {
+ // only deal with change events from the currently
+ // affected sprite
+ if( rSpriteRecord.mpAffectedSprite != mpAffectedSprite )
+ return;
+
+ switch( rSpriteRecord.meChangeType )
+ {
+ case SpriteRedrawManager::SpriteChangeRecord::ChangeType::move:
+ if( !mbIsMove )
+ {
+ // no move yet - this must be the first one
+ maMoveStartArea = ::basegfx::B2DRectangle(
+ rSpriteRecord.maOldPos,
+ rSpriteRecord.maOldPos + rSpriteRecord.maUpdateArea.getRange() );
+ mbIsMove = true;
+ }
+
+ maMoveEndArea = rSpriteRecord.maUpdateArea;
+ break;
+
+ case SpriteRedrawManager::SpriteChangeRecord::ChangeType::update:
+ // update end update area of the
+ // sprite. Thus, every update() action
+ // _after_ the last move will correctly
+ // update the final repaint area. And this
+ // does not interfere with subsequent
+ // moves, because moves always perform a
+ // hard set of maMoveEndArea to their
+ // stored value
+ maMoveEndArea.expand( rSpriteRecord.maUpdateArea );
+ mbIsGenericUpdate = true;
+ break;
+
+ default:
+ ENSURE_OR_THROW( false,
+ "Unexpected case in SpriteUpdater::operator()" );
+ break;
+ }
+ }
+
+ void commit( SpriteRedrawManager::SpriteConnectedRanges& rUpdateCollector ) const
+ {
+ if( mbIsMove )
+ {
+ if( !maMoveStartArea.isEmpty() ||
+ !maMoveEndArea.isEmpty() )
+ {
+ // if mbIsGenericUpdate is false, this is a
+ // pure move (i.e. no other update
+ // operations). Pass that information on to
+ // the SpriteInfo
+ const bool bIsPureMove( !mbIsGenericUpdate );
+
+ // ignore the case that start and end update
+ // area overlap - the b2dconnectedranges
+ // handle that, anyway. doing it this way
+ // ensures that we have both old and new area
+ // stored
+
+ // round all given range up to enclosing
+ // integer rectangle - since the whole thing
+ // here is about
+
+ // first, draw the new sprite position
+ rUpdateCollector.addRange(
+ ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ),
+ SpriteRedrawManager::SpriteInfo(
+ mpAffectedSprite,
+ maMoveEndArea,
+ true,
+ bIsPureMove ) );
+
+ // then, clear the old place (looks smoother
+ // this way)
+ rUpdateCollector.addRange(
+ ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveStartArea ),
+ SpriteRedrawManager::SpriteInfo(
+ Sprite::Reference(),
+ maMoveStartArea,
+ true,
+ bIsPureMove ) );
+ }
+ }
+ else if( mbIsGenericUpdate &&
+ !maMoveEndArea.isEmpty() )
+ {
+ rUpdateCollector.addRange(
+ ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ),
+ SpriteRedrawManager::SpriteInfo(
+ mpAffectedSprite,
+ maMoveEndArea,
+ true ) );
+ }
+ }
+
+ private:
+ Sprite::Reference mpAffectedSprite;
+ ::basegfx::B2DRectangle maMoveStartArea;
+ ::basegfx::B2DRectangle maMoveEndArea;
+
+ /// True, if at least one move was encountered
+ bool mbIsMove;
+
+ /// True, if at least one generic update was encountered
+ bool mbIsGenericUpdate;
+ };
+
+
+ /** SpriteChecker functor, which for every sprite checks the
+ given update vector for necessary screen updates
+ */
+ class SpriteUpdater
+ {
+ public:
+ /** Generate update area list
+
+ @param rUpdater
+ Reference to an updater object, which will receive the
+ update areas.
+
+ @param rChangeContainer
+ Container with all sprite change requests
+
+ */
+ SpriteUpdater( SpriteRedrawManager::SpriteConnectedRanges& rUpdater,
+ const SpriteRedrawManager::VectorOfChangeRecords& rChangeContainer ) :
+ mrUpdater( rUpdater ),
+ mrChangeContainer( rChangeContainer )
+ {
+ }
+
+ /** Call this method for every sprite on your screen
+
+ This method scans the change container, collecting all
+ update info for the given sprite into one or two
+ update operations, which in turn are inserted into the
+ connected ranges processor.
+
+ @param rSprite
+ Current sprite to collect update info for.
+ */
+ void operator()( const Sprite::Reference& rSprite )
+ {
+ SpriteTracer aSpriteTracer( rSprite );
+
+ for (auto const& aChange : mrChangeContainer)
+ aSpriteTracer( aChange );
+
+ aSpriteTracer.commit( mrUpdater );
+ }
+
+ private:
+ SpriteRedrawManager::SpriteConnectedRanges& mrUpdater;
+ const SpriteRedrawManager::VectorOfChangeRecords& mrChangeContainer;
+ };
+ }
+
+ void SpriteRedrawManager::setupUpdateAreas( SpriteConnectedRanges& rUpdateAreas ) const
+ {
+ // TODO(T3): This is NOT thread safe at all. This only works
+ // under the assumption that NOBODY changes ANYTHING
+ // concurrently, while this method is on the stack. We should
+ // really rework the canvas::Sprite interface, in such a way
+ // that it dumps ALL its state with a single, atomic
+ // call. Then, we store that state locally. This prolly goes
+ // in line with the problem of having sprite state available
+ // for the frame before the last frame; plus, it avoids
+ // frequent locks of the object mutexes
+ SpriteWeakOrder aSpriteComparator;
+
+ // put all sprites that have changed content into update areas
+ for( const auto& pSprite : maSprites )
+ {
+ if( pSprite->isContentChanged() )
+ const_cast< SpriteRedrawManager* >( this )->updateSprite( pSprite,
+ pSprite->getPosPixel(),
+ pSprite->getUpdateArea() );
+ }
+
+ // sort sprites after prio
+ VectorOfSprites aSortedSpriteVector( maSprites.begin(), maSprites.end() );
+ std::sort( aSortedSpriteVector.begin(),
+ aSortedSpriteVector.end(),
+ aSpriteComparator );
+
+ // extract all referenced sprites from the maChangeRecords
+ // (copy sprites, make the list unique, regarding the
+ // sprite pointer). This assumes that, until this scope
+ // ends, nobody changes the maChangeRecords vector!
+ VectorOfSprites aUpdatableSprites;
+ for( const auto& rChangeRecord : maChangeRecords )
+ {
+ const Sprite::Reference& rSprite( rChangeRecord.getSprite() );
+ if( rSprite.is() )
+ aUpdatableSprites.push_back( rSprite );
+ }
+
+ std::sort( aUpdatableSprites.begin(),
+ aUpdatableSprites.end(),
+ aSpriteComparator );
+
+ VectorOfSprites::iterator aEnd=
+ std::unique( aUpdatableSprites.begin(),
+ aUpdatableSprites.end() );
+
+ // for each unique sprite, check the change event vector,
+ // calculate the update operation from that, and add the
+ // result to the aUpdateArea.
+ std::for_each( aUpdatableSprites.begin(),
+ aEnd,
+ SpriteUpdater( rUpdateAreas,
+ maChangeRecords) );
+
+ // TODO(P2): Implement your own output iterator adapter, to
+ // avoid that totally superfluous temp aUnchangedSprites
+ // vector.
+
+ // add all sprites to rUpdateAreas, that are _not_ already
+ // contained in the uniquified vector of changed ones
+ // (i.e. the difference between aSortedSpriteVector and
+ // aUpdatableSprites).
+ VectorOfSprites aUnchangedSprites;
+ std::set_difference( aSortedSpriteVector.begin(),
+ aSortedSpriteVector.end(),
+ aUpdatableSprites.begin(),
+ aEnd,
+ std::back_insert_iterator< VectorOfSprites >(aUnchangedSprites),
+ aSpriteComparator );
+
+ // add each remaining unchanged sprite to connected ranges,
+ // marked as "don't need update"
+ for( const auto& pUnchangedSprite : aUnchangedSprites )
+ {
+ const ::basegfx::B2DRange& rUpdateArea( pUnchangedSprite->getUpdateArea() );
+ rUpdateAreas.addRange(
+ ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( rUpdateArea ),
+ SpriteInfo( pUnchangedSprite,
+ rUpdateArea,
+ false ) );
+ }
+ }
+
+#if OSL_DEBUG_LEVEL > 0
+ static bool impIsEqualB2DRange(const basegfx::B2DRange& rRangeA, const basegfx::B2DRange& rRangeB, double fSmallValue)
+ {
+ return fabs(rRangeB.getMinX() - rRangeA.getMinX()) <= fSmallValue
+ && fabs(rRangeB.getMinY() - rRangeA.getMinY()) <= fSmallValue
+ && fabs(rRangeB.getMaxX() - rRangeA.getMaxX()) <= fSmallValue
+ && fabs(rRangeB.getMaxY() - rRangeA.getMaxY()) <= fSmallValue;
+ }
+
+ static bool impIsEqualB2DVector(const basegfx::B2DVector& rVecA, const basegfx::B2DVector& rVecB, double fSmallValue)
+ {
+ return fabs(rVecB.getX() - rVecA.getX()) <= fSmallValue
+ && fabs(rVecB.getY() - rVecA.getY()) <= fSmallValue;
+ }
+#endif
+
+ bool SpriteRedrawManager::isAreaUpdateScroll( ::basegfx::B2DRectangle& o_rMoveStart,
+ ::basegfx::B2DRectangle& o_rMoveEnd,
+ const UpdateArea& rUpdateArea,
+ std::size_t nNumSprites ) const
+ {
+ // check for a solitary move, which consists of exactly two
+ // pure-move entries, the first with valid, the second with
+ // invalid sprite (see SpriteTracer::commit()). Note that we
+ // cannot simply store some flag in SpriteTracer::commit()
+ // above and just check that here, since during the connected
+ // range calculations, other sprites might get merged into the
+ // same region (thus spoiling the scrolling move
+ // optimization).
+ if( nNumSprites != 2 )
+ return false;
+
+ const SpriteConnectedRanges::ComponentListType::const_iterator aFirst(
+ rUpdateArea.maComponentList.begin() );
+ SpriteConnectedRanges::ComponentListType::const_iterator aSecond(
+ aFirst );
+ ++aSecond;
+
+ if( !aFirst->second.isPureMove() ||
+ !aSecond->second.isPureMove() ||
+ !aFirst->second.getSprite().is() ||
+ // use _true_ update area, not the rounded version
+ !aFirst->second.getSprite()->isAreaUpdateOpaque( aFirst->second.getUpdateArea() ) ||
+ aSecond->second.getSprite().is() )
+ {
+ // either no move update, or incorrect sprite, or sprite
+ // content not fully opaque over update region.
+ return false;
+ }
+
+ o_rMoveStart = aSecond->second.getUpdateArea();
+ o_rMoveEnd = aFirst->second.getUpdateArea();
+
+#if OSL_DEBUG_LEVEL > 0
+ ::basegfx::B2DRectangle aTotalBounds( o_rMoveStart );
+ aTotalBounds.expand( o_rMoveEnd );
+
+ SAL_WARN_IF(!impIsEqualB2DRange(rUpdateArea.maTotalBounds, basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange(aTotalBounds), 0.5),
+ "canvas",
+ "SpriteRedrawManager::isAreaUpdateScroll(): sprite area and total area mismatch");
+ SAL_WARN_IF(!impIsEqualB2DVector(o_rMoveStart.getRange(), o_rMoveEnd.getRange(), 0.5),
+ "canvas",
+ "SpriteRedrawManager::isAreaUpdateScroll(): scroll start and end area have mismatching size");
+#endif
+
+ return true;
+ }
+
+ bool SpriteRedrawManager::isAreaUpdateNotOpaque( const ::basegfx::B2DRectangle& rUpdateRect,
+ const AreaComponent& rComponent ) const
+ {
+ const Sprite::Reference& pAffectedSprite( rComponent.second.getSprite() );
+
+ if( !pAffectedSprite.is() )
+ return true; // no sprite, no opaque update!
+
+ return !pAffectedSprite->isAreaUpdateOpaque( rUpdateRect );
+ }
+
+ bool SpriteRedrawManager::isAreaUpdateOpaque( const UpdateArea& rUpdateArea,
+ std::size_t nNumSprites ) const
+ {
+ // check whether the sprites in the update area's list will
+ // fully cover the given area _and_ do that in an opaque way
+ // (i.e. no alpha, no non-rectangular sprite content).
+
+ // TODO(P1): Come up with a smarter early-exit criterion here
+ // (though, I think, the case that _lots_ of sprites _fully_
+ // cover a rectangular area _without_ any holes is extremely
+ // improbable)
+
+ // avoid checking large number of sprites (and probably fail,
+ // anyway). Note: the case nNumSprites < 1 should normally not
+ // happen, as handleArea() calls backgroundPaint() then.
+ if( nNumSprites > 3 || nNumSprites < 1 )
+ return false;
+
+ // now, calc the _true_ update area, by merging all sprite's
+ // true update areas into one rectangle
+ ::basegfx::B2DRange aTrueArea( rUpdateArea.maComponentList.begin()->second.getUpdateArea() );
+ for( const auto& rArea : rUpdateArea.maComponentList )
+ aTrueArea.expand(rArea.second.getUpdateArea());
+
+ const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
+ rUpdateArea.maComponentList.end() );
+
+ // and check whether _any_ of the sprites tells that its area
+ // update will not be opaque.
+ return std::none_of( rUpdateArea.maComponentList.begin(),
+ aEnd,
+ [&aTrueArea, this]( const std::pair< ::basegfx::B2DRange, SpriteInfo >& cp )
+ { return this->isAreaUpdateNotOpaque(aTrueArea, cp); } );
+ }
+
+ bool SpriteRedrawManager::areSpritesChanged( const UpdateArea& rUpdateArea ) const
+ {
+ // check whether SpriteInfo::needsUpdate returns false for
+ // all elements of this area's contained sprites
+
+ // if not a single changed sprite found - just ignore this
+ // component (return false)
+ const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
+ rUpdateArea.maComponentList.end() );
+ return std::any_of( rUpdateArea.maComponentList.begin(),
+ aEnd,
+ []( const std::pair< ::basegfx::B2DRange, SpriteInfo >& cp )
+ { return cp.second.needsUpdate(); } );
+ }
+
+ SpriteRedrawManager::SpriteRedrawManager()
+ {
+ }
+
+ void SpriteRedrawManager::disposing()
+ {
+ // drop all references
+ maChangeRecords.clear();
+
+ // dispose all sprites - the spritecanvas, and by delegation,
+ // this object, is the owner of the sprites. After all, a
+ // sprite without a canvas to render into makes not terribly
+ // much sense.
+ for( const auto& rCurr : boost::adaptors::reverse(maSprites) )
+ rCurr->dispose();
+
+ maSprites.clear();
+ }
+
+ void SpriteRedrawManager::clearChangeRecords()
+ {
+ maChangeRecords.clear();
+ }
+
+ void SpriteRedrawManager::showSprite( const Sprite::Reference& rSprite )
+ {
+ maSprites.push_back( rSprite );
+ }
+
+ void SpriteRedrawManager::hideSprite( const Sprite::Reference& rSprite )
+ {
+ std::erase(maSprites, rSprite);
+ }
+
+ void SpriteRedrawManager::moveSprite( const Sprite::Reference& rSprite,
+ const ::basegfx::B2DPoint& rOldPos,
+ const ::basegfx::B2DPoint& rNewPos,
+ const ::basegfx::B2DVector& rSpriteSize )
+ {
+ maChangeRecords.emplace_back( rSprite,
+ rOldPos,
+ rNewPos,
+ rSpriteSize );
+ }
+
+ void SpriteRedrawManager::updateSprite( const Sprite::Reference& rSprite,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DRange& rUpdateArea )
+ {
+ maChangeRecords.emplace_back( rSprite,
+ rPos,
+ rUpdateArea );
+ }
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/surface.cxx b/canvas/source/tools/surface.cxx
new file mode 100644
index 0000000000..88d20bbcfe
--- /dev/null
+++ b/canvas/source/tools/surface.cxx
@@ -0,0 +1,437 @@
+/* -*- 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/matrix/b2dhommatrixtools.hxx>
+#include <basegfx/polygon/b2dpolygonclipper.hxx>
+#include <comphelper/scopeguard.hxx>
+#include <utility>
+
+#include "surface.hxx"
+
+namespace canvas
+{
+ Surface::Surface( PageManagerSharedPtr rPageManager,
+ std::shared_ptr<IColorBuffer> xColorBuffer,
+ const ::basegfx::B2IPoint& rPos,
+ const ::basegfx::B2ISize& rSize ) :
+ mpColorBuffer(std::move(xColorBuffer)),
+ mpPageManager(std::move(rPageManager)),
+ maSourceOffset(rPos),
+ maSize(rSize),
+ mbIsDirty(true)
+ {
+ }
+
+ Surface::~Surface()
+ {
+ if(mpFragment)
+ mpPageManager->free(mpFragment);
+ }
+
+ void Surface::setColorBufferDirty()
+ {
+ mbIsDirty=true;
+ }
+
+ basegfx::B2DRectangle Surface::getUVCoords() const
+ {
+ ::basegfx::B2ISize aPageSize(mpPageManager->getPageSize());
+ ::basegfx::B2IPoint aDestOffset;
+ if( mpFragment )
+ aDestOffset = mpFragment->getPos();
+
+ const double pw( aPageSize.getWidth() );
+ const double ph( aPageSize.getHeight() );
+ const double ox( aDestOffset.getX() );
+ const double oy( aDestOffset.getY() );
+ const double sx( maSize.getWidth() );
+ const double sy( maSize.getHeight() );
+
+ return ::basegfx::B2DRectangle( ox/pw,
+ oy/ph,
+ (ox+sx)/pw,
+ (oy+sy)/ph );
+ }
+
+ basegfx::B2DRectangle Surface::getUVCoords( const ::basegfx::B2IPoint& rPos,
+ const ::basegfx::B2ISize& rSize ) const
+ {
+ ::basegfx::B2ISize aPageSize(mpPageManager->getPageSize());
+
+ const double pw( aPageSize.getWidth() );
+ const double ph( aPageSize.getHeight() );
+ const double ox( rPos.getX() );
+ const double oy( rPos.getY() );
+ const double sx( rSize.getWidth() );
+ const double sy( rSize.getHeight() );
+
+ return ::basegfx::B2DRectangle( ox/pw,
+ oy/ph,
+ (ox+sx)/pw,
+ (oy+sy)/ph );
+ }
+
+ bool Surface::draw( double fAlpha,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DHomMatrix& rTransform )
+ {
+ std::shared_ptr<IRenderModule> pRenderModule(mpPageManager->getRenderModule());
+
+ RenderModuleGuard aGuard( pRenderModule );
+
+ prepareRendering();
+
+ // convert size to normalized device coordinates
+ const ::basegfx::B2DRectangle& rUV( getUVCoords() );
+
+ const double u1(rUV.getMinX());
+ const double v1(rUV.getMinY());
+ const double u2(rUV.getMaxX());
+ const double v2(rUV.getMaxY());
+
+ // concat transforms
+ // 1) offset of surface subarea
+ // 2) surface transform
+ // 3) translation to output position [rPos]
+ // 4) scale to normalized device coordinates
+ // 5) flip y-axis
+ // 6) translate to account for viewport transform
+ basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix(
+ maSourceOffset.getX(), maSourceOffset.getY()));
+ aTransform = aTransform * rTransform;
+ aTransform.translate(::basegfx::fround(rPos.getX()),
+ ::basegfx::fround(rPos.getY()));
+
+ /*
+ ######################################
+ ######################################
+ ######################################
+
+ Y
+ ^+1
+ |
+ 2 | 3
+ x------------x
+ | | |
+ | | |
+ ------|-----O------|------>X
+ -1 | | | +1
+ | | |
+ x------------x
+ 1 | 0
+ |
+ |-1
+
+ ######################################
+ ######################################
+ ######################################
+ */
+
+ const ::basegfx::B2DPoint& p0(aTransform * ::basegfx::B2DPoint(maSize.getWidth(),maSize.getHeight()));
+ const ::basegfx::B2DPoint& p1(aTransform * ::basegfx::B2DPoint(0.0,maSize.getHeight()));
+ const ::basegfx::B2DPoint& p2(aTransform * ::basegfx::B2DPoint(0.0,0.0));
+ const ::basegfx::B2DPoint& p3(aTransform * ::basegfx::B2DPoint(maSize.getWidth(),0.0));
+
+ canvas::Vertex vertex;
+ vertex.r = 1.0f;
+ vertex.g = 1.0f;
+ vertex.b = 1.0f;
+ vertex.a = static_cast<float>(fAlpha);
+ vertex.z = 0.0f;
+
+ {
+ pRenderModule->beginPrimitive( canvas::IRenderModule::PrimitiveType::Quad );
+
+ // issue an endPrimitive() when leaving the scope
+ const ::comphelper::ScopeGuard aScopeGuard(
+ [&pRenderModule]() mutable { pRenderModule->endPrimitive(); } );
+
+ vertex.u=static_cast<float>(u2); vertex.v=static_cast<float>(v2);
+ vertex.x=static_cast<float>(p0.getX()); vertex.y=static_cast<float>(p0.getY());
+ pRenderModule->pushVertex(vertex);
+
+ vertex.u=static_cast<float>(u1); vertex.v=static_cast<float>(v2);
+ vertex.x=static_cast<float>(p1.getX()); vertex.y=static_cast<float>(p1.getY());
+ pRenderModule->pushVertex(vertex);
+
+ vertex.u=static_cast<float>(u1); vertex.v=static_cast<float>(v1);
+ vertex.x=static_cast<float>(p2.getX()); vertex.y=static_cast<float>(p2.getY());
+ pRenderModule->pushVertex(vertex);
+
+ vertex.u=static_cast<float>(u2); vertex.v=static_cast<float>(v1);
+ vertex.x=static_cast<float>(p3.getX()); vertex.y=static_cast<float>(p3.getY());
+ pRenderModule->pushVertex(vertex);
+ }
+
+ return !(pRenderModule->isError());
+ }
+
+ bool Surface::drawRectangularArea(
+ double fAlpha,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DRectangle& rArea,
+ const ::basegfx::B2DHomMatrix& rTransform )
+ {
+ if( rArea.isEmpty() )
+ return true; // immediate exit for empty area
+
+ std::shared_ptr<IRenderModule> pRenderModule(mpPageManager->getRenderModule());
+
+ RenderModuleGuard aGuard( pRenderModule );
+
+ prepareRendering();
+
+ // these positions are relative to the texture
+ ::basegfx::B2IPoint aPos1(
+ ::basegfx::fround(rArea.getMinimum().getX()),
+ ::basegfx::fround(rArea.getMinimum().getY()));
+ ::basegfx::B2IPoint aPos2(
+ ::basegfx::fround(rArea.getMaximum().getX()),
+ ::basegfx::fround(rArea.getMaximum().getY()) );
+
+ // clip the positions to the area this surface covers
+ aPos1.setX(std::max(aPos1.getX(), maSourceOffset.getX()));
+ aPos1.setY(std::max(aPos1.getY(), maSourceOffset.getY()));
+ aPos2.setX(std::min(aPos2.getX(), maSourceOffset.getX() + maSize.getWidth()));
+ aPos2.setY(std::min(aPos2.getY(), maSourceOffset.getY() + maSize.getHeight()));
+
+ // if the resulting area is empty, return immediately
+ ::basegfx::B2IVector aSize(aPos2 - aPos1);
+ if(aSize.getX() <= 0 || aSize.getY() <= 0)
+ return true;
+
+ ::basegfx::B2IPoint aDestOffset;
+ if( mpFragment )
+ aDestOffset = mpFragment->getPos();
+
+ // convert size to normalized device coordinates
+ const ::basegfx::B2DRectangle& rUV(
+ getUVCoords(aPos1 - maSourceOffset + aDestOffset,
+ basegfx::B2ISize(aSize.getX(), aSize.getY())) );
+ const double u1(rUV.getMinX());
+ const double v1(rUV.getMinY());
+ const double u2(rUV.getMaxX());
+ const double v2(rUV.getMaxY());
+
+ // concatenate transforms
+ // 1) offset of surface subarea
+ // 2) surface transform
+ // 3) translation to output position [rPos]
+ basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix(aPos1.getX(), aPos1.getY()));
+ aTransform = aTransform * rTransform;
+ aTransform.translate(::basegfx::fround(rPos.getX()),
+ ::basegfx::fround(rPos.getY()));
+
+
+ /*
+ ######################################
+ ######################################
+ ######################################
+
+ Y
+ ^+1
+ |
+ 2 | 3
+ x------------x
+ | | |
+ | | |
+ ------|-----O------|------>X
+ -1 | | | +1
+ | | |
+ x------------x
+ 1 | 0
+ |
+ |-1
+
+ ######################################
+ ######################################
+ ######################################
+ */
+
+ const ::basegfx::B2DPoint& p0(aTransform * ::basegfx::B2DPoint(aSize.getX(),aSize.getY()));
+ const ::basegfx::B2DPoint& p1(aTransform * ::basegfx::B2DPoint(0.0, aSize.getY()));
+ const ::basegfx::B2DPoint& p2(aTransform * ::basegfx::B2DPoint(0.0, 0.0));
+ const ::basegfx::B2DPoint& p3(aTransform * ::basegfx::B2DPoint(aSize.getX(),0.0));
+
+ canvas::Vertex vertex;
+ vertex.r = 1.0f;
+ vertex.g = 1.0f;
+ vertex.b = 1.0f;
+ vertex.a = static_cast<float>(fAlpha);
+ vertex.z = 0.0f;
+
+ {
+ pRenderModule->beginPrimitive( canvas::IRenderModule::PrimitiveType::Quad );
+
+ // issue an endPrimitive() when leaving the scope
+ const ::comphelper::ScopeGuard aScopeGuard(
+ [&pRenderModule]() mutable { pRenderModule->endPrimitive(); } );
+
+ vertex.u=static_cast<float>(u2); vertex.v=static_cast<float>(v2);
+ vertex.x=static_cast<float>(p0.getX()); vertex.y=static_cast<float>(p0.getY());
+ pRenderModule->pushVertex(vertex);
+
+ vertex.u=static_cast<float>(u1); vertex.v=static_cast<float>(v2);
+ vertex.x=static_cast<float>(p1.getX()); vertex.y=static_cast<float>(p1.getY());
+ pRenderModule->pushVertex(vertex);
+
+ vertex.u=static_cast<float>(u1); vertex.v=static_cast<float>(v1);
+ vertex.x=static_cast<float>(p2.getX()); vertex.y=static_cast<float>(p2.getY());
+ pRenderModule->pushVertex(vertex);
+
+ vertex.u=static_cast<float>(u2); vertex.v=static_cast<float>(v1);
+ vertex.x=static_cast<float>(p3.getX()); vertex.y=static_cast<float>(p3.getY());
+ pRenderModule->pushVertex(vertex);
+ }
+
+ return !(pRenderModule->isError());
+ }
+
+ bool Surface::drawWithClip( double fAlpha,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DPolygon& rClipPoly,
+ const ::basegfx::B2DHomMatrix& rTransform )
+ {
+ std::shared_ptr<IRenderModule> pRenderModule(mpPageManager->getRenderModule());
+
+ RenderModuleGuard aGuard( pRenderModule );
+
+ prepareRendering();
+
+ // untransformed surface rectangle, relative to the whole
+ // image (note: this surface might actually only be a tile of
+ // the whole image, with non-zero maSourceOffset)
+ const double x1(maSourceOffset.getX());
+ const double y1(maSourceOffset.getY());
+ const double w(maSize.getWidth());
+ const double h(maSize.getHeight());
+ const double x2(x1+w);
+ const double y2(y1+h);
+ const ::basegfx::B2DRectangle aSurfaceClipRect(x1,y1,x2,y2);
+
+ // concatenate transforms
+ // we use 'fround' here to avoid rounding errors. the vertices will
+ // be transformed by the overall transform and uv coordinates will
+ // be calculated from the result, and this is why we need to use
+ // integer coordinates here...
+ basegfx::B2DHomMatrix aTransform = rTransform;
+ aTransform.translate(::basegfx::fround(rPos.getX()),
+ ::basegfx::fround(rPos.getY()));
+
+ /*
+ ######################################
+ ######################################
+ ######################################
+
+ Y
+ ^+1
+ |
+ 2 | 3
+ x------------x
+ | | |
+ | | |
+ ------|-----O------|------>X
+ -1 | | | +1
+ | | |
+ x------------x
+ 1 | 0
+ |
+ |-1
+
+ ######################################
+ ######################################
+ ######################################
+ */
+
+ // uv coordinates that map the surface rectangle
+ // to the destination rectangle.
+ const ::basegfx::B2DRectangle& rUV( getUVCoords() );
+
+ basegfx::B2DPolygon rTriangleList(basegfx::utils::clipTriangleListOnRange(rClipPoly,
+ aSurfaceClipRect));
+
+ // Push vertices to backend renderer
+ if(const sal_uInt32 nVertexCount = rTriangleList.count())
+ {
+ canvas::Vertex vertex;
+ vertex.r = 1.0f;
+ vertex.g = 1.0f;
+ vertex.b = 1.0f;
+ vertex.a = static_cast<float>(fAlpha);
+ vertex.z = 0.0f;
+
+ pRenderModule->beginPrimitive( canvas::IRenderModule::PrimitiveType::Triangle );
+
+ // issue an endPrimitive() when leaving the scope
+ const ::comphelper::ScopeGuard aScopeGuard(
+ [&pRenderModule]() mutable { pRenderModule->endPrimitive(); } );
+
+ for(sal_uInt32 nIndex=0; nIndex<nVertexCount; ++nIndex)
+ {
+ const basegfx::B2DPoint &aPoint = rTriangleList.getB2DPoint(nIndex);
+ basegfx::B2DPoint aTransformedPoint(aTransform * aPoint);
+ const double tu(((aPoint.getX()-aSurfaceClipRect.getMinX())*rUV.getWidth()/w)+rUV.getMinX());
+ const double tv(((aPoint.getY()-aSurfaceClipRect.getMinY())*rUV.getHeight()/h)+rUV.getMinY());
+ vertex.u=static_cast<float>(tu);
+ vertex.v=static_cast<float>(tv);
+ vertex.x=static_cast<float>(aTransformedPoint.getX());
+ vertex.y=static_cast<float>(aTransformedPoint.getY());
+ pRenderModule->pushVertex(vertex);
+ }
+ }
+
+ return !(pRenderModule->isError());
+ }
+
+ void Surface::prepareRendering()
+ {
+ mpPageManager->validatePages();
+
+ // clients requested to draw from this surface, therefore one
+ // of the above implemented concrete rendering operations
+ // was triggered. we therefore need to ask the pagemanager
+ // to allocate some space for the fragment we're dedicated to.
+ if(!mpFragment)
+ {
+ mpFragment = mpPageManager->allocateSpace(maSize);
+ if( mpFragment )
+ {
+ mpFragment->setColorBuffer(mpColorBuffer);
+ mpFragment->setSourceOffset(maSourceOffset);
+ }
+ }
+
+ if( mpFragment )
+ {
+ // now we need to 'select' the fragment, which will in turn
+ // pull information from the image on demand.
+ // in case this fragment is still not located on any of the
+ // available pages ['naked'], we force the page manager to
+ // do it now, no way to defer this any longer...
+ if(!(mpFragment->select(mbIsDirty)))
+ mpPageManager->nakedFragment(mpFragment);
+
+ }
+ mbIsDirty=false;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/surface.hxx b/canvas/source/tools/surface.hxx
new file mode 100644
index 0000000000..e78925292f
--- /dev/null
+++ b/canvas/source/tools/surface.hxx
@@ -0,0 +1,143 @@
+/* -*- 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 <basegfx/point/b2ipoint.hxx>
+#include <basegfx/point/b2dpoint.hxx>
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/vector/b2isize.hxx>
+#include <basegfx/matrix/b2dhommatrix.hxx>
+#include <rendering/icolorbuffer.hxx>
+
+#include "pagemanager.hxx"
+
+namespace canvas
+{
+ /** surfaces denote occupied areas within pages.
+
+ pages encapsulate the hardware buffers that
+ contain image data which can be used for texturing.
+ surfaces are areas within those pages.
+ */
+ class Surface
+ {
+ public:
+
+ Surface( PageManagerSharedPtr xPageManager,
+ std::shared_ptr<IColorBuffer> xColorBuffer,
+ const ::basegfx::B2IPoint& rPos,
+ const ::basegfx::B2ISize& rSize );
+ ~Surface();
+
+ void setColorBufferDirty();
+
+ /** Render the surface content to screen.
+
+ @param fAlpha
+ Overall alpha for content
+
+ @param rPos
+ Output position
+
+ @param rTransform
+ Output transformation (does not affect output position)
+ */
+ bool draw( double fAlpha,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DHomMatrix& rTransform );
+
+ /** Render the surface content to screen.
+
+ @param fAlpha
+ Overall alpha for content
+
+ @param rPos
+ Output position
+
+ @param rArea
+ Subset of the surface to render. Coordinate system are
+ surface area pixel, given area will be clipped to the
+ surface bounds.
+
+ @param rTransform
+ Output transformation (does not affect output position)
+ */
+ bool drawRectangularArea(
+ double fAlpha,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DRectangle& rArea,
+ const ::basegfx::B2DHomMatrix& rTransform );
+
+ /** Render the surface content to screen.
+
+ @param fAlpha
+ Overall alpha for content
+
+ @param rPos
+ Output position
+
+ @param rClipPoly
+ Clip polygon for the surface. The clip polygon is also
+ subject to the output transformation.
+
+ @param rTransform
+ Output transformation (does not affect output position)
+ */
+ bool drawWithClip( double fAlpha,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DPolygon& rClipPoly,
+ const ::basegfx::B2DHomMatrix& rTransform );
+
+ private:
+ std::shared_ptr<IColorBuffer> mpColorBuffer;
+
+ // invoking any of the above defined 'draw' methods
+ // will forward primitive commands to the rendermodule.
+ PageManagerSharedPtr mpPageManager;
+
+ FragmentSharedPtr mpFragment;
+
+ // the offset of this surface with regard to the source
+ // image. if the source image had to be tiled into multiple
+ // surfaces, this offset denotes the relative pixel distance
+ // from the source image's upper, left corner
+ ::basegfx::B2IPoint maSourceOffset;
+
+ // the size in pixels of this surface. please note that
+ // this size is likely to be smaller than the size of
+ // the colorbuffer we're associated with since we
+ // maybe represent only a part of it.
+ ::basegfx::B2ISize maSize;
+
+ bool mbIsDirty;
+
+ private:
+ void prepareRendering();
+
+ basegfx::B2DRectangle getUVCoords() const;
+ basegfx::B2DRectangle getUVCoords( const ::basegfx::B2IPoint& rPos,
+ const ::basegfx::B2ISize& rSize ) const;
+ };
+
+ typedef std::shared_ptr< Surface > SurfaceSharedPtr;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/surfaceproxy.cxx b/canvas/source/tools/surfaceproxy.cxx
new file mode 100644
index 0000000000..af76b8b3c3
--- /dev/null
+++ b/canvas/source/tools/surfaceproxy.cxx
@@ -0,0 +1,141 @@
+/* -*- 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/polygon/b2dpolypolygon.hxx>
+#include <basegfx/polygon/b2dpolygontriangulator.hxx>
+#include <basegfx/polygon/b2dpolypolygontools.hxx>
+#include <utility>
+
+#include "surfaceproxy.hxx"
+
+namespace canvas
+{
+ SurfaceProxy::SurfaceProxy( std::shared_ptr<canvas::IColorBuffer> xBuffer,
+ PageManagerSharedPtr xPageManager ) :
+ mpPageManager(std::move( xPageManager )),
+ mpBuffer(std::move( xBuffer ))
+ {
+ const ::basegfx::B2ISize aImageSize(mpBuffer->getWidth(),mpBuffer->getHeight());
+ const ::basegfx::B2ISize aPageSize(mpPageManager->getPageSize());
+ const sal_Int32 aPageSizeX(aPageSize.getWidth());
+ const sal_Int32 aPageSizeY(aPageSize.getHeight());
+ const sal_Int32 aImageSizeX(aImageSize.getWidth());
+ const sal_Int32 aImageSizeY(aImageSize.getHeight());
+
+ // see if the size of the colorbuffer is larger than the size
+ // of a single page. if this is the case we divide the
+ // colorbuffer into as many surfaces as we need to get the
+ // whole area distributed. otherwise (the colorbuffer is
+ // smaller than the size of a single page) we search for free
+ // pages or create a new one.
+ // the incoming image is too large to fit into a single
+ // page. strategy: we split the image into rectangular
+ // areas that are as large as the maximum page size
+ // dictates and follow the strategy for fitting images.
+ size_t dwNumSurfaces(0);
+ for(sal_Int32 y=0; y<aImageSizeY; y+=aPageSizeY)
+ for(sal_Int32 x=0; x<aImageSizeX; x+=aPageSizeX)
+ ++dwNumSurfaces;
+ maSurfaceList.reserve(dwNumSurfaces);
+
+ for(sal_Int32 y=0; y<aImageSizeY; y+=aPageSizeY)
+ {
+ for(sal_Int32 x=0; x<aImageSizeX; x+=aPageSizeX)
+ {
+ // the current surface is located at the position [x,y]
+ // and has the size [min(restx,pagesizex),min(resty,pagesizey)
+ ::basegfx::B2IPoint aOffset(x,y);
+ ::basegfx::B2ISize aSize( std::min( aImageSize.getWidth()-x,
+ aPageSize.getWidth() ),
+ std::min( aImageSize.getHeight()-y,
+ aPageSize.getHeight() ) );
+
+ maSurfaceList.push_back(
+ std::make_shared<Surface>(
+ mpPageManager,
+ mpBuffer,
+ aOffset,
+ aSize));
+ }
+ }
+ }
+
+ void SurfaceProxy::setColorBufferDirty()
+ {
+ for( const auto& rSurfacePtr : maSurfaceList )
+ rSurfacePtr->setColorBufferDirty();
+ }
+
+ bool SurfaceProxy::draw( double fAlpha,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DHomMatrix& rTransform )
+ {
+ for( const auto& rSurfacePtr : maSurfaceList )
+ rSurfacePtr->draw( fAlpha, rPos, rTransform );
+
+ return true;
+ }
+
+ bool SurfaceProxy::draw( double fAlpha,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DRange& rArea,
+ const ::basegfx::B2DHomMatrix& rTransform )
+ {
+ for( const auto& rSurfacePtr : maSurfaceList )
+ rSurfacePtr->drawRectangularArea( fAlpha, rPos, rArea, rTransform );
+
+ return true;
+ }
+
+ bool SurfaceProxy::draw( double fAlpha,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DPolyPolygon& rClipPoly,
+ const ::basegfx::B2DHomMatrix& rTransform )
+ {
+ const ::basegfx::triangulator::B2DTriangleVector& rTriangulatedVector(
+ ::basegfx::triangulator::triangulate(rClipPoly));
+
+ // we have now an explicit ::B2DTriangle and ::B2DTriangleVector,
+ // but I do not know enough about 'drawWithClip' or 'clipTriangleListOnRange'
+ // to adapt to that. Convert back to old three-point-in-polygon convention
+ ::basegfx::B2DPolygon aTriangulatedPolygon;
+ aTriangulatedPolygon.reserve(rTriangulatedVector.size() * 3);
+
+ for(const auto& rCandidate : rTriangulatedVector)
+ {
+ aTriangulatedPolygon.append(rCandidate.getA());
+ aTriangulatedPolygon.append(rCandidate.getB());
+ aTriangulatedPolygon.append(rCandidate.getC());
+ }
+
+ // dump polygons
+ SAL_INFO("canvas", "Original clip polygon: " << basegfx::utils::exportToSvgD( rClipPoly, true, true, false ));
+ SAL_INFO("canvas", "Triangulated polygon: " << basegfx::utils::exportToSvgD(basegfx::B2DPolyPolygon(aTriangulatedPolygon), true, true, false ));
+
+ for( const auto& rSurfacePtr : maSurfaceList )
+ rSurfacePtr->drawWithClip( fAlpha, rPos, aTriangulatedPolygon, rTransform );
+
+ return true;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/surfaceproxy.hxx b/canvas/source/tools/surfaceproxy.hxx
new file mode 100644
index 0000000000..72841d754e
--- /dev/null
+++ b/canvas/source/tools/surfaceproxy.hxx
@@ -0,0 +1,118 @@
+/* -*- 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 <rendering/isurfaceproxy.hxx>
+#include <rendering/icolorbuffer.hxx>
+
+#include "pagemanager.hxx"
+#include "surface.hxx"
+
+namespace canvas
+{
+ /** Definition of the surface proxy class.
+
+ Surface proxies are the connection between *one* source image
+ and *one or more* hardware surfaces (or textures). in a
+ logical structure surface proxies represent solely this
+ dependency plus some simple cache management.
+ */
+ class SurfaceProxy : public ISurfaceProxy
+ {
+ public:
+
+ SurfaceProxy( std::shared_ptr<canvas::IColorBuffer> xBuffer,
+ PageManagerSharedPtr xPageManager );
+
+ // ISurfaceProxy interface
+ virtual void setColorBufferDirty() override;
+
+ /** Render the surface content to screen.
+
+ @param fAlpha
+ Overall alpha for content
+
+ @param rPos
+ Output position
+
+ @param rTransform
+ Output transformation (does not affect output position)
+ */
+ virtual bool draw( double fAlpha,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DHomMatrix& rTransform ) override;
+
+ /** Render the surface content to screen.
+
+ @param fAlpha
+ Overall alpha for content
+
+ @param rPos
+ Output position
+
+ @param rArea
+ Subset of the surface to render. Coordinate system are
+ surface area pixel, given area will be clipped to the
+ surface bounds.
+
+ @param rTransform
+ Output transformation (does not affect output position)
+ */
+ virtual bool draw( double fAlpha,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DRange& rArea,
+ const ::basegfx::B2DHomMatrix& rTransform ) override;
+
+ /** Render the surface content to screen.
+
+ @param fAlpha
+ Overall alpha for content
+
+ @param rPos
+ Output position
+
+ @param rClipPoly
+ Clip polygon for the surface. The clip polygon is also
+ subject to the output transformation.
+
+ @param rTransform
+ Output transformation (does not affect output position)
+ */
+ virtual bool draw( double fAlpha,
+ const ::basegfx::B2DPoint& rPos,
+ const ::basegfx::B2DPolyPolygon& rClipPoly,
+ const ::basegfx::B2DHomMatrix& rTransform ) override;
+
+ private:
+ PageManagerSharedPtr mpPageManager;
+
+ // the pagemanager will distribute the image
+ // to one or more surfaces, this is why we
+ // need a list here.
+ std::vector<SurfaceSharedPtr> maSurfaceList;
+
+ // pointer to the source of image data
+ // which always is stored in system memory,
+ // 32bit rgba and can have any size.
+ std::shared_ptr<canvas::IColorBuffer> mpBuffer;
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/surfaceproxymanager.cxx b/canvas/source/tools/surfaceproxymanager.cxx
new file mode 100644
index 0000000000..b60c5ecb48
--- /dev/null
+++ b/canvas/source/tools/surfaceproxymanager.cxx
@@ -0,0 +1,73 @@
+/* -*- 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 <rendering/isurfaceproxy.hxx>
+#include <rendering/isurfaceproxymanager.hxx>
+
+#include "surfaceproxy.hxx"
+
+namespace canvas
+{
+ namespace {
+
+ class SurfaceProxyManager : public ISurfaceProxyManager
+ {
+ public:
+
+ explicit SurfaceProxyManager( const std::shared_ptr<IRenderModule>& rRenderModule ) :
+ mpPageManager( std::make_shared<PageManager>(rRenderModule) )
+ {
+ }
+
+ /** the whole idea is build around the concept that you create
+ some arbitrary buffer which contains the image data and
+ tell the texture manager about it. from there on you can
+ draw this image using any kind of graphics api you want.
+ in the technical sense we allocate some space in local
+ videomemory or AGP memory which will be filled on demand,
+ which means if there exists any rendering operation that
+ needs to read from this memory location. this method
+ creates a logical hardware surface object which uses the
+ given color buffer as the image source. internally this
+ texture may be distributed to several real hardware
+ surfaces.
+ */
+ virtual std::shared_ptr<ISurfaceProxy> createSurfaceProxy( const std::shared_ptr<IColorBuffer>& pBuffer ) const override
+ {
+ // not much to do for now, simply allocate a new surface
+ // proxy from our internal pool and initialize this thing
+ // properly. we *don't* create a hardware surface for now.
+ return std::make_shared<SurfaceProxy>(pBuffer,mpPageManager);
+ }
+
+ private:
+ PageManagerSharedPtr mpPageManager;
+ };
+
+ }
+
+ std::shared_ptr<ISurfaceProxyManager> createSurfaceProxyManager( const std::shared_ptr<IRenderModule>& rRenderModule )
+ {
+ return std::make_shared<SurfaceProxyManager>(rRenderModule);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/surfacerect.hxx b/canvas/source/tools/surfacerect.hxx
new file mode 100644
index 0000000000..0925fa8e6f
--- /dev/null
+++ b/canvas/source/tools/surfacerect.hxx
@@ -0,0 +1,90 @@
+/* -*- 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 <basegfx/point/b2ipoint.hxx>
+#include <basegfx/vector/b2isize.hxx>
+
+namespace canvas
+{
+ /**
+ * This implements some equivalent to basegfx::B2IBox, but instead of two
+ * BasicBox ranges, it uses a position and a size. maPos and maSize could
+ * be replaced by:
+ * - B2IPoint(getMinX(), getMinY()) and
+ * - B2ISize(getMaxX()-getMinX(), getMaxY()-getMinY())
+ *
+ * The current allocation algorithm uses size and pos a lot. Not sure how
+ * time-critical any of this code is and if that would be a problem.
+ */
+ struct SurfaceRect
+ {
+ ::basegfx::B2IPoint maPos;
+ ::basegfx::B2ISize maSize;
+
+ explicit SurfaceRect( const ::basegfx::B2ISize &rSize ) :
+ maPos(),
+ maSize(rSize)
+ {
+ }
+
+ bool pointInside( sal_Int32 px, sal_Int32 py ) const
+ {
+ const sal_Int32 x1(maPos.getX());
+ const sal_Int32 y1(maPos.getY());
+ const sal_Int32 x2(x1 + maSize.getWidth());
+ const sal_Int32 y2(y1 + maSize.getHeight());
+ if(px < x1) return false;
+ if(px >= x2) return false;
+ if(py < y1) return false;
+ if(py >= y2) return false;
+ return true;
+ }
+
+ /// returns true if the passed rect intersects this one.
+ bool intersection( const SurfaceRect& r ) const
+ {
+ const sal_Int32 x1(maPos.getX());
+ const sal_Int32 y1(maPos.getY());
+ const sal_Int32 x1w(x1 + maSize.getWidth() - 1);
+ const sal_Int32 y1h(y1 + maSize.getHeight() - 1);
+
+ const sal_Int32 x2(r.maPos.getX());
+ const sal_Int32 y2(r.maPos.getY());
+ const sal_Int32 x2w(x2 + r.maSize.getWidth() - 1);
+ const sal_Int32 y2h(y2 + r.maSize.getHeight() - 1);
+
+ return !((x1w < x2) || (x2w < x1) || (y1h < y2) || (y2h < y1));
+ }
+
+ bool inside( const SurfaceRect& r ) const
+ {
+ const sal_Int32 x1(maPos.getX());
+ const sal_Int32 y1(maPos.getY());
+ const sal_Int32 x2(x1 + maSize.getWidth() - 1);
+ const sal_Int32 y2(y1 + maSize.getHeight() - 1);
+ if(!(r.pointInside(x1,y1))) return false;
+ if(!(r.pointInside(x2,y2))) return false;
+ return true;
+ }
+ };
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/canvas/source/tools/verifyinput.cxx b/canvas/source/tools/verifyinput.cxx
new file mode 100644
index 0000000000..ae0704d81d
--- /dev/null
+++ b/canvas/source/tools/verifyinput.cxx
@@ -0,0 +1,709 @@
+/* -*- 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/range/b2irange.hxx>
+#include <basegfx/utils/canvastools.hxx>
+#include <com/sun/star/geometry/AffineMatrix2D.hpp>
+#include <com/sun/star/geometry/IntegerPoint2D.hpp>
+#include <com/sun/star/geometry/IntegerSize2D.hpp>
+#include <com/sun/star/geometry/Matrix2D.hpp>
+#include <com/sun/star/geometry/RealBezierSegment2D.hpp>
+#include <com/sun/star/geometry/RealPoint2D.hpp>
+#include <com/sun/star/geometry/RealRectangle2D.hpp>
+#include <com/sun/star/geometry/RealSize2D.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <com/sun/star/rendering/CompositeOperation.hpp>
+#include <com/sun/star/rendering/FontRequest.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/RenderState.hpp>
+#include <com/sun/star/rendering/Texture.hpp>
+#include <com/sun/star/rendering/TexturingMode.hpp>
+#include <com/sun/star/rendering/ViewState.hpp>
+#include <com/sun/star/util/Endianness.hpp>
+
+#include <verifyinput.hxx>
+
+
+using namespace ::com::sun::star;
+
+namespace canvas::tools
+{
+ void verifyInput( const geometry::RealPoint2D& rPoint,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf,
+ ::sal_Int16 nArgPos )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ if( !std::isfinite( rPoint.X ) )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii( pStr ) + ": verifyInput(): point X value contains infinite or NAN",
+ xIf, nArgPos );
+ }
+
+ if( !std::isfinite( rPoint.Y ) )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii( pStr ) + ": verifyInput(): point X value contains infinite or NAN",
+ xIf, nArgPos );
+ }
+#else
+ (void)pStr; (void)xIf; (void)nArgPos;
+ if( !std::isfinite( rPoint.X ) ||
+ !std::isfinite( rPoint.Y ) )
+ {
+ throw lang::IllegalArgumentException();
+ }
+#endif
+ }
+
+ void verifyInput( const geometry::RealBezierSegment2D& rSegment,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf,
+ ::sal_Int16 nArgPos )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ if( !std::isfinite( rSegment.Px ) )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii( pStr ) +
+ ": verifyInput(): bezier segment's Px value contains infinite or NAN",
+ xIf, nArgPos );
+ }
+
+ if( !std::isfinite( rSegment.Py ) )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii( pStr ) +
+ ": verifyInput(): bezier segment's Py value contains infinite or NAN",
+ xIf, nArgPos );
+ }
+
+ if( !std::isfinite( rSegment.C1x ) )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii( pStr ) +
+ ": verifyInput(): bezier segment's C1x value contains infinite or NAN",
+ xIf, nArgPos );
+ }
+
+ if( !std::isfinite( rSegment.C1y ) )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii( pStr ) +
+ ": verifyInput(): bezier segment's C1y value contains infinite or NAN",
+ xIf, nArgPos );
+ }
+
+ if( !std::isfinite( rSegment.C2x ) )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii( pStr ) +
+ ": verifyInput(): bezier segment's C2x value contains infinite or NAN",
+ xIf, nArgPos );
+ }
+
+ if( !std::isfinite( rSegment.C2y ) )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii( pStr ) +
+ ": verifyInput(): bezier segment's C2y value contains infinite or NAN",
+ xIf, nArgPos );
+ }
+#else
+ (void)pStr; (void)xIf; (void)nArgPos;
+ if( !std::isfinite( rSegment.Px ) ||
+ !std::isfinite( rSegment.Py ) ||
+ !std::isfinite( rSegment.C1x ) ||
+ !std::isfinite( rSegment.C1y ) ||
+ !std::isfinite( rSegment.C2x ) ||
+ !std::isfinite( rSegment.C2y ) )
+ {
+ throw lang::IllegalArgumentException();
+ }
+#endif
+ }
+
+ void verifyInput( const geometry::RealRectangle2D& rRect,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf,
+ ::sal_Int16 nArgPos )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ if( !std::isfinite( rRect.X1 ) )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): rectangle point X1 contains infinite or NAN",
+ xIf, nArgPos );
+ }
+
+ if( !std::isfinite( rRect.Y1 ) )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): rectangle point Y1 contains infinite or NAN",
+ xIf, nArgPos );
+ }
+
+ if( !std::isfinite( rRect.X2 ) )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): rectangle point X2 contains infinite or NAN",
+ xIf, nArgPos );
+ }
+
+ if( !std::isfinite( rRect.Y2 ) )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): rectangle point Y2 contains infinite or NAN",
+ xIf, nArgPos );
+ }
+#else
+ (void)pStr; (void)xIf; (void)nArgPos;
+ if( !std::isfinite( rRect.X1 ) ||
+ !std::isfinite( rRect.Y1 ) ||
+ !std::isfinite( rRect.X2 ) ||
+ !std::isfinite( rRect.Y2 ) )
+ {
+ throw lang::IllegalArgumentException();
+ }
+#endif
+ }
+
+ void verifyInput( const geometry::AffineMatrix2D& matrix,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf,
+ ::sal_Int16 nArgPos )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ const sal_Int32 nBinaryState(
+ 100000 * int(!std::isfinite( matrix.m00 )) +
+ 10000 * int(!std::isfinite( matrix.m01 )) +
+ 1000 * int(!std::isfinite( matrix.m02 )) +
+ 100 * int(!std::isfinite( matrix.m10 )) +
+ 10 * int(!std::isfinite( matrix.m11 )) +
+ 1 * int(!std::isfinite( matrix.m12 )) );
+
+ if( nBinaryState )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): AffineMatrix2D contains infinite or NAN value(s) at the following positions (m00-m12): " +
+ OUString::number(nBinaryState),
+ xIf, nArgPos );
+ }
+#else
+ (void)pStr; (void)xIf; (void)nArgPos;
+ if( !std::isfinite( matrix.m00 ) ||
+ !std::isfinite( matrix.m01 ) ||
+ !std::isfinite( matrix.m02 ) ||
+ !std::isfinite( matrix.m10 ) ||
+ !std::isfinite( matrix.m11 ) ||
+ !std::isfinite( matrix.m12 ) )
+ {
+ throw lang::IllegalArgumentException();
+ }
+#endif
+ }
+
+ void verifyInput( const geometry::Matrix2D& matrix,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf,
+ ::sal_Int16 nArgPos )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ const sal_Int32 nBinaryState(
+ 1000 * int(!std::isfinite( matrix.m00 )) +
+ 100 * int(!std::isfinite( matrix.m01 )) +
+ 10 * int(!std::isfinite( matrix.m10 )) +
+ 1 * int(!std::isfinite( matrix.m11 )) );
+
+ if( nBinaryState )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): Matrix2D contains infinite or NAN value(s) at the following positions (m00-m11): " +
+ OUString::number(nBinaryState),
+ xIf, nArgPos );
+ }
+#else
+ (void)pStr; (void)xIf; (void)nArgPos;
+ if( !std::isfinite( matrix.m00 ) ||
+ !std::isfinite( matrix.m01 ) ||
+ !std::isfinite( matrix.m10 ) ||
+ !std::isfinite( matrix.m11 ) )
+ {
+ throw lang::IllegalArgumentException();
+ }
+#endif
+ }
+
+ void verifyInput( const rendering::ViewState& viewState,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf,
+ ::sal_Int16 nArgPos )
+ {
+ verifyInput( viewState.AffineTransform,
+ pStr, xIf, nArgPos );
+ }
+
+ void verifyInput( const rendering::RenderState& renderState,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf,
+ ::sal_Int16 nArgPos,
+ sal_Int32 nMinColorComponents )
+ {
+ verifyInput( renderState.AffineTransform,
+ pStr, xIf, nArgPos );
+
+ if( renderState.DeviceColor.getLength() < nMinColorComponents )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): render state's device color has too few components (" +
+ OUString::number(nMinColorComponents) +
+ " expected, " +
+ OUString::number(renderState.DeviceColor.getLength()) +
+ " provided)",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( renderState.CompositeOperation >= rendering::CompositeOperation::CLEAR &&
+ renderState.CompositeOperation <= rendering::CompositeOperation::SATURATE )
+ return;
+
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): render state's CompositeOperation value out of range (" +
+ OUString::number(sal::static_int_cast<sal_Int32>(renderState.CompositeOperation)) +
+ " not known)",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ void verifyInput( const rendering::Texture& texture,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf,
+ ::sal_Int16 nArgPos )
+ {
+ verifyInput( texture.AffineTransform,
+ pStr, xIf, nArgPos );
+
+ if( !std::isfinite( texture.Alpha ) ||
+ texture.Alpha < 0.0 ||
+ texture.Alpha > 1.0 )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): textures' alpha value out of range (is " +
+ OUString::number(texture.Alpha) + ")",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( texture.NumberOfHatchPolygons < 0 )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): textures' NumberOfHatchPolygons is negative",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( texture.RepeatModeX < rendering::TexturingMode::NONE ||
+ texture.RepeatModeX > rendering::TexturingMode::REPEAT )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): textures' RepeatModeX value is out of range (" +
+ OUString::number(sal::static_int_cast<sal_Int32>(texture.RepeatModeX)) +
+ " not known)",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( texture.RepeatModeY >= rendering::TexturingMode::NONE &&
+ texture.RepeatModeY <= rendering::TexturingMode::REPEAT )
+ return;
+
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): textures' RepeatModeY value is out of range (" +
+ OUString::number(sal::static_int_cast<sal_Int32>(texture.RepeatModeY)) +
+ " not known)",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ namespace
+ {
+ struct VerifyDashValue
+ {
+ VerifyDashValue( const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf,
+ ::sal_Int16 nArgPos ) :
+ mpStr( pStr ),
+ mrIf( xIf ),
+ mnArgPos( nArgPos )
+ {
+ }
+
+ void operator()( const double& rVal )
+ {
+ if( !std::isfinite( rVal ) || rVal < 0.0 )
+ {
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(mpStr) +
+ ": verifyInput(): one of stroke attributes' DashArray value out of range (is " +
+ OUString::number(rVal) + ")",
+ mrIf, mnArgPos );
+ }
+ }
+
+ const char* mpStr;
+ const uno::Reference< uno::XInterface >& mrIf;
+ sal_Int16 mnArgPos;
+ };
+ }
+
+ void verifyInput( const rendering::StrokeAttributes& strokeAttributes,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf,
+ ::sal_Int16 nArgPos )
+ {
+ if( !std::isfinite( strokeAttributes.StrokeWidth ) ||
+ strokeAttributes.StrokeWidth < 0.0 )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): stroke attributes' StrokeWidth value out of range (is " +
+ OUString::number(strokeAttributes.StrokeWidth) +
+ ")",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( !std::isfinite( strokeAttributes.MiterLimit ) ||
+ strokeAttributes.MiterLimit < 0.0 )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): stroke attributes' MiterLimit value out of range (is " +
+ OUString::number(strokeAttributes.MiterLimit) + ")",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ VerifyDashValue aVerifyDashValue( pStr, xIf, nArgPos );
+ for (auto const& aStrokeAttribute : strokeAttributes.DashArray)
+ aVerifyDashValue( aStrokeAttribute );
+
+ for (auto const& aStrokeAttribute : strokeAttributes.LineArray)
+ aVerifyDashValue( aStrokeAttribute );
+
+ if( strokeAttributes.StartCapType < rendering::PathCapType::BUTT ||
+ strokeAttributes.StartCapType > rendering::PathCapType::SQUARE )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): stroke attributes' StartCapType value is out of range (" +
+ OUString::number(sal::static_int_cast<sal_Int32>(strokeAttributes.StartCapType)) +
+ " not known)",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( strokeAttributes.EndCapType < rendering::PathCapType::BUTT ||
+ strokeAttributes.EndCapType > rendering::PathCapType::SQUARE )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): stroke attributes' StartCapType value is out of range (" +
+ OUString::number(sal::static_int_cast<sal_Int32>(strokeAttributes.EndCapType)) +
+ " not known)",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( strokeAttributes.JoinType >= rendering::PathJoinType::NONE &&
+ strokeAttributes.JoinType <= rendering::PathJoinType::BEVEL )
+ return;
+
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): stroke attributes' JoinType value is out of range (" +
+ OUString::number(sal::static_int_cast<sal_Int32>(strokeAttributes.JoinType)) +
+ " not known)",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ void verifyInput( const rendering::IntegerBitmapLayout& bitmapLayout,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf,
+ ::sal_Int16 nArgPos )
+ {
+ if( bitmapLayout.ScanLines < 0 )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): bitmap layout's ScanLines is negative",
+ xIf, nArgPos );
+#else
+ (void)pStr; (void)xIf; (void)nArgPos;
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( bitmapLayout.ScanLineBytes < 0 )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): bitmap layout's ScanLineBytes is negative",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( !bitmapLayout.ColorSpace.is() )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): bitmap layout's ColorSpace is invalid",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+ if( bitmapLayout.ColorSpace->getBitsPerPixel() < 0 )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): bitmap layout's ColorSpace getBitsPerPixel() is negative",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( bitmapLayout.ColorSpace->getEndianness() >= util::Endianness::LITTLE &&
+ bitmapLayout.ColorSpace->getEndianness() <= util::Endianness::BIG )
+ return;
+
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): bitmap layout's ColorSpace getEndianness() value is out of range (" +
+ OUString::number(sal::static_int_cast<sal_Int32>(bitmapLayout.ColorSpace->getEndianness())) +
+ " not known)",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ void verifyInput( const rendering::FontRequest& fontRequest,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf,
+ ::sal_Int16 nArgPos )
+ {
+ verifyInput( fontRequest.FontDescription,
+ pStr, xIf, nArgPos );
+
+ if( !std::isfinite( fontRequest.CellSize ) )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): font request's CellSize value contains infinite or NAN",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( !std::isfinite( fontRequest.ReferenceAdvancement ) )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): font request's ReferenceAdvancement value contains infinite or NAN",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( fontRequest.CellSize != 0.0 &&
+ fontRequest.ReferenceAdvancement != 0.0 )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyInput(): font request's CellSize and ReferenceAdvancement are mutually exclusive, one of them must be 0.0",
+ xIf, nArgPos );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+ }
+
+ void verifyIndexRange( const geometry::IntegerRectangle2D& rect,
+ const geometry::IntegerSize2D& size )
+ {
+ const ::basegfx::B2IRange aRect(
+ ::basegfx::unotools::b2IRectangleFromIntegerRectangle2D(
+ rect ) );
+
+ if( aRect.getMinX() < 0 ||
+ aRect.getMaxX() > size.Width ||
+ aRect.getMinY() < 0 ||
+ aRect.getMaxY() > size.Height )
+ {
+ throw css::lang::IndexOutOfBoundsException();
+ }
+ }
+
+ void verifyIndexRange( const geometry::IntegerPoint2D& pos,
+ const geometry::IntegerSize2D& size )
+ {
+ if( pos.X < 0 ||
+ pos.X > size.Width ||
+ pos.Y < 0 ||
+ pos.Y > size.Height )
+ {
+ throw css::lang::IndexOutOfBoundsException();
+ }
+ }
+
+ void verifyBitmapSize( const geometry::IntegerSize2D& size,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf )
+ {
+ if( size.Width <= 0 )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyBitmapSize(): size has 0 or negative width (value: " +
+ OUString::number(size.Width) + ")",
+ xIf, 0 );
+#else
+ (void)pStr; (void)xIf;
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( size.Height > 0 )
+ return;
+
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifyBitmapSize(): size has 0 or negative height (value: " +
+ OUString::number(size.Height) +
+ ")",
+ xIf, 0 );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ void verifySpriteSize( const geometry::RealSize2D& size,
+ const char* pStr,
+ const uno::Reference< uno::XInterface >& xIf )
+ {
+ if( size.Width <= 0.0 )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifySpriteSize(): size has 0 or negative width (value: " +
+ OUString::number(size.Width) + ")",
+ xIf, 0 );
+#else
+ (void)pStr; (void)xIf;
+ throw lang::IllegalArgumentException();
+#endif
+ }
+
+ if( size.Height <= 0.0 )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ throw lang::IllegalArgumentException(
+ OUString::createFromAscii(pStr) +
+ ": verifySpriteSize(): size has 0 or negative height (value: " +
+ OUString::number(size.Height) + ")",
+ xIf, 0 );
+#else
+ throw lang::IllegalArgumentException();
+#endif
+ }
+ }
+
+
+} // namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */