diff options
Diffstat (limited to '')
-rw-r--r-- | slideshow/source/engine/shapes/viewshape.cxx | 856 |
1 files changed, 856 insertions, 0 deletions
diff --git a/slideshow/source/engine/shapes/viewshape.cxx b/slideshow/source/engine/shapes/viewshape.cxx new file mode 100644 index 000000000..f2d909524 --- /dev/null +++ b/slideshow/source/engine/shapes/viewshape.cxx @@ -0,0 +1,856 @@ +/* -*- 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 <tools/diagnose_ex.h> + +#include <algorithm> + +#include <rtl/math.hxx> +#include <sal/log.hxx> + +#include <com/sun/star/rendering/PanoseLetterForm.hpp> +#include <com/sun/star/awt/FontSlant.hpp> + +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +#include <canvas/canvastools.hxx> +#include <cppcanvas/vclfactory.hxx> +#include <cppcanvas/basegfxfactory.hxx> + +#include "viewshape.hxx" +#include <tools.hxx> + +using namespace ::com::sun::star; + +namespace slideshow::internal +{ + + // TODO(F2): Provide sensible setup for mtf-related attributes (fill mode, + // char rotation etc.). Do that via mtf argument at this object + + bool ViewShape::prefetch( RendererCacheEntry& io_rCacheEntry, + const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf, + const ShapeAttributeLayerSharedPtr& rAttr ) + { + ENSURE_OR_RETURN_FALSE( rMtf, + "ViewShape::prefetch(): no valid metafile!" ); + + if( rMtf != io_rCacheEntry.mpMtf || + rDestinationCanvas != io_rCacheEntry.getDestinationCanvas() ) + { + // buffered renderer invalid, re-create + ::cppcanvas::Renderer::Parameters aParms; + + // rendering attribute override parameter struct. For + // every valid attribute, the corresponding struct + // member is filled, which in the metafile renderer + // forces rendering with the given attribute. + if( rAttr ) + { + if( rAttr->isFillColorValid() ) + { + // convert RGBColor to RGBA32 integer. Note + // that getIntegerColor() also truncates + // out-of-range values appropriately + aParms.maFillColor = + rAttr->getFillColor().getIntegerColor(); + } + if( rAttr->isLineColorValid() ) + { + // convert RGBColor to RGBA32 integer. Note + // that getIntegerColor() also truncates + // out-of-range values appropriately + aParms.maLineColor = + rAttr->getLineColor().getIntegerColor(); + } + if( rAttr->isCharColorValid() ) + { + // convert RGBColor to RGBA32 integer. Note + // that getIntegerColor() also truncates + // out-of-range values appropriately + aParms.maTextColor = + rAttr->getCharColor().getIntegerColor(); + } + if( rAttr->isDimColorValid() ) + { + // convert RGBColor to RGBA32 integer. Note + // that getIntegerColor() also truncates + // out-of-range values appropriately + + // dim color overrides all other colors + aParms.maFillColor = + aParms.maLineColor = + aParms.maTextColor = + rAttr->getDimColor().getIntegerColor(); + } + if( rAttr->isFontFamilyValid() ) + { + aParms.maFontName = + rAttr->getFontFamily(); + } + if( rAttr->isCharScaleValid() ) + { + ::basegfx::B2DHomMatrix aMatrix; + + // enlarge text by given scale factor. Do that + // with the middle of the shape as the center + // of scaling. + aMatrix.translate( -0.5, -0.5 ); + aMatrix.scale( rAttr->getCharScale(), + rAttr->getCharScale() ); + aMatrix.translate( 0.5, 0.5 ); + + aParms.maTextTransformation = aMatrix; + } + if( rAttr->isCharWeightValid() ) + { + aParms.maFontWeight = + static_cast< sal_Int8 >( + ::basegfx::fround( + ::std::max( 0.0, + ::std::min( 11.0, + rAttr->getCharWeight() / 20.0 ) ) ) ); + } + if( rAttr->isCharPostureValid() ) + { + aParms.maFontLetterForm = + rAttr->getCharPosture() == sal_Int16(awt::FontSlant_NONE) ? + rendering::PanoseLetterForm::ANYTHING : + rendering::PanoseLetterForm::OBLIQUE_CONTACT; + } + if( rAttr->isUnderlineModeValid() ) + { + aParms.maFontUnderline = + rAttr->getUnderlineMode(); + } + } + + io_rCacheEntry.mpRenderer = ::cppcanvas::VCLFactory::createRenderer( rDestinationCanvas, + *rMtf, + aParms ); + + io_rCacheEntry.mpMtf = rMtf; + io_rCacheEntry.mpDestinationCanvas = rDestinationCanvas; + + // also invalidate alpha compositing bitmap (created + // new renderer, which possibly generates different + // output). Do NOT invalidate, if we're incidentally + // rendering INTO it. + if( rDestinationCanvas != io_rCacheEntry.mpLastBitmapCanvas ) + { + io_rCacheEntry.mpLastBitmapCanvas.reset(); + io_rCacheEntry.mpLastBitmap.reset(); + } + } + + return static_cast< bool >(io_rCacheEntry.mpRenderer); + } + + bool ViewShape::draw( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf, + const ShapeAttributeLayerSharedPtr& rAttr, + const ::basegfx::B2DHomMatrix& rTransform, + const ::basegfx::B2DPolyPolygon* pClip, + const VectorOfDocTreeNodes& rSubsets ) const + { + ::cppcanvas::RendererSharedPtr pRenderer( + getRenderer( rDestinationCanvas, rMtf, rAttr ) ); + + ENSURE_OR_RETURN_FALSE( pRenderer, "ViewShape::draw(): Invalid renderer" ); + + pRenderer->setTransformation( rTransform ); +#if OSL_DEBUG_LEVEL >= 2 + rendering::RenderState aRenderState; + ::canvas::tools::initRenderState(aRenderState); + ::canvas::tools::setRenderStateTransform(aRenderState, + rTransform); + aRenderState.DeviceColor.realloc(4); + aRenderState.DeviceColor[0] = 1.0; + aRenderState.DeviceColor[1] = 0.0; + aRenderState.DeviceColor[2] = 0.0; + aRenderState.DeviceColor[3] = 1.0; + + try + { + rDestinationCanvas->getUNOCanvas()->drawLine( geometry::RealPoint2D(0.0,0.0), + geometry::RealPoint2D(1.0,1.0), + rDestinationCanvas->getViewState(), + aRenderState ); + rDestinationCanvas->getUNOCanvas()->drawLine( geometry::RealPoint2D(1.0,0.0), + geometry::RealPoint2D(0.0,1.0), + rDestinationCanvas->getViewState(), + aRenderState ); + } + catch( uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("slideshow"); + } +#endif + if( pClip ) + pRenderer->setClip( *pClip ); + else + pRenderer->setClip(); + + if( rSubsets.empty() ) + { + return pRenderer->draw(); + } + else + { + // render subsets of whole metafile + + + bool bRet(true); + for( const auto& rSubset : rSubsets ) + { + if( !pRenderer->drawSubset( rSubset.getStartIndex(), + rSubset.getEndIndex() ) ) + bRet = false; + } + + return bRet; + } + } + + namespace + { + /// Convert untransformed shape update area to device pixel. + ::basegfx::B2DRectangle shapeArea2AreaPixel( const ::basegfx::B2DHomMatrix& rCanvasTransformation, + const ::basegfx::B2DRectangle& rUntransformedArea ) + { + // convert area to pixel, and add anti-aliasing border + + // TODO(P1): Should the view transform some + // day contain rotation/shear, transforming + // the original bounds with the total + // transformation might result in smaller + // overall bounds. + + ::basegfx::B2DRectangle aBoundsPixel; + ::canvas::tools::calcTransformedRectBounds( aBoundsPixel, + rUntransformedArea, + rCanvasTransformation ); + + // add antialiasing border around the shape (AA + // touches pixel _outside_ the nominal bound rect) + aBoundsPixel.grow( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE ); + + return aBoundsPixel; + } + + /// Convert shape unit rect to device pixel. + ::basegfx::B2DRectangle calcUpdateAreaPixel( const ::basegfx::B2DRectangle& rUnitBounds, + const ::basegfx::B2DHomMatrix& rShapeTransformation, + const ::basegfx::B2DHomMatrix& rCanvasTransformation, + const ShapeAttributeLayerSharedPtr& pAttr ) + { + // calc update area for whole shape (including + // character scaling) + return shapeArea2AreaPixel( rCanvasTransformation, + getShapeUpdateArea( rUnitBounds, + rShapeTransformation, + pAttr ) ); + } + } + + bool ViewShape::renderSprite( const ViewLayerSharedPtr& rViewLayer, + const GDIMetaFileSharedPtr& rMtf, + const ::basegfx::B2DRectangle& rOrigBounds, + const ::basegfx::B2DRectangle& rBounds, + const ::basegfx::B2DRectangle& rUnitBounds, + UpdateFlags nUpdateFlags, + const ShapeAttributeLayerSharedPtr& pAttr, + const VectorOfDocTreeNodes& rSubsets, + double nPrio, + bool bIsVisible ) const + { + // TODO(P1): For multiple views, it might pay off to reorg Shape and ViewShape, + // in that all the common setup steps here are refactored to Shape (would then + // have to be performed only _once_ per Shape paint). + + if( !bIsVisible || + rUnitBounds.isEmpty() || + rOrigBounds.isEmpty() || + rBounds.isEmpty() ) + { + // shape is invisible or has zero size, no need to + // update anything. + if( mpSprite ) + mpSprite->hide(); + + return true; + } + + + // calc sprite position, size and content transformation + // ===================================================== + + // the shape transformation for a sprite is always a + // simple scale-up to the nominal shape size. Everything + // else is handled via the sprite transformation + ::basegfx::B2DHomMatrix aNonTranslationalShapeTransformation; + aNonTranslationalShapeTransformation.scale( rOrigBounds.getWidth(), + rOrigBounds.getHeight() ); + ::basegfx::B2DHomMatrix aShapeTransformation( aNonTranslationalShapeTransformation ); + aShapeTransformation.translate( rOrigBounds.getMinX(), + rOrigBounds.getMinY() ); + + const ::basegfx::B2DHomMatrix& rCanvasTransform( + rViewLayer->getSpriteTransformation() ); + + // area actually needed for the sprite + const ::basegfx::B2DRectangle& rSpriteBoundsPixel( + calcUpdateAreaPixel( rUnitBounds, + aShapeTransformation, + rCanvasTransform, + pAttr ) ); + + // actual area for the shape (without subsetting, but + // including char scaling) + const ::basegfx::B2DRectangle& rShapeBoundsPixel( + calcUpdateAreaPixel( ::basegfx::B2DRectangle(0.0,0.0,1.0,1.0), + aShapeTransformation, + rCanvasTransform, + pAttr ) ); + + // nominal area for the shape (without subsetting, without + // char scaling). NOTE: to cancel the shape translation, + // contained in rSpriteBoundsPixel, this is _without_ any + // translational component. + ::basegfx::B2DRectangle aLogShapeBounds; + const ::basegfx::B2DRectangle& rNominalShapeBoundsPixel( + shapeArea2AreaPixel( rCanvasTransform, + ::canvas::tools::calcTransformedRectBounds( + aLogShapeBounds, + ::basegfx::B2DRectangle(0.0,0.0,1.0,1.0), + aNonTranslationalShapeTransformation ) ) ); + + // create (or resize) sprite with sprite's pixel size, if + // not done already + const ::basegfx::B2DSize& rSpriteSizePixel(rSpriteBoundsPixel.getRange()); + if( !mpSprite ) + { + mpSprite = std::make_shared<AnimatedSprite>( mpViewLayer, + rSpriteSizePixel, + nPrio ); + } + else + { + // TODO(F2): when the sprite _actually_ gets resized, + // content needs a repaint! + mpSprite->resize( rSpriteSizePixel ); + } + + ENSURE_OR_RETURN_FALSE( mpSprite, "ViewShape::renderSprite(): No sprite" ); + + SAL_INFO("slideshow", "ViewShape::renderSprite(): Rendering sprite " << + mpSprite.get() ); + + + // always show the sprite (might have been hidden before) + mpSprite->show(); + + // determine center of sprite output position in pixel + // (assumption here: all shape transformations have the + // shape center as the pivot point). From that, subtract + // distance of rSpriteBoundsPixel's left, top edge from + // rShapeBoundsPixel's center. This moves the sprite at + // the appropriate output position within the virtual + // rShapeBoundsPixel area. + ::basegfx::B2DPoint aSpritePosPixel( rBounds.getCenter() ); + aSpritePosPixel *= rCanvasTransform; + aSpritePosPixel -= rShapeBoundsPixel.getCenter() - rSpriteBoundsPixel.getMinimum(); + + // the difference between rShapeBoundsPixel and + // rSpriteBoundsPixel upper, left corner is: the offset we + // have to move sprite output to the right, top (to make + // the desired subset content visible at all) + const ::basegfx::B2DSize& rSpriteCorrectionOffset( + rSpriteBoundsPixel.getMinimum() - rNominalShapeBoundsPixel.getMinimum() ); + + // offset added top, left for anti-aliasing (otherwise, + // shapes fully filling the sprite will have anti-aliased + // pixel cut off) + const ::basegfx::B2DSize aAAOffset( + ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE, + ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE ); + + // set pixel output offset to sprite: we always leave + // ANTIALIASING_EXTRA_SIZE room atop and to the left, and, + // what's more, for subsetted shapes, we _have_ to cancel + // the effect of the shape renderer outputting the subset + // at its absolute position inside the shape, instead of + // at the origin. + // NOTE: As for now, sprites are always positioned on + // integer pixel positions on screen, have to round to + // nearest integer here, too + mpSprite->setPixelOffset( + aAAOffset - ::basegfx::B2DSize( + ::basegfx::fround( rSpriteCorrectionOffset.getX() ), + ::basegfx::fround( rSpriteCorrectionOffset.getY() ) ) ); + + // always set sprite position and transformation, since + // they do not relate directly to the update flags + // (e.g. sprite position changes when sprite size changes) + mpSprite->movePixel( aSpritePosPixel ); + mpSprite->transform( getSpriteTransformation( rSpriteSizePixel, + rOrigBounds.getRange(), + pAttr ) ); + + + // process flags + // ============= + + bool bRedrawRequired( mbForceUpdate || (nUpdateFlags & UpdateFlags::Force) ); + + if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Alpha) ) + { + mpSprite->setAlpha( (pAttr && pAttr->isAlphaValid()) ? + std::clamp(pAttr->getAlpha(), + 0.0, + 1.0) : + 1.0 ); + } + if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Clip) ) + { + if( pAttr && pAttr->isClipValid() ) + { + ::basegfx::B2DPolyPolygon aClipPoly( pAttr->getClip() ); + + // extract linear part of canvas view transformation + // (linear means: without translational components) + ::basegfx::B2DHomMatrix aViewTransform( + mpViewLayer->getTransformation() ); + aViewTransform.set( 0, 2, 0.0 ); + aViewTransform.set( 1, 2, 0.0 ); + + // make the clip 2*ANTIALIASING_EXTRA_SIZE larger + // such that it's again centered over the sprite. + aViewTransform.scale(rSpriteSizePixel.getX()/ + (rSpriteSizePixel.getX()-2*::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE), + rSpriteSizePixel.getY()/ + (rSpriteSizePixel.getY()-2*::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE)); + + // transform clip polygon from view to device + // coordinate space + aClipPoly.transform( aViewTransform ); + + mpSprite->clip( aClipPoly ); + } + else + mpSprite->clip(); + } + if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Content) ) + { + bRedrawRequired = true; + + // TODO(P1): maybe provide some appearance change methods at + // the Renderer interface + + // force the renderer to be regenerated below, for the + // different attributes to take effect + invalidateRenderer(); + } + + mbForceUpdate = false; + + if( !bRedrawRequired ) + return true; + + + // sprite needs repaint - output to sprite canvas + // ============================================== + + ::cppcanvas::CanvasSharedPtr pContentCanvas( mpSprite->getContentCanvas() ); + + return draw( pContentCanvas, + rMtf, + pAttr, + aShapeTransformation, + nullptr, // clipping is done via Sprite::clip() + rSubsets ); + } + + bool ViewShape::render( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf, + const ::basegfx::B2DRectangle& rBounds, + const ::basegfx::B2DRectangle& rUpdateBounds, + UpdateFlags nUpdateFlags, + const ShapeAttributeLayerSharedPtr& pAttr, + const VectorOfDocTreeNodes& rSubsets, + bool bIsVisible ) const + { + // TODO(P1): For multiple views, it might pay off to reorg Shape and ViewShape, + // in that all the common setup steps here are refactored to Shape (would then + // have to be performed only _once_ per Shape paint). + + if( !bIsVisible ) + { + SAL_INFO("slideshow", "ViewShape::render(): skipping shape " << this ); + + // shape is invisible, no need to update anything. + return true; + } + + // since we have no sprite here, _any_ update request + // translates into a required redraw. + bool bRedrawRequired( mbForceUpdate || nUpdateFlags != UpdateFlags::NONE ); + + if( nUpdateFlags & UpdateFlags::Content ) + { + // TODO(P1): maybe provide some appearance change methods at + // the Renderer interface + + // force the renderer to be regenerated below, for the + // different attributes to take effect + invalidateRenderer(); + } + + mbForceUpdate = false; + + if( !bRedrawRequired ) + return true; + + SAL_INFO( "slideshow", "ViewShape::render(): rendering shape " << + this << + " at position (" << + rBounds.getMinX() << "," << + rBounds.getMinY() << ")" ); + + + // shape needs repaint - setup all that's needed + + + std::optional<basegfx::B2DPolyPolygon> aClip; + + if( pAttr ) + { + // setup clip poly + if( pAttr->isClipValid() ) + aClip = pAttr->getClip(); + + // emulate global shape alpha by first rendering into + // a temp bitmap, and then to screen (this would have + // been much easier if we'd be currently a sprite - + // see above) + if( pAttr->isAlphaValid() ) + { + const double nAlpha( pAttr->getAlpha() ); + + if( !::basegfx::fTools::equalZero( nAlpha ) && + !::rtl::math::approxEqual(nAlpha, 1.0) ) + { + // render with global alpha - have to prepare + // a bitmap, and render that with modulated + // alpha + + + const ::basegfx::B2DHomMatrix aTransform( + getShapeTransformation( rBounds, + pAttr ) ); + + // TODO(P1): Should the view transform some + // day contain rotation/shear, transforming + // the original bounds with the total + // transformation might result in smaller + // overall bounds. + + // determine output rect of _shape update + // area_ in device pixel + const ::basegfx::B2DHomMatrix aCanvasTransform( + rDestinationCanvas->getTransformation() ); + ::basegfx::B2DRectangle aTmpRect; + ::canvas::tools::calcTransformedRectBounds( aTmpRect, + rUpdateBounds, + aCanvasTransform ); + + // pixel size of cache bitmap: round up to + // nearest int + const ::basegfx::B2ISize aBmpSize( static_cast<sal_Int32>( aTmpRect.getWidth() )+1, + static_cast<sal_Int32>( aTmpRect.getHeight() )+1 ); + + // try to fetch temporary surface for alpha + // compositing (to achieve the global alpha + // blend effect, have to first render shape as + // a whole, then blit that surface with global + // alpha to the destination) + const RendererCacheVector::iterator aCompositingSurface( + getCacheEntry( rDestinationCanvas ) ); + + if( !aCompositingSurface->mpLastBitmapCanvas || + aCompositingSurface->mpLastBitmapCanvas->getSize() != aBmpSize ) + { + // create a bitmap of appropriate size + ::cppcanvas::BitmapSharedPtr pBitmap( + ::cppcanvas::BaseGfxFactory::createAlphaBitmap( + rDestinationCanvas, + aBmpSize ) ); + + ENSURE_OR_THROW(pBitmap, + "ViewShape::render(): Could not create compositing surface"); + + aCompositingSurface->mpDestinationCanvas = rDestinationCanvas; + aCompositingSurface->mpLastBitmap = pBitmap; + aCompositingSurface->mpLastBitmapCanvas = pBitmap->getBitmapCanvas(); + } + + // buffer aCompositingSurface iterator content + // - said one might get invalidated during + // draw() below. + ::cppcanvas::BitmapCanvasSharedPtr pBitmapCanvas( + aCompositingSurface->mpLastBitmapCanvas ); + + ::cppcanvas::BitmapSharedPtr pBitmap( + aCompositingSurface->mpLastBitmap); + + // setup bitmap canvas transformation - + // which happens to be the destination + // canvas transformation without any + // translational components. + + // But then, the render transformation as + // calculated by getShapeTransformation() + // above outputs the shape at its real + // destination position. Thus, we have to + // offset the output back to the origin, + // for which we simply plug in the + // negative position of the left, top edge + // of the shape's bound rect in device + // pixel into aLinearTransform below. + ::basegfx::B2DHomMatrix aAdjustedCanvasTransform( aCanvasTransform ); + aAdjustedCanvasTransform.translate( -aTmpRect.getMinX(), + -aTmpRect.getMinY() ); + + pBitmapCanvas->setTransformation( aAdjustedCanvasTransform ); + + // TODO(P2): If no update flags, or only + // alpha_update is set, we can save us the + // rendering into the bitmap (uh, it's not + // _that_ easy - for a forced redraw, + // e.g. when ending an animation, we always + // get UPDATE_FORCE here). + + // render into this bitmap + if( !draw( pBitmapCanvas, + rMtf, + pAttr, + aTransform, + !aClip ? nullptr : &(*aClip), + rSubsets ) ) + { + return false; + } + + // render bitmap to screen, with given global + // alpha. Since the bitmap already contains + // pixel-equivalent output, we have to use the + // inverse view transformation, adjusted with + // the final shape output position (note: + // cannot simply change the view + // transformation here, as that would affect a + // possibly set clip!) + ::basegfx::B2DHomMatrix aBitmapTransform( aCanvasTransform ); + OSL_ENSURE( aBitmapTransform.isInvertible(), + "ViewShape::render(): View transformation is singular!" ); + + aBitmapTransform.invert(); + + const basegfx::B2DHomMatrix aTranslation(basegfx::utils::createTranslateB2DHomMatrix( + aTmpRect.getMinX(), aTmpRect.getMinY())); + + aBitmapTransform = aBitmapTransform * aTranslation; + pBitmap->setTransformation( aBitmapTransform ); + + // finally, render bitmap alpha-modulated + pBitmap->drawAlphaModulated( nAlpha ); + + return true; + } + } + } + + // retrieve shape transformation, _with_ shape translation + // to actual page position. + const ::basegfx::B2DHomMatrix aTransform( + getShapeTransformation( rBounds, + pAttr ) ); + + return draw( rDestinationCanvas, + rMtf, + pAttr, + aTransform, + !aClip ? nullptr : &(*aClip), + rSubsets ); + } + + + ViewShape::ViewShape( const ViewLayerSharedPtr& rViewLayer ) : + mpViewLayer( rViewLayer ), + maRenderers(), + mpSprite(), + mbAnimationMode( false ), + mbForceUpdate( true ) + { + ENSURE_OR_THROW( mpViewLayer, "ViewShape::ViewShape(): Invalid View" ); + } + + const ViewLayerSharedPtr& ViewShape::getViewLayer() const + { + return mpViewLayer; + } + + ViewShape::RendererCacheVector::iterator ViewShape::getCacheEntry( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas ) const + { + // lookup destination canvas - is there already a renderer + // created for that target? + RendererCacheVector::iterator aIter; + const RendererCacheVector::iterator aEnd( maRenderers.end() ); + + // already there? + if( (aIter=::std::find_if( maRenderers.begin(), + aEnd, + [&rDestinationCanvas]( const RendererCacheEntry& rCacheEntry ) + { return rDestinationCanvas == rCacheEntry.getDestinationCanvas(); } ) ) == aEnd ) + { + if( maRenderers.size() >= MAX_RENDER_CACHE_ENTRIES ) + { + // cache size exceeded - prune entries. For now, + // simply remove the first one, which of course + // breaks for more complex access schemes. But in + // general, this leads to most recently used + // entries to reside at the end of the vector. + maRenderers.erase( maRenderers.begin() ); + + // ATTENTION: after this, both aIter and aEnd are + // invalid! + } + + // not yet in cache - add default-constructed cache + // entry, to have something to return + maRenderers.emplace_back( ); + aIter = maRenderers.end()-1; + } + + return aIter; + } + + ::cppcanvas::RendererSharedPtr ViewShape::getRenderer( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas, + const GDIMetaFileSharedPtr& rMtf, + const ShapeAttributeLayerSharedPtr& rAttr ) const + { + // lookup destination canvas - is there already a renderer + // created for that target? + const RendererCacheVector::iterator aIter( + getCacheEntry( rDestinationCanvas ) ); + + // now we have a valid entry, either way. call prefetch() + // on it, nevertheless - maybe the metafile changed, and + // the renderer still needs an update (prefetch() will + // detect that) + if( prefetch( *aIter, + rDestinationCanvas, + rMtf, + rAttr ) ) + { + return aIter->mpRenderer; + } + else + { + // prefetch failed - renderer is invalid + return ::cppcanvas::RendererSharedPtr(); + } + } + + void ViewShape::invalidateRenderer() const + { + // simply clear the cache. Subsequent getRenderer() calls + // will regenerate the Renderers. + maRenderers.clear(); + } + + ::basegfx::B2DSize ViewShape::getAntialiasingBorder() const + { + ENSURE_OR_THROW( mpViewLayer->getCanvas(), + "ViewShape::getAntialiasingBorder(): Invalid ViewLayer canvas" ); + + const ::basegfx::B2DHomMatrix& rViewTransform( + mpViewLayer->getTransformation() ); + + // TODO(F1): As a quick shortcut (did not want to invert + // whole matrix here), taking only scale components of + // view transformation matrix. This will be wrong when + // e.g. shearing is involved. + const double nXBorder( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE / rViewTransform.get(0,0) ); + const double nYBorder( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE / rViewTransform.get(1,1) ); + + return ::basegfx::B2DSize( nXBorder, + nYBorder ); + } + + void ViewShape::enterAnimationMode() + { + mbForceUpdate = true; + mbAnimationMode = true; + } + + void ViewShape::leaveAnimationMode() + { + mpSprite.reset(); + mbAnimationMode = false; + mbForceUpdate = true; + } + + bool ViewShape::update( const GDIMetaFileSharedPtr& rMtf, + const RenderArgs& rArgs, + UpdateFlags nUpdateFlags, + bool bIsVisible ) const + { + ENSURE_OR_RETURN_FALSE( mpViewLayer->getCanvas(), "ViewShape::update(): Invalid layer canvas" ); + + // Shall we render to a sprite, or to a plain canvas? + if( mbAnimationMode ) + return renderSprite( mpViewLayer, + rMtf, + rArgs.maOrigBounds, + rArgs.maBounds, + rArgs.maUnitBounds, + nUpdateFlags, + rArgs.mrAttr, + rArgs.mrSubsets, + rArgs.mnShapePriority, + bIsVisible ); + else + return render( mpViewLayer->getCanvas(), + rMtf, + rArgs.maBounds, + rArgs.maUpdateBounds, + nUpdateFlags, + rArgs.mrAttr, + rArgs.mrSubsets, + bIsVisible ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |