| /************************************************************** |
| * |
| * 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 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| *************************************************************/ |
| |
| |
| |
| // MARKER(update_precomp.py): autogen include statement, do not remove |
| #include "precompiled_cppcanvas.hxx" |
| |
| #include <canvas/debug.hxx> |
| #include <tools/diagnose_ex.h> |
| #include <canvas/verbosetrace.hxx> |
| #include <osl/mutex.hxx> |
| #include <vos/mutex.hxx> |
| #include <vcl/svapp.hxx> |
| #include <rtl/logfile.hxx> |
| #include <comphelper/sequence.hxx> |
| #include <comphelper/anytostring.hxx> |
| #include <cppuhelper/exc_hlp.hxx> |
| #include <cppcanvas/canvas.hxx> |
| #include <com/sun/star/rendering/XGraphicDevice.hpp> |
| #include <com/sun/star/rendering/TexturingMode.hpp> |
| #include <com/sun/star/uno/Sequence.hxx> |
| #include <com/sun/star/geometry/RealPoint2D.hpp> |
| #include <com/sun/star/rendering/PanoseProportion.hpp> |
| #include <com/sun/star/rendering/ViewState.hpp> |
| #include <com/sun/star/rendering/RenderState.hpp> |
| #include <com/sun/star/rendering/XCanvasFont.hpp> |
| #include <com/sun/star/rendering/XPolyPolygon2D.hpp> |
| #include <com/sun/star/rendering/XCanvas.hpp> |
| #include <com/sun/star/rendering/PathCapType.hpp> |
| #include <com/sun/star/rendering/PathJoinType.hpp> |
| #include <basegfx/tools/canvastools.hxx> |
| #include <basegfx/tools/gradienttools.hxx> |
| #include <basegfx/numeric/ftools.hxx> |
| #include <basegfx/polygon/b2dpolypolygontools.hxx> |
| #include <basegfx/polygon/b2dpolygontools.hxx> |
| #include <basegfx/polygon/b2dpolygon.hxx> |
| #include <basegfx/polygon/b2dpolypolygon.hxx> |
| #include <basegfx/matrix/b2dhommatrix.hxx> |
| #include <basegfx/vector/b2dsize.hxx> |
| #include <basegfx/range/b2drectangle.hxx> |
| #include <basegfx/point/b2dpoint.hxx> |
| #include <basegfx/tuple/b2dtuple.hxx> |
| #include <basegfx/polygon/b2dpolygonclipper.hxx> |
| #include <basegfx/polygon/b2dpolypolygoncutter.hxx> |
| #include <canvas/canvastools.hxx> |
| #include <vcl/canvastools.hxx> |
| #include <vcl/salbtype.hxx> |
| #include <vcl/gdimtf.hxx> |
| #include <vcl/metaact.hxx> |
| #include <vcl/virdev.hxx> |
| #include <vcl/metric.hxx> |
| #include <vcl/graphictools.hxx> |
| #include <tools/poly.hxx> |
| #include <i18npool/mslangid.hxx> |
| #include <implrenderer.hxx> |
| #include <tools.hxx> |
| #include <outdevstate.hxx> |
| #include <action.hxx> |
| #include <bitmapaction.hxx> |
| #include <lineaction.hxx> |
| #include <pointaction.hxx> |
| #include <polypolyaction.hxx> |
| #include <textaction.hxx> |
| #include <transparencygroupaction.hxx> |
| #include <vector> |
| #include <algorithm> |
| #include <iterator> |
| #include <boost/scoped_array.hpp> |
| #include "mtftools.hxx" |
| #include "outdevstate.hxx" |
| #include <basegfx/matrix/b2dhommatrixtools.hxx> |
| |
| |
| using namespace ::com::sun::star; |
| |
| |
| // free support functions |
| // ====================== |
| namespace |
| { |
| template < class MetaActionType > void setStateColor( MetaActionType* pAct, |
| bool& rIsColorSet, |
| uno::Sequence< double >& rColorSequence, |
| const cppcanvas::CanvasSharedPtr& rCanvas ) |
| { |
| // set rIsColorSet and check for true at the same time |
| if( (rIsColorSet=pAct->IsSetting()) != false ) |
| { |
| ::Color aColor( pAct->GetColor() ); |
| |
| // force alpha part of color to |
| // opaque. transparent painting is done |
| // explicitly via META_TRANSPARENT_ACTION |
| aColor.SetTransparency(0); |
| //aColor.SetTransparency(128); |
| |
| rColorSequence = ::vcl::unotools::colorToDoubleSequence( |
| aColor, |
| rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() ); |
| } |
| } |
| |
| |
| // state stack manipulators |
| // ------------------------ |
| void clearStateStack( ::cppcanvas::internal::VectorOfOutDevStates& rStates ) |
| { |
| rStates.clear(); |
| const ::cppcanvas::internal::OutDevState aDefaultState; |
| rStates.push_back( aDefaultState ); |
| } |
| |
| ::cppcanvas::internal::OutDevState& getState( ::cppcanvas::internal::VectorOfOutDevStates& rStates ) |
| { |
| return rStates.back(); |
| } |
| |
| const ::cppcanvas::internal::OutDevState& getState( const ::cppcanvas::internal::VectorOfOutDevStates& rStates ) |
| { |
| return rStates.back(); |
| } |
| |
| void pushState( ::cppcanvas::internal::VectorOfOutDevStates& rStates, |
| sal_uInt16 nFlags ) |
| { |
| rStates.push_back( getState( rStates ) ); |
| getState( rStates ).pushFlags = nFlags; |
| } |
| |
| void popState( ::cppcanvas::internal::VectorOfOutDevStates& rStates ) |
| { |
| if( getState( rStates ).pushFlags != PUSH_ALL ) |
| { |
| // a state is pushed which is incomplete, i.e. does not |
| // restore everything to the previous stack level when |
| // popped. |
| // That means, we take the old state, and restore every |
| // OutDevState member whose flag is set, from the new to the |
| // old state. Then the new state gets overwritten by the |
| // calculated state |
| |
| // preset to-be-calculated new state with old state |
| ::cppcanvas::internal::OutDevState aCalculatedNewState( getState( rStates ) ); |
| |
| // selectively copy to-be-restored content over saved old |
| // state |
| rStates.pop_back(); |
| |
| const ::cppcanvas::internal::OutDevState& rNewState( getState( rStates ) ); |
| |
| if( (aCalculatedNewState.pushFlags & PUSH_LINECOLOR) ) |
| { |
| aCalculatedNewState.lineColor = rNewState.lineColor; |
| aCalculatedNewState.isLineColorSet = rNewState.isLineColorSet; |
| } |
| |
| if( (aCalculatedNewState.pushFlags & PUSH_FILLCOLOR) ) |
| { |
| aCalculatedNewState.fillColor = rNewState.fillColor; |
| aCalculatedNewState.isFillColorSet = rNewState.isFillColorSet; |
| } |
| |
| if( (aCalculatedNewState.pushFlags & PUSH_FONT) ) |
| { |
| aCalculatedNewState.xFont = rNewState.xFont; |
| aCalculatedNewState.fontRotation = rNewState.fontRotation; |
| aCalculatedNewState.textReliefStyle = rNewState.textReliefStyle; |
| aCalculatedNewState.textOverlineStyle = rNewState.textOverlineStyle; |
| aCalculatedNewState.textUnderlineStyle = rNewState.textUnderlineStyle; |
| aCalculatedNewState.textStrikeoutStyle = rNewState.textStrikeoutStyle; |
| aCalculatedNewState.textEmphasisMarkStyle = rNewState.textEmphasisMarkStyle; |
| aCalculatedNewState.isTextEffectShadowSet = rNewState.isTextEffectShadowSet; |
| aCalculatedNewState.isTextWordUnderlineSet = rNewState.isTextWordUnderlineSet; |
| aCalculatedNewState.isTextOutlineModeSet = rNewState.isTextOutlineModeSet; |
| } |
| |
| if( (aCalculatedNewState.pushFlags & PUSH_TEXTCOLOR) ) |
| { |
| aCalculatedNewState.textColor = rNewState.textColor; |
| } |
| |
| if( (aCalculatedNewState.pushFlags & PUSH_MAPMODE) ) |
| { |
| aCalculatedNewState.mapModeTransform = rNewState.mapModeTransform; |
| } |
| |
| if( (aCalculatedNewState.pushFlags & PUSH_CLIPREGION) ) |
| { |
| aCalculatedNewState.clip = rNewState.clip; |
| aCalculatedNewState.clipRect = rNewState.clipRect; |
| aCalculatedNewState.xClipPoly = rNewState.xClipPoly; |
| } |
| |
| // TODO(F2): Raster ops NYI |
| // if( (aCalculatedNewState.pushFlags & PUSH_RASTEROP) ) |
| // { |
| // } |
| |
| if( (aCalculatedNewState.pushFlags & PUSH_TEXTFILLCOLOR) ) |
| { |
| aCalculatedNewState.textFillColor = rNewState.textFillColor; |
| aCalculatedNewState.isTextFillColorSet = rNewState.isTextFillColorSet; |
| } |
| |
| if( (aCalculatedNewState.pushFlags & PUSH_TEXTALIGN) ) |
| { |
| aCalculatedNewState.textReferencePoint = rNewState.textReferencePoint; |
| } |
| |
| // TODO(F1): Refpoint handling NYI |
| // if( (aCalculatedNewState.pushFlags & PUSH_REFPOINT) ) |
| // { |
| // } |
| |
| if( (aCalculatedNewState.pushFlags & PUSH_TEXTLINECOLOR) ) |
| { |
| aCalculatedNewState.textLineColor = rNewState.textLineColor; |
| aCalculatedNewState.isTextLineColorSet = rNewState.isTextLineColorSet; |
| } |
| |
| if( (aCalculatedNewState.pushFlags & PUSH_TEXTLAYOUTMODE) ) |
| { |
| aCalculatedNewState.textAlignment = rNewState.textAlignment; |
| aCalculatedNewState.textDirection = rNewState.textDirection; |
| } |
| |
| // TODO(F2): Text language handling NYI |
| // if( (aCalculatedNewState.pushFlags & PUSH_TEXTLANGUAGE) ) |
| // { |
| // } |
| |
| // always copy push mode |
| aCalculatedNewState.pushFlags = rNewState.pushFlags; |
| |
| // flush to stack |
| getState( rStates ) = aCalculatedNewState; |
| } |
| else |
| { |
| rStates.pop_back(); |
| } |
| } |
| |
| void setupStrokeAttributes( rendering::StrokeAttributes& o_rStrokeAttributes, |
| const ::cppcanvas::internal::ActionFactoryParameters& rParms, |
| const LineInfo& rLineInfo ) |
| { |
| const ::basegfx::B2DSize aWidth( rLineInfo.GetWidth(), 0 ); |
| o_rStrokeAttributes.StrokeWidth = |
| (getState( rParms.mrStates ).mapModeTransform * aWidth).getX(); |
| |
| // setup reasonable defaults |
| o_rStrokeAttributes.MiterLimit = 15.0; // 1.0 was no good default; GDI+'s limit is 10.0, our's is 15.0 |
| o_rStrokeAttributes.StartCapType = rendering::PathCapType::BUTT; |
| o_rStrokeAttributes.EndCapType = rendering::PathCapType::BUTT; |
| |
| switch(rLineInfo.GetLineJoin()) |
| { |
| default: // B2DLINEJOIN_NONE, B2DLINEJOIN_MIDDLE |
| o_rStrokeAttributes.JoinType = rendering::PathJoinType::NONE; |
| break; |
| case basegfx::B2DLINEJOIN_BEVEL: |
| o_rStrokeAttributes.JoinType = rendering::PathJoinType::BEVEL; |
| break; |
| case basegfx::B2DLINEJOIN_MITER: |
| o_rStrokeAttributes.JoinType = rendering::PathJoinType::MITER; |
| break; |
| case basegfx::B2DLINEJOIN_ROUND: |
| o_rStrokeAttributes.JoinType = rendering::PathJoinType::ROUND; |
| break; |
| } |
| |
| switch(rLineInfo.GetLineCap()) |
| { |
| default: /* com::sun::star::drawing::LineCap_BUTT */ |
| { |
| o_rStrokeAttributes.StartCapType = rendering::PathCapType::BUTT; |
| o_rStrokeAttributes.EndCapType = rendering::PathCapType::BUTT; |
| break; |
| } |
| case com::sun::star::drawing::LineCap_ROUND: |
| { |
| o_rStrokeAttributes.StartCapType = rendering::PathCapType::ROUND; |
| o_rStrokeAttributes.EndCapType = rendering::PathCapType::ROUND; |
| break; |
| } |
| case com::sun::star::drawing::LineCap_SQUARE: |
| { |
| o_rStrokeAttributes.StartCapType = rendering::PathCapType::SQUARE; |
| o_rStrokeAttributes.EndCapType = rendering::PathCapType::SQUARE; |
| break; |
| } |
| } |
| |
| if( LINE_DASH == rLineInfo.GetStyle() ) |
| { |
| const ::cppcanvas::internal::OutDevState& rState( getState( rParms.mrStates ) ); |
| |
| // TODO(F1): Interpret OutDev::GetRefPoint() for the start of the dashing. |
| |
| // interpret dash info only if explicitly enabled as |
| // style |
| const ::basegfx::B2DSize aDistance( rLineInfo.GetDistance(), 0 ); |
| const double nDistance( (rState.mapModeTransform * aDistance).getX() ); |
| |
| const ::basegfx::B2DSize aDashLen( rLineInfo.GetDashLen(), 0 ); |
| const double nDashLen( (rState.mapModeTransform * aDashLen).getX() ); |
| |
| const ::basegfx::B2DSize aDotLen( rLineInfo.GetDotLen(), 0 ); |
| const double nDotLen( (rState.mapModeTransform * aDotLen).getX() ); |
| |
| const sal_Int32 nNumArryEntries( 2*rLineInfo.GetDashCount() + |
| 2*rLineInfo.GetDotCount() ); |
| |
| o_rStrokeAttributes.DashArray.realloc( nNumArryEntries ); |
| double* pDashArray = o_rStrokeAttributes.DashArray.getArray(); |
| |
| |
| // iteratively fill dash array, first with dashs, then |
| // with dots. |
| // =================================================== |
| |
| sal_Int32 nCurrEntry=0; |
| |
| for( sal_Int32 i=0; i<rLineInfo.GetDashCount(); ++i ) |
| { |
| pDashArray[nCurrEntry++] = nDashLen; |
| pDashArray[nCurrEntry++] = nDistance; |
| } |
| for( sal_Int32 i=0; i<rLineInfo.GetDotCount(); ++i ) |
| { |
| pDashArray[nCurrEntry++] = nDotLen; |
| pDashArray[nCurrEntry++] = nDistance; |
| } |
| } |
| } |
| |
| |
| /** Create masked BitmapEx, where the white areas of rBitmap are |
| transparent, and the other appear in rMaskColor. |
| */ |
| BitmapEx createMaskBmpEx( const Bitmap& rBitmap, |
| const ::Color& rMaskColor ) |
| { |
| const ::Color aWhite( COL_WHITE ); |
| BitmapPalette aBiLevelPalette(2); |
| aBiLevelPalette[0] = aWhite; |
| aBiLevelPalette[1] = rMaskColor; |
| |
| Bitmap aMask( rBitmap.CreateMask( aWhite )); |
| Bitmap aSolid( rBitmap.GetSizePixel(), |
| 1, |
| &aBiLevelPalette ); |
| aSolid.Erase( rMaskColor ); |
| |
| return BitmapEx( aSolid, aMask ); |
| } |
| |
| /** Shameless rip from vcl/source/gdi/outdev3.cxx |
| |
| Should consolidate, into something like basetxt... |
| */ |
| sal_Unicode getLocalizedChar( sal_Unicode nChar, LanguageType eLang ) |
| { |
| // currently only conversion from ASCII digits is interesting |
| if( (nChar < '0') || ('9' < nChar) ) |
| return nChar; |
| |
| sal_Unicode nOffset(0); |
| // eLang & LANGUAGE_MASK_PRIMARY catches language independent of region. |
| // CAVEAT! To some like Mongolian MS assigned the same primary language |
| // although the script type is different! |
| switch( eLang & LANGUAGE_MASK_PRIMARY ) |
| { |
| default: |
| break; |
| |
| case LANGUAGE_ARABIC_SAUDI_ARABIA & LANGUAGE_MASK_PRIMARY: |
| case LANGUAGE_URDU & LANGUAGE_MASK_PRIMARY: |
| case LANGUAGE_PUNJABI & LANGUAGE_MASK_PRIMARY: //??? |
| nOffset = 0x0660 - '0'; // arabic/persian/urdu |
| break; |
| case LANGUAGE_BENGALI & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x09E6 - '0'; // bengali |
| break; |
| case LANGUAGE_BURMESE & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x1040 - '0'; // burmese |
| break; |
| case LANGUAGE_HINDI & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0966 - '0'; // devanagari |
| break; |
| case LANGUAGE_GUJARATI & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0AE6 - '0'; // gujarati |
| break; |
| case LANGUAGE_KANNADA & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0CE6 - '0'; // kannada |
| break; |
| case LANGUAGE_KHMER & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x17E0 - '0'; // khmer |
| break; |
| case LANGUAGE_LAO & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0ED0 - '0'; // lao |
| break; |
| case LANGUAGE_MALAYALAM & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0D66 - '0'; // malayalam |
| break; |
| case LANGUAGE_MONGOLIAN & LANGUAGE_MASK_PRIMARY: |
| if (eLang == LANGUAGE_MONGOLIAN_MONGOLIAN) |
| nOffset = 0x1810 - '0'; // mongolian |
| else |
| nOffset = 0; // mongolian cyrillic |
| break; |
| case LANGUAGE_ORIYA & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0B66 - '0'; // oriya |
| break; |
| case LANGUAGE_TAMIL & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0BE7 - '0'; // tamil |
| break; |
| case LANGUAGE_TELUGU & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0C66 - '0'; // telugu |
| break; |
| case LANGUAGE_THAI & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0E50 - '0'; // thai |
| break; |
| case LANGUAGE_TIBETAN & LANGUAGE_MASK_PRIMARY: |
| nOffset = 0x0F20 - '0'; // tibetan |
| break; |
| } |
| |
| nChar = sal::static_int_cast<sal_Unicode>(nChar + nOffset); |
| return nChar; |
| } |
| |
| void convertToLocalizedNumerals( XubString& rStr, |
| LanguageType eTextLanguage ) |
| { |
| const sal_Unicode* pBase = rStr.GetBuffer(); |
| const sal_Unicode* pBegin = pBase + 0; |
| const xub_StrLen nEndIndex = rStr.Len(); |
| const sal_Unicode* pEnd = pBase + nEndIndex; |
| |
| for( ; pBegin < pEnd; ++pBegin ) |
| { |
| // TODO: are there non-digit localizations? |
| if( (*pBegin >= '0') && (*pBegin <= '9') ) |
| { |
| // translate characters to local preference |
| sal_Unicode cChar = getLocalizedChar( *pBegin, eTextLanguage ); |
| if( cChar != *pBegin ) |
| rStr.SetChar( sal::static_int_cast<sal_uInt16>(pBegin - pBase), cChar ); |
| } |
| } |
| } |
| } |
| |
| |
| namespace cppcanvas |
| { |
| namespace internal |
| { |
| bool ImplRenderer::createFillAndStroke( const ::basegfx::B2DPolyPolygon& rPolyPoly, |
| const ActionFactoryParameters& rParms ) |
| { |
| const OutDevState& rState( getState( rParms.mrStates ) ); |
| if( (!rState.isLineColorSet && |
| !rState.isFillColorSet) || |
| (rState.lineColor.getLength() == 0 && |
| rState.fillColor.getLength() == 0) ) |
| { |
| return false; |
| } |
| |
| ActionSharedPtr pPolyAction( |
| internal::PolyPolyActionFactory::createPolyPolyAction( |
| rPolyPoly, rParms.mrCanvas, rState ) ); |
| |
| if( pPolyAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pPolyAction, |
| rParms.mrCurrActionIndex ) ); |
| |
| rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1; |
| } |
| |
| return true; |
| } |
| |
| bool ImplRenderer::createFillAndStroke( const ::basegfx::B2DPolygon& rPoly, |
| const ActionFactoryParameters& rParms ) |
| { |
| return createFillAndStroke( ::basegfx::B2DPolyPolygon( rPoly ), |
| rParms ); |
| } |
| |
| void ImplRenderer::skipContent( GDIMetaFile& rMtf, |
| const char* pCommentString, |
| sal_Int32& io_rCurrActionIndex ) const |
| { |
| ENSURE_OR_THROW( pCommentString, |
| "ImplRenderer::skipContent(): NULL string given" ); |
| |
| MetaAction* pCurrAct; |
| while( (pCurrAct=rMtf.NextAction()) != NULL ) |
| { |
| // increment action index, we've skipped an action. |
| ++io_rCurrActionIndex; |
| |
| if( pCurrAct->GetType() == META_COMMENT_ACTION && |
| static_cast<MetaCommentAction*>(pCurrAct)->GetComment().CompareIgnoreCaseToAscii( |
| pCommentString ) == COMPARE_EQUAL ) |
| { |
| // requested comment found, done |
| return; |
| } |
| } |
| |
| // EOF |
| return; |
| } |
| |
| bool ImplRenderer::isActionContained( GDIMetaFile& rMtf, |
| const char* pCommentString, |
| sal_uInt16 nType ) const |
| { |
| ENSURE_OR_THROW( pCommentString, |
| "ImplRenderer::isActionContained(): NULL string given" ); |
| |
| bool bRet( false ); |
| |
| // at least _one_ call to GDIMetaFile::NextAction() is |
| // executed |
| sal_uIntPtr nPos( 1 ); |
| |
| MetaAction* pCurrAct; |
| while( (pCurrAct=rMtf.NextAction()) != NULL ) |
| { |
| if( pCurrAct->GetType() == nType ) |
| { |
| bRet = true; // action type found |
| break; |
| } |
| |
| if( pCurrAct->GetType() == META_COMMENT_ACTION && |
| static_cast<MetaCommentAction*>(pCurrAct)->GetComment().CompareIgnoreCaseToAscii( |
| pCommentString ) == COMPARE_EQUAL ) |
| { |
| // delimiting end comment found, done |
| bRet = false; // not yet found |
| break; |
| } |
| |
| ++nPos; |
| } |
| |
| // rewind metafile to previous position (this method must |
| // not change the current metaaction) |
| while( nPos-- ) |
| rMtf.WindPrev(); |
| |
| if( !pCurrAct ) |
| { |
| // EOF, and not yet found |
| bRet = false; |
| } |
| |
| return bRet; |
| } |
| |
| void ImplRenderer::createGradientAction( const ::PolyPolygon& rPoly, |
| const ::Gradient& rGradient, |
| const ActionFactoryParameters& rParms, |
| bool bIsPolygonRectangle, |
| bool bSubsettableActions ) |
| { |
| DBG_TESTSOLARMUTEX(); |
| |
| ::basegfx::B2DPolyPolygon aDevicePoly( rPoly.getB2DPolyPolygon() ); |
| aDevicePoly.transform( getState( rParms.mrStates ).mapModeTransform ); |
| |
| // decide, whether this gradient can be rendered natively |
| // by the canvas, or must be emulated via VCL gradient |
| // action extraction. |
| const sal_uInt16 nSteps( rGradient.GetSteps() ); |
| |
| if( // step count is infinite, can use native canvas |
| // gradients here |
| nSteps == 0 || |
| // step count is sufficiently high, such that no |
| // discernible difference should be visible. |
| nSteps > 64 ) |
| { |
| uno::Reference< lang::XMultiServiceFactory> xFactory( |
| rParms.mrCanvas->getUNOCanvas()->getDevice()->getParametricPolyPolygonFactory() ); |
| |
| if( xFactory.is() ) |
| { |
| rendering::Texture aTexture; |
| |
| aTexture.RepeatModeX = rendering::TexturingMode::CLAMP; |
| aTexture.RepeatModeY = rendering::TexturingMode::CLAMP; |
| aTexture.Alpha = 1.0; |
| |
| |
| // setup start/end color values |
| // ---------------------------- |
| |
| // scale color coefficients with gradient intensities |
| const sal_uInt16 nStartIntensity( rGradient.GetStartIntensity() ); |
| ::Color aVCLStartColor( rGradient.GetStartColor() ); |
| aVCLStartColor.SetRed( (sal_uInt8)(aVCLStartColor.GetRed() * nStartIntensity / 100) ); |
| aVCLStartColor.SetGreen( (sal_uInt8)(aVCLStartColor.GetGreen() * nStartIntensity / 100) ); |
| aVCLStartColor.SetBlue( (sal_uInt8)(aVCLStartColor.GetBlue() * nStartIntensity / 100) ); |
| |
| const sal_uInt16 nEndIntensity( rGradient.GetEndIntensity() ); |
| ::Color aVCLEndColor( rGradient.GetEndColor() ); |
| aVCLEndColor.SetRed( (sal_uInt8)(aVCLEndColor.GetRed() * nEndIntensity / 100) ); |
| aVCLEndColor.SetGreen( (sal_uInt8)(aVCLEndColor.GetGreen() * nEndIntensity / 100) ); |
| aVCLEndColor.SetBlue( (sal_uInt8)(aVCLEndColor.GetBlue() * nEndIntensity / 100) ); |
| |
| uno::Reference<rendering::XColorSpace> xColorSpace( |
| rParms.mrCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace()); |
| const uno::Sequence< double > aStartColor( |
| ::vcl::unotools::colorToDoubleSequence( aVCLStartColor, |
| xColorSpace )); |
| const uno::Sequence< double > aEndColor( |
| ::vcl::unotools::colorToDoubleSequence( aVCLEndColor, |
| xColorSpace )); |
| |
| uno::Sequence< uno::Sequence < double > > aColors(2); |
| uno::Sequence< double > aStops(2); |
| |
| if( rGradient.GetStyle() == GRADIENT_AXIAL ) |
| { |
| aStops.realloc(3); |
| aColors.realloc(3); |
| |
| aStops[0] = 0.0; |
| aStops[1] = 0.5; |
| aStops[2] = 1.0; |
| |
| aColors[0] = aEndColor; |
| aColors[1] = aStartColor; |
| aColors[2] = aEndColor; |
| } |
| else |
| { |
| aStops[0] = 0.0; |
| aStops[1] = 1.0; |
| |
| aColors[0] = aStartColor; |
| aColors[1] = aEndColor; |
| } |
| |
| const ::basegfx::B2DRectangle aBounds( |
| ::basegfx::tools::getRange(aDevicePoly) ); |
| const ::basegfx::B2DVector aOffset( |
| rGradient.GetOfsX() / 100.0, |
| rGradient.GetOfsY() / 100.0); |
| double fRotation( rGradient.GetAngle() * M_PI / 1800.0 ); |
| const double fBorder( rGradient.GetBorder() / 100.0 ); |
| |
| basegfx::B2DHomMatrix aRot90; |
| aRot90.rotate(M_PI_2); |
| |
| basegfx::ODFGradientInfo aGradInfo; |
| rtl::OUString aGradientService; |
| switch( rGradient.GetStyle() ) |
| { |
| case GRADIENT_LINEAR: |
| aGradInfo = basegfx::tools::createLinearODFGradientInfo( |
| aBounds, |
| nSteps, |
| fBorder, |
| fRotation); |
| // map odf to svg gradient orientation - x |
| // instead of y direction |
| aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aRot90); |
| aGradientService = rtl::OUString::createFromAscii("LinearGradient"); |
| break; |
| |
| case GRADIENT_AXIAL: |
| { |
| // Adapt the border so that it is suitable |
| // for the axial gradient. An axial |
| // gradient consists of two linear |
| // gradients. Each of those covers half |
| // of the total size. In order to |
| // compensate for the condensed display of |
| // the linear gradients, we have to |
| // enlarge the area taken up by the actual |
| // gradient (1-fBorder). After that we |
| // have to turn the result back into a |
| // border value, hence the second (left |
| // most 1-... |
| const double fAxialBorder (1-2*(1-fBorder)); |
| aGradInfo = basegfx::tools::createAxialODFGradientInfo( |
| aBounds, |
| nSteps, |
| fAxialBorder, |
| fRotation); |
| // map odf to svg gradient orientation - x |
| // instead of y direction |
| aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aRot90); |
| |
| // map odf axial gradient to 3-stop linear |
| // gradient - shift left by 0.5 |
| basegfx::B2DHomMatrix aShift; |
| |
| aShift.translate(-0.5,0); |
| aGradInfo.setTextureTransform(aGradInfo.getTextureTransform() * aShift); |
| aGradientService = rtl::OUString::createFromAscii("LinearGradient"); |
| break; |
| } |
| |
| case GRADIENT_RADIAL: |
| aGradInfo = basegfx::tools::createRadialODFGradientInfo( |
| aBounds, |
| aOffset, |
| nSteps, |
| fBorder); |
| aGradientService = rtl::OUString::createFromAscii("EllipticalGradient"); |
| break; |
| |
| case GRADIENT_ELLIPTICAL: |
| aGradInfo = basegfx::tools::createEllipticalODFGradientInfo( |
| aBounds, |
| aOffset, |
| nSteps, |
| fBorder, |
| fRotation); |
| aGradientService = rtl::OUString::createFromAscii("EllipticalGradient"); |
| break; |
| |
| case GRADIENT_SQUARE: |
| aGradInfo = basegfx::tools::createSquareODFGradientInfo( |
| aBounds, |
| aOffset, |
| nSteps, |
| fBorder, |
| fRotation); |
| aGradientService = rtl::OUString::createFromAscii("RectangularGradient"); |
| break; |
| |
| case GRADIENT_RECT: |
| aGradInfo = basegfx::tools::createRectangularODFGradientInfo( |
| aBounds, |
| aOffset, |
| nSteps, |
| fBorder, |
| fRotation); |
| aGradientService = rtl::OUString::createFromAscii("RectangularGradient"); |
| break; |
| |
| default: |
| ENSURE_OR_THROW( false, |
| "ImplRenderer::createGradientAction(): Unexpected gradient type" ); |
| break; |
| } |
| |
| // As the texture coordinate space is relative to |
| // the polygon coordinate space (NOT to the |
| // polygon itself), move gradient to the start of |
| // the actual polygon. If we skip this, the |
| // gradient will always display at the origin, and |
| // not within the polygon bound (which might be |
| // miles away from the origin). |
| aGradInfo.setTextureTransform( |
| basegfx::tools::createTranslateB2DHomMatrix( |
| aBounds.getMinX(), |
| aBounds.getMinY()) * aGradInfo.getTextureTransform()); |
| ::basegfx::unotools::affineMatrixFromHomMatrix( aTexture.AffineTransform, |
| aGradInfo.getTextureTransform() ); |
| |
| uno::Sequence<uno::Any> args(3); |
| beans::PropertyValue aProp; |
| aProp.Name = rtl::OUString::createFromAscii("Colors"); |
| aProp.Value <<= aColors; |
| args[0] <<= aProp; |
| aProp.Name = rtl::OUString::createFromAscii("Stops"); |
| aProp.Value <<= aStops; |
| args[1] <<= aProp; |
| aProp.Name = rtl::OUString::createFromAscii("AspectRatio"); |
| aProp.Value <<= aGradInfo.getAspectRatio(); |
| args[2] <<= aProp; |
| |
| aTexture.Gradient.set( |
| xFactory->createInstanceWithArguments(aGradientService, |
| args), |
| uno::UNO_QUERY); |
| if( aTexture.Gradient.is() ) |
| { |
| ActionSharedPtr pPolyAction( |
| internal::PolyPolyActionFactory::createPolyPolyAction( |
| aDevicePoly, |
| rParms.mrCanvas, |
| getState( rParms.mrStates ), |
| aTexture ) ); |
| |
| if( pPolyAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pPolyAction, |
| rParms.mrCurrActionIndex ) ); |
| |
| rParms.mrCurrActionIndex += pPolyAction->getActionCount()-1; |
| } |
| |
| // done, using native gradients |
| return; |
| } |
| } |
| } |
| |
| // cannot currently use native canvas gradients, as a |
| // finite step size is given (this funny feature is not |
| // supported by the XCanvas API) |
| pushState( rParms.mrStates, PUSH_ALL ); |
| |
| if( !bIsPolygonRectangle ) |
| { |
| // only clip, if given polygon is not a rectangle in |
| // the first place (the gradient is always limited to |
| // the given bound rect) |
| updateClipping( |
| aDevicePoly, |
| rParms, |
| true ); |
| } |
| |
| GDIMetaFile aTmpMtf; |
| rParms.mrVDev.AddGradientActions( rPoly.GetBoundRect(), |
| rGradient, |
| aTmpMtf ); |
| |
| createActions( aTmpMtf, rParms, bSubsettableActions ); |
| |
| popState( rParms.mrStates ); |
| } |
| |
| uno::Reference< rendering::XCanvasFont > ImplRenderer::createFont( double& o_rFontRotation, |
| const ::Font& rFont, |
| const ActionFactoryParameters& rParms ) const |
| { |
| rendering::FontRequest aFontRequest; |
| |
| if( rParms.mrParms.maFontName.is_initialized() ) |
| aFontRequest.FontDescription.FamilyName = *rParms.mrParms.maFontName; |
| else |
| aFontRequest.FontDescription.FamilyName = rFont.GetName(); |
| |
| aFontRequest.FontDescription.StyleName = rFont.GetStyleName(); |
| |
| aFontRequest.FontDescription.IsSymbolFont = (rFont.GetCharSet() == RTL_TEXTENCODING_SYMBOL) ? util::TriState_YES : util::TriState_NO; |
| aFontRequest.FontDescription.IsVertical = rFont.IsVertical() ? util::TriState_YES : util::TriState_NO; |
| |
| // TODO(F2): improve vclenum->panose conversion |
| aFontRequest.FontDescription.FontDescription.Weight = |
| rParms.mrParms.maFontWeight.is_initialized() ? |
| *rParms.mrParms.maFontWeight : |
| ::canvas::tools::numeric_cast<sal_Int8>( ::basegfx::fround( rFont.GetWeight() ) ); |
| aFontRequest.FontDescription.FontDescription.Letterform = |
| rParms.mrParms.maFontLetterForm.is_initialized() ? |
| *rParms.mrParms.maFontLetterForm : |
| (rFont.GetItalic() == ITALIC_NONE) ? 0 : 9; |
| aFontRequest.FontDescription.FontDescription.Proportion = |
| rParms.mrParms.maFontProportion.is_initialized() ? |
| *rParms.mrParms.maFontProportion : |
| (rFont.GetPitch() == PITCH_FIXED) |
| ? rendering::PanoseProportion::MONO_SPACED |
| : rendering::PanoseProportion::ANYTHING; |
| |
| LanguageType aLang = rFont.GetLanguage(); |
| aFontRequest.Locale = MsLangId::convertLanguageToLocale(aLang, false); |
| |
| // setup state-local text transformation, |
| // if the font be rotated |
| const short nFontAngle( rFont.GetOrientation() ); |
| if( nFontAngle != 0 ) |
| { |
| // set to unity transform rotated by font angle |
| const double nAngle( nFontAngle * (F_PI / 1800.0) ); |
| o_rFontRotation = -nAngle; |
| } |
| else |
| { |
| o_rFontRotation = 0.0; |
| } |
| |
| geometry::Matrix2D aFontMatrix; |
| ::canvas::tools::setIdentityMatrix2D( aFontMatrix ); |
| |
| // TODO(F2): use correct scale direction, font |
| // height might be width or anything else |
| |
| // TODO(Q3): This code smells of programming by |
| // coincidence (the next two if statements) |
| const ::Size rFontSizeLog( rFont.GetSize() ); |
| const sal_Int32 nFontWidthLog = rFontSizeLog.Width(); |
| if( nFontWidthLog != 0 ) |
| { |
| ::Font aTestFont = rFont; |
| aTestFont.SetWidth( 0 ); |
| sal_Int32 nNormalWidth = rParms.mrVDev.GetFontMetric( aTestFont ).GetWidth(); |
| if( nNormalWidth != nFontWidthLog ) |
| if( nNormalWidth ) |
| aFontMatrix.m00 = (double)nFontWidthLog / nNormalWidth; |
| } |
| |
| // #i52608# apply map mode scale also to font matrix - an |
| // anisotrophic mapmode must be reflected in an |
| // anisotrophic font matrix scale. |
| const OutDevState& rState( getState( rParms.mrStates ) ); |
| if( !::basegfx::fTools::equal( |
| rState.mapModeTransform.get(0,0), |
| rState.mapModeTransform.get(1,1)) ) |
| { |
| const double nScaleX( rState.mapModeTransform.get(0,0) ); |
| const double nScaleY( rState.mapModeTransform.get(1,1) ); |
| |
| // note: no reason to check for division by zero, we |
| // always have the value closer (or equal) to zero as |
| // the nominator. |
| if( fabs(nScaleX) < fabs(nScaleY) ) |
| aFontMatrix.m00 *= nScaleX / nScaleY; |
| else |
| aFontMatrix.m11 *= nScaleY / nScaleX; |
| } |
| aFontRequest.CellSize = (rState.mapModeTransform * ::vcl::unotools::b2DSizeFromSize(rFontSizeLog)).getY(); |
| |
| return rParms.mrCanvas->getUNOCanvas()->createFont( aFontRequest, |
| uno::Sequence< beans::PropertyValue >(), |
| aFontMatrix ); |
| } |
| |
| // create text effects such as shadow/relief/embossed |
| void ImplRenderer::createTextAction( const ::Point& rStartPoint, |
| const String rString, |
| int nIndex, |
| int nLength, |
| const sal_Int32* pCharWidths, |
| const ActionFactoryParameters& rParms, |
| bool bSubsettableActions ) |
| { |
| ENSURE_OR_THROW( nIndex >= 0 && nLength <= rString.Len() + nIndex, |
| "ImplRenderer::createTextWithEffectsAction(): Invalid text index" ); |
| |
| if( !nLength ) |
| return; // zero-length text, no visible output |
| |
| const OutDevState& rState( getState( rParms.mrStates ) ); |
| |
| // TODO(F2): implement all text effects |
| // if( rState.textAlignment ); // TODO(F2): NYI |
| |
| ::Color aShadowColor( COL_AUTO ); |
| ::Color aReliefColor( COL_AUTO ); |
| ::Size aShadowOffset; |
| ::Size aReliefOffset; |
| |
| uno::Reference<rendering::XColorSpace> xColorSpace( |
| rParms.mrCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() ); |
| |
| if( rState.isTextEffectShadowSet ) |
| { |
| // calculate shadow offset (similar to outdev3.cxx) |
| // TODO(F3): better match with outdev3.cxx |
| sal_Int32 nShadowOffset = static_cast<sal_Int32>(1.5 + ((rParms.mrVDev.GetFont().GetHeight()-24.0)/24.0)); |
| if( nShadowOffset < 1 ) |
| nShadowOffset = 1; |
| |
| aShadowOffset.setWidth( nShadowOffset ); |
| aShadowOffset.setHeight( nShadowOffset ); |
| |
| // determine shadow color (from outdev3.cxx) |
| ::Color aTextColor = ::vcl::unotools::doubleSequenceToColor( |
| rState.textColor, xColorSpace ); |
| bool bIsDark = (aTextColor.GetColor() == COL_BLACK) |
| || (aTextColor.GetLuminance() < 8); |
| |
| aShadowColor = bIsDark ? COL_LIGHTGRAY : COL_BLACK; |
| aShadowColor.SetTransparency( aTextColor.GetTransparency() ); |
| } |
| |
| if( rState.textReliefStyle ) |
| { |
| // calculate relief offset (similar to outdev3.cxx) |
| sal_Int32 nReliefOffset = rParms.mrVDev.PixelToLogic( Size( 1, 1 ) ).Height(); |
| nReliefOffset += nReliefOffset/2; |
| if( nReliefOffset < 1 ) |
| nReliefOffset = 1; |
| |
| if( rState.textReliefStyle == RELIEF_ENGRAVED ) |
| nReliefOffset = -nReliefOffset; |
| |
| aReliefOffset.setWidth( nReliefOffset ); |
| aReliefOffset.setHeight( nReliefOffset ); |
| |
| // determine relief color (from outdev3.cxx) |
| ::Color aTextColor = ::vcl::unotools::doubleSequenceToColor( |
| rState.textColor, xColorSpace ); |
| |
| aReliefColor = ::Color( COL_LIGHTGRAY ); |
| |
| // we don't have a automatic color, so black is always |
| // drawn on white (literally copied from |
| // vcl/source/gdi/outdev3.cxx) |
| if( aTextColor.GetColor() == COL_BLACK ) |
| { |
| aTextColor = ::Color( COL_WHITE ); |
| getState( rParms.mrStates ).textColor = |
| ::vcl::unotools::colorToDoubleSequence( |
| aTextColor, xColorSpace ); |
| } |
| |
| if( aTextColor.GetColor() == COL_WHITE ) |
| aReliefColor = ::Color( COL_BLACK ); |
| aReliefColor.SetTransparency( aTextColor.GetTransparency() ); |
| } |
| |
| // create the actual text action |
| ActionSharedPtr pTextAction( |
| TextActionFactory::createTextAction( |
| rStartPoint, |
| aReliefOffset, |
| aReliefColor, |
| aShadowOffset, |
| aShadowColor, |
| rString, |
| nIndex, |
| nLength, |
| pCharWidths, |
| rParms.mrVDev, |
| rParms.mrCanvas, |
| rState, |
| rParms.mrParms, |
| bSubsettableActions ) ); |
| |
| ActionSharedPtr pStrikeoutTextAction; |
| |
| if ( rState.textStrikeoutStyle == STRIKEOUT_X || rState.textStrikeoutStyle == STRIKEOUT_SLASH ) |
| { |
| long nWidth = rParms.mrVDev.GetTextWidth( rString,nIndex,nLength ); |
| |
| xub_Unicode pChars[5]; |
| if ( rState.textStrikeoutStyle == STRIKEOUT_X ) |
| pChars[0] = 'X'; |
| else |
| pChars[0] = '/'; |
| pChars[3]=pChars[2]=pChars[1]=pChars[0]; |
| |
| long nStrikeoutWidth = nWidth; |
| String aStrikeoutTest( pChars, 4 ); |
| |
| if( aStrikeoutTest.Len() ) |
| { |
| nStrikeoutWidth = ( rParms.mrVDev.GetTextWidth( aStrikeoutTest ) + 2 ) / 4; |
| aStrikeoutTest.Erase(); |
| |
| if( nStrikeoutWidth <= 0 ) |
| nStrikeoutWidth = 1; |
| } |
| |
| long nMaxWidth = nStrikeoutWidth/2; |
| if ( nMaxWidth < 2 ) |
| nMaxWidth = 2; |
| nMaxWidth += nWidth + 1; |
| |
| long nFullStrikeoutWidth = 0; |
| String aStrikeoutText( pChars, 0 ); |
| while( (nFullStrikeoutWidth+=nStrikeoutWidth ) < nMaxWidth+1 ) |
| aStrikeoutText += pChars[0]; |
| |
| |
| sal_Int32 nStartPos = 0; |
| xub_StrLen nLen = aStrikeoutText.Len(); |
| |
| if( nLen ) |
| { |
| long nInterval = ( nWidth - nStrikeoutWidth * nLen ) / nLen; |
| nStrikeoutWidth += nInterval; |
| sal_Int32* pStrikeoutCharWidths = new sal_Int32[nLen]; |
| |
| for ( int i = 0;i<nLen; i++) |
| { |
| pStrikeoutCharWidths[i] = nStrikeoutWidth; |
| } |
| |
| for ( int i = 1;i< nLen; i++ ) |
| { |
| pStrikeoutCharWidths[ i ] += pStrikeoutCharWidths[ i-1 ]; |
| } |
| |
| pStrikeoutTextAction = |
| TextActionFactory::createTextAction( |
| rStartPoint, |
| aReliefOffset, |
| aReliefColor, |
| aShadowOffset, |
| aShadowColor, |
| aStrikeoutText, |
| nStartPos, |
| aStrikeoutText.Len(), |
| pStrikeoutCharWidths, |
| rParms.mrVDev, |
| rParms.mrCanvas, |
| rState, |
| rParms.mrParms, |
| bSubsettableActions ) ; |
| } |
| } |
| |
| if( pTextAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pTextAction, |
| rParms.mrCurrActionIndex ) ); |
| |
| if ( pStrikeoutTextAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pStrikeoutTextAction, |
| rParms.mrCurrActionIndex ) ); |
| } |
| |
| rParms.mrCurrActionIndex += pTextAction->getActionCount()-1; |
| } |
| } |
| |
| void ImplRenderer::updateClipping( const ::basegfx::B2DPolyPolygon& rClipPoly, |
| const ActionFactoryParameters& rParms, |
| bool bIntersect ) |
| { |
| ::cppcanvas::internal::OutDevState& rState( getState( rParms.mrStates ) ); |
| ::basegfx::B2DPolyPolygon aClipPoly( rClipPoly ); |
| |
| const bool bEmptyClipRect( rState.clipRect.IsEmpty() ); |
| const bool bEmptyClipPoly( rState.clip.count() == 0 ); |
| |
| ENSURE_OR_THROW( bEmptyClipPoly || bEmptyClipRect, |
| "ImplRenderer::updateClipping(): Clip rect and polygon are both set!" ); |
| |
| if( !bIntersect || |
| (bEmptyClipRect && bEmptyClipPoly) ) |
| { |
| rState.clip = rClipPoly; |
| } |
| else |
| { |
| if( !bEmptyClipRect ) |
| { |
| // TODO(P3): Use Liang-Barsky polygon clip here, |
| // after all, one object is just a rectangle! |
| |
| // convert rect to polygon beforehand, must revert |
| // to general polygon clipping here. |
| rState.clip = ::basegfx::B2DPolyPolygon( |
| ::basegfx::tools::createPolygonFromRect( |
| // #121100# VCL rectangular clips always |
| // include one more pixel to the right |
| // and the bottom |
| ::basegfx::B2DRectangle( rState.clipRect.Left(), |
| rState.clipRect.Top(), |
| rState.clipRect.Right()+1, |
| rState.clipRect.Bottom()+1 ) ) ); |
| } |
| |
| // AW: Simplified |
| rState.clip = basegfx::tools::clipPolyPolygonOnPolyPolygon( |
| aClipPoly, rState.clip, true, false); |
| } |
| |
| // by now, our clip resides in the OutDevState::clip |
| // poly-polygon. |
| rState.clipRect.SetEmpty(); |
| |
| if( rState.clip.count() == 0 ) |
| { |
| if( rState.clipRect.IsEmpty() ) |
| { |
| rState.xClipPoly.clear(); |
| } |
| else |
| { |
| rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( |
| rParms.mrCanvas->getUNOCanvas()->getDevice(), |
| ::basegfx::B2DPolyPolygon( |
| ::basegfx::tools::createPolygonFromRect( |
| // #121100# VCL rectangular clips |
| // always include one more pixel to |
| // the right and the bottom |
| ::basegfx::B2DRectangle( rState.clipRect.Left(), |
| rState.clipRect.Top(), |
| rState.clipRect.Right()+1, |
| rState.clipRect.Bottom()+1 ) ) ) ); |
| } |
| } |
| else |
| { |
| rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( |
| rParms.mrCanvas->getUNOCanvas()->getDevice(), |
| rState.clip ); |
| } |
| } |
| |
| void ImplRenderer::updateClipping( const ::Rectangle& rClipRect, |
| const ActionFactoryParameters& rParms, |
| bool bIntersect ) |
| { |
| ::cppcanvas::internal::OutDevState& rState( getState( rParms.mrStates ) ); |
| |
| const bool bEmptyClipRect( rState.clipRect.IsEmpty() ); |
| const bool bEmptyClipPoly( rState.clip.count() == 0 ); |
| |
| ENSURE_OR_THROW( bEmptyClipPoly || bEmptyClipRect, |
| "ImplRenderer::updateClipping(): Clip rect and polygon are both set!" ); |
| |
| if( !bIntersect || |
| (bEmptyClipRect && bEmptyClipPoly) ) |
| { |
| rState.clipRect = rClipRect; |
| rState.clip.clear(); |
| } |
| else if( bEmptyClipPoly ) |
| { |
| rState.clipRect.Intersection( rClipRect ); |
| rState.clip.clear(); |
| } |
| else |
| { |
| // TODO(P3): Handle a fourth case here, when all clip |
| // polygons are rectangular, once B2DMultiRange's |
| // sweep line implementation is done. |
| |
| // general case: convert to polygon and clip |
| // ----------------------------------------- |
| |
| // convert rect to polygon beforehand, must revert |
| // to general polygon clipping here. |
| ::basegfx::B2DPolyPolygon aClipPoly( |
| ::basegfx::tools::createPolygonFromRect( |
| ::basegfx::B2DRectangle( rClipRect.Left(), |
| rClipRect.Top(), |
| rClipRect.Right(), |
| rClipRect.Bottom() ) ) ); |
| |
| rState.clipRect.SetEmpty(); |
| |
| // AW: Simplified |
| rState.clip = basegfx::tools::clipPolyPolygonOnPolyPolygon( |
| aClipPoly, rState.clip, true, false); |
| } |
| |
| if( rState.clip.count() == 0 ) |
| { |
| if( rState.clipRect.IsEmpty() ) |
| { |
| rState.xClipPoly.clear(); |
| } |
| else |
| { |
| rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( |
| rParms.mrCanvas->getUNOCanvas()->getDevice(), |
| ::basegfx::B2DPolyPolygon( |
| ::basegfx::tools::createPolygonFromRect( |
| // #121100# VCL rectangular clips |
| // always include one more pixel to |
| // the right and the bottom |
| ::basegfx::B2DRectangle( rState.clipRect.Left(), |
| rState.clipRect.Top(), |
| rState.clipRect.Right()+1, |
| rState.clipRect.Bottom()+1 ) ) ) ); |
| } |
| } |
| else |
| { |
| rState.xClipPoly = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( |
| rParms.mrCanvas->getUNOCanvas()->getDevice(), |
| rState.clip ); |
| } |
| } |
| |
| bool ImplRenderer::createActions( GDIMetaFile& rMtf, |
| const ActionFactoryParameters& rFactoryParms, |
| bool bSubsettableActions ) |
| { |
| /* TODO(P2): interpret mtf-comments |
| ================================ |
| |
| - gradient fillings (do that via comments) |
| |
| - think about mapping. _If_ we do everything in logical |
| coordinates (which would solve the probs for stroke |
| widths and text offsets), then we would have to |
| recalc scaling for every drawing operation. This is |
| because the outdev map mode might change at any time. |
| Also keep in mind, that, although we've double precision |
| float arithmetic now, different offsets might still |
| generate different roundings (aka |
| 'OutputDevice::SetPixelOffset()) |
| |
| */ |
| |
| // alias common parameters |
| VectorOfOutDevStates& rStates(rFactoryParms.mrStates); |
| const CanvasSharedPtr& rCanvas(rFactoryParms.mrCanvas); |
| ::VirtualDevice& rVDev(rFactoryParms.mrVDev); |
| const Parameters& rParms(rFactoryParms.mrParms); |
| sal_Int32& io_rCurrActionIndex(rFactoryParms.mrCurrActionIndex); |
| |
| |
| // Loop over every metaaction |
| // ========================== |
| MetaAction* pCurrAct; |
| |
| // TODO(P1): think about caching |
| for( pCurrAct=rMtf.FirstAction(); |
| pCurrAct; |
| pCurrAct = rMtf.NextAction() ) |
| { |
| // execute every action, to keep VDev state up-to-date |
| // currently used only for |
| // - the map mode |
| // - the line/fill color when processing a META_TRANSPARENT_ACTION |
| // - SetFont to process font metric specific actions |
| pCurrAct->Execute( &rVDev ); |
| |
| switch( pCurrAct->GetType() ) |
| { |
| // ------------------------------------------------------------ |
| |
| // In the first part of this monster-switch, we |
| // handle all state-changing meta actions. These |
| // are all handled locally. |
| |
| // ------------------------------------------------------------ |
| |
| case META_PUSH_ACTION: |
| { |
| MetaPushAction* pPushAction = static_cast<MetaPushAction*>(pCurrAct); |
| pushState( rStates, |
| pPushAction->GetFlags() ); |
| } |
| break; |
| |
| case META_POP_ACTION: |
| popState( rStates ); |
| break; |
| |
| case META_TEXTLANGUAGE_ACTION: |
| // FALLTHROUGH intended |
| case META_REFPOINT_ACTION: |
| // handled via pCurrAct->Execute( &rVDev ) |
| break; |
| |
| case META_MAPMODE_ACTION: |
| // modify current mapModeTransformation |
| // transformation, such that subsequent |
| // coordinates map correctly |
| tools::calcLogic2PixelAffineTransform( getState( rStates ).mapModeTransform, |
| rVDev ); |
| break; |
| |
| // monitor clip regions, to assemble clip polygon on our own |
| case META_CLIPREGION_ACTION: |
| { |
| MetaClipRegionAction* pClipAction = static_cast<MetaClipRegionAction*>(pCurrAct); |
| |
| if( !pClipAction->IsClipping() ) |
| { |
| // clear clipping |
| getState( rStates ).clip.clear(); |
| } |
| else |
| { |
| if( !pClipAction->GetRegion().HasPolyPolygonOrB2DPolyPolygon() ) |
| { |
| VERBOSE_TRACE( "ImplRenderer::createActions(): non-polygonal clip " |
| "region encountered, falling back to bounding box!" ); |
| |
| // #121806# explicitly kept integer |
| Rectangle aClipRect( |
| rVDev.LogicToPixel( |
| pClipAction->GetRegion().GetBoundRect() ) ); |
| |
| // intersect current clip with given rect |
| updateClipping( |
| aClipRect, |
| rFactoryParms, |
| false ); |
| } |
| else |
| { |
| // set new clip polygon (don't intersect |
| // with old one, just set it) |
| |
| // #121806# explicitly kept integer |
| basegfx::B2DPolyPolygon aPolyPolygon(pClipAction->GetRegion().GetAsB2DPolyPolygon()); |
| |
| aPolyPolygon.transform(rVDev.GetViewTransformation()); |
| updateClipping( |
| aPolyPolygon, |
| rFactoryParms, |
| false ); |
| } |
| } |
| |
| break; |
| } |
| |
| case META_ISECTRECTCLIPREGION_ACTION: |
| { |
| MetaISectRectClipRegionAction* pClipAction = static_cast<MetaISectRectClipRegionAction*>(pCurrAct); |
| |
| // #121806# explicitly kept integer |
| Rectangle aClipRect( |
| rVDev.LogicToPixel( pClipAction->GetRect() ) ); |
| |
| // intersect current clip with given rect |
| updateClipping( |
| aClipRect, |
| rFactoryParms, |
| true ); |
| |
| break; |
| } |
| |
| case META_ISECTREGIONCLIPREGION_ACTION: |
| { |
| MetaISectRegionClipRegionAction* pClipAction = static_cast<MetaISectRegionClipRegionAction*>(pCurrAct); |
| |
| if( !pClipAction->GetRegion().HasPolyPolygonOrB2DPolyPolygon() ) |
| { |
| VERBOSE_TRACE( "ImplRenderer::createActions(): non-polygonal clip " |
| "region encountered, falling back to bounding box!" ); |
| |
| // #121806# explicitly kept integer |
| Rectangle aClipRect( |
| rVDev.LogicToPixel( pClipAction->GetRegion().GetBoundRect() ) ); |
| |
| // intersect current clip with given rect |
| updateClipping( |
| aClipRect, |
| rFactoryParms, |
| true ); |
| } |
| else |
| { |
| // intersect current clip with given clip polygon |
| |
| // #121806# explicitly kept integer |
| basegfx::B2DPolyPolygon aPolyPolygon(pClipAction->GetRegion().GetAsB2DPolyPolygon()); |
| |
| aPolyPolygon.transform(rVDev.GetViewTransformation()); |
| updateClipping( |
| aPolyPolygon, |
| rFactoryParms, |
| true ); |
| } |
| |
| break; |
| } |
| |
| case META_MOVECLIPREGION_ACTION: |
| // TODO(F2): NYI |
| break; |
| |
| case META_LINECOLOR_ACTION: |
| if( !rParms.maLineColor.is_initialized() ) |
| { |
| setStateColor( static_cast<MetaLineColorAction*>(pCurrAct), |
| getState( rStates ).isLineColorSet, |
| getState( rStates ).lineColor, |
| rCanvas ); |
| } |
| else |
| { |
| // #120994# Do switch on/off LineColor, even when a overriding one is set |
| bool bSetting(static_cast<MetaLineColorAction*>(pCurrAct)->IsSetting()); |
| |
| getState( rStates ).isLineColorSet = bSetting; |
| } |
| break; |
| |
| case META_FILLCOLOR_ACTION: |
| if( !rParms.maFillColor.is_initialized() ) |
| { |
| setStateColor( static_cast<MetaFillColorAction*>(pCurrAct), |
| getState( rStates ).isFillColorSet, |
| getState( rStates ).fillColor, |
| rCanvas ); |
| } |
| else |
| { |
| // #120994# Do switch on/off FillColor, even when a overriding one is set |
| bool bSetting(static_cast<MetaFillColorAction*>(pCurrAct)->IsSetting()); |
| |
| getState( rStates ).isFillColorSet = bSetting; |
| } |
| break; |
| |
| case META_TEXTCOLOR_ACTION: |
| { |
| if( !rParms.maTextColor.is_initialized() ) |
| { |
| // Text color is set unconditionally, thus, no |
| // use of setStateColor here |
| ::Color aColor( static_cast<MetaTextColorAction*>(pCurrAct)->GetColor() ); |
| |
| // force alpha part of color to |
| // opaque. transparent painting is done |
| // explicitly via META_TRANSPARENT_ACTION |
| aColor.SetTransparency(0); |
| |
| getState( rStates ).textColor = |
| ::vcl::unotools::colorToDoubleSequence( |
| aColor, |
| rCanvas->getUNOCanvas()->getDevice()->getDeviceColorSpace() ); |
| } |
| } |
| break; |
| |
| case META_TEXTFILLCOLOR_ACTION: |
| if( !rParms.maTextColor.is_initialized() ) |
| { |
| setStateColor( static_cast<MetaTextFillColorAction*>(pCurrAct), |
| getState( rStates ).isTextFillColorSet, |
| getState( rStates ).textFillColor, |
| rCanvas ); |
| } |
| else |
| { |
| // #120994# Do switch on/off TextFillColor, even when a overriding one is set |
| bool bSetting(static_cast<MetaTextFillColorAction*>(pCurrAct)->IsSetting()); |
| |
| getState( rStates ).isTextFillColorSet = bSetting; |
| } |
| break; |
| |
| case META_TEXTLINECOLOR_ACTION: |
| if( !rParms.maTextColor.is_initialized() ) |
| { |
| setStateColor( static_cast<MetaTextLineColorAction*>(pCurrAct), |
| getState( rStates ).isTextLineColorSet, |
| getState( rStates ).textLineColor, |
| rCanvas ); |
| } |
| else |
| { |
| // #120994# Do switch on/off TextLineColor, even when a overriding one is set |
| bool bSetting(static_cast<MetaTextLineColorAction*>(pCurrAct)->IsSetting()); |
| |
| getState( rStates ).isTextLineColorSet = bSetting; |
| } |
| break; |
| |
| case META_TEXTALIGN_ACTION: |
| { |
| ::cppcanvas::internal::OutDevState& rState = getState( rStates ); |
| const TextAlign eTextAlign( static_cast<MetaTextAlignAction*>(pCurrAct)->GetTextAlign() ); |
| |
| rState.textReferencePoint = eTextAlign; |
| } |
| break; |
| |
| case META_FONT_ACTION: |
| { |
| ::cppcanvas::internal::OutDevState& rState = getState( rStates ); |
| const ::Font& rFont( static_cast<MetaFontAction*>(pCurrAct)->GetFont() ); |
| |
| rState.xFont = createFont( rState.fontRotation, |
| rFont, |
| rFactoryParms ); |
| |
| // TODO(Q2): define and use appropriate enumeration types |
| rState.textReliefStyle = (sal_Int8)rFont.GetRelief(); |
| rState.textOverlineStyle = (sal_Int8)rFont.GetOverline(); |
| rState.textUnderlineStyle = rParms.maFontUnderline.is_initialized() ? |
| (*rParms.maFontUnderline ? (sal_Int8)UNDERLINE_SINGLE : (sal_Int8)UNDERLINE_NONE) : |
| (sal_Int8)rFont.GetUnderline(); |
| rState.textStrikeoutStyle = (sal_Int8)rFont.GetStrikeout(); |
| rState.textEmphasisMarkStyle = (sal_Int8)rFont.GetEmphasisMark(); |
| rState.isTextEffectShadowSet = (rFont.IsShadow() != sal_False); |
| rState.isTextWordUnderlineSet = (rFont.IsWordLineMode() != sal_False); |
| rState.isTextOutlineModeSet = (rFont.IsOutline() != sal_False); |
| } |
| break; |
| |
| case META_RASTEROP_ACTION: |
| // TODO(F2): NYI |
| break; |
| |
| case META_LAYOUTMODE_ACTION: |
| { |
| // TODO(F2): A lot is missing here |
| int nLayoutMode = static_cast<MetaLayoutModeAction*>(pCurrAct)->GetLayoutMode(); |
| ::cppcanvas::internal::OutDevState& rState = getState( rStates ); |
| switch( nLayoutMode & (TEXT_LAYOUT_BIDI_RTL|TEXT_LAYOUT_BIDI_STRONG) ) |
| { |
| case TEXT_LAYOUT_BIDI_LTR: |
| rState.textDirection = rendering::TextDirection::WEAK_LEFT_TO_RIGHT; |
| break; |
| |
| case (TEXT_LAYOUT_BIDI_LTR | TEXT_LAYOUT_BIDI_STRONG): |
| rState.textDirection = rendering::TextDirection::STRONG_LEFT_TO_RIGHT; |
| break; |
| |
| case TEXT_LAYOUT_BIDI_RTL: |
| rState.textDirection = rendering::TextDirection::WEAK_RIGHT_TO_LEFT; |
| break; |
| |
| case (TEXT_LAYOUT_BIDI_RTL | TEXT_LAYOUT_BIDI_STRONG): |
| rState.textDirection = rendering::TextDirection::STRONG_RIGHT_TO_LEFT; |
| break; |
| } |
| |
| rState.textAlignment = 0; // TODO(F2): rendering::TextAlignment::LEFT_ALIGNED; |
| if( (nLayoutMode & (TEXT_LAYOUT_BIDI_RTL | TEXT_LAYOUT_TEXTORIGIN_RIGHT) ) |
| && !(nLayoutMode & TEXT_LAYOUT_TEXTORIGIN_LEFT ) ) |
| { |
| rState.textAlignment = 1; // TODO(F2): rendering::TextAlignment::RIGHT_ALIGNED; |
| } |
| } |
| break; |
| |
| // ------------------------------------------------------------ |
| |
| // In the second part of this monster-switch, we |
| // handle all recursing meta actions. These are the |
| // ones generating a metafile by themselves, which is |
| // then processed by recursively calling this method. |
| |
| // ------------------------------------------------------------ |
| |
| case META_GRADIENT_ACTION: |
| { |
| MetaGradientAction* pGradAct = static_cast<MetaGradientAction*>(pCurrAct); |
| createGradientAction( ::Polygon( pGradAct->GetRect() ), |
| pGradAct->GetGradient(), |
| rFactoryParms, |
| true, |
| bSubsettableActions ); |
| } |
| break; |
| |
| case META_HATCH_ACTION: |
| { |
| // TODO(F2): use native Canvas hatches here |
| GDIMetaFile aTmpMtf; |
| |
| rVDev.AddHatchActions( static_cast<MetaHatchAction*>(pCurrAct)->GetPolyPolygon(), |
| static_cast<MetaHatchAction*>(pCurrAct)->GetHatch(), |
| aTmpMtf ); |
| createActions( aTmpMtf, rFactoryParms, |
| bSubsettableActions ); |
| } |
| break; |
| |
| case META_EPS_ACTION: |
| { |
| MetaEPSAction* pAct = static_cast<MetaEPSAction*>(pCurrAct); |
| const GDIMetaFile& rSubstitute = pAct->GetSubstitute(); |
| |
| // #121806# explicitly kept integer |
| const Size aMtfSize( rSubstitute.GetPrefSize() ); |
| const Size aMtfSizePixPre( rVDev.LogicToPixel( aMtfSize, |
| rSubstitute.GetPrefMapMode() ) ); |
| |
| // #i44110# correct null-sized output - there |
| // are metafiles which have zero size in at |
| // least one dimension |
| const Size aMtfSizePix( ::std::max( aMtfSizePixPre.Width(), 1L ), |
| ::std::max( aMtfSizePixPre.Height(), 1L ) ); |
| |
| // Setup local transform, such that the |
| // metafile renders itself into the given |
| // output rectangle |
| pushState( rStates, PUSH_ALL ); |
| |
| rVDev.Push(); |
| rVDev.SetMapMode( rSubstitute.GetPrefMapMode() ); |
| |
| const ::Point& rPos( rVDev.LogicToPixel( pAct->GetPoint() ) ); |
| const ::Size& rSize( rVDev.LogicToPixel( pAct->GetSize() ) ); |
| |
| getState( rStates ).transform.translate( rPos.X(), |
| rPos.Y() ); |
| getState( rStates ).transform.scale( (double)rSize.Width() / aMtfSizePix.Width(), |
| (double)rSize.Height() / aMtfSizePix.Height() ); |
| |
| createActions( const_cast<GDIMetaFile&>(pAct->GetSubstitute()), |
| rFactoryParms, |
| bSubsettableActions ); |
| |
| rVDev.Pop(); |
| popState( rStates ); |
| } |
| break; |
| |
| // handle metafile comments, to retrieve |
| // meta-information for gradients, fills and |
| // strokes. May skip actions, and may recurse. |
| case META_COMMENT_ACTION: |
| { |
| MetaCommentAction* pAct = static_cast<MetaCommentAction*>(pCurrAct); |
| |
| // Handle gradients |
| if ( pAct->GetComment().CompareIgnoreCaseToAscii( "XGRAD_SEQ_BEGIN" ) == COMPARE_EQUAL ) |
| { |
| MetaGradientExAction* pGradAction = NULL; |
| bool bDone( false ); |
| while( !bDone && |
| (pCurrAct=rMtf.NextAction()) != NULL ) |
| { |
| switch( pCurrAct->GetType() ) |
| { |
| // extract gradient info |
| case META_GRADIENTEX_ACTION: |
| pGradAction = static_cast<MetaGradientExAction*>(pCurrAct); |
| break; |
| |
| // skip broken-down rendering, output gradient when sequence is ended |
| case META_COMMENT_ACTION: |
| if( static_cast<MetaCommentAction*>(pCurrAct)->GetComment().CompareIgnoreCaseToAscii( "XGRAD_SEQ_END" ) == COMPARE_EQUAL ) |
| { |
| bDone = true; |
| |
| if( pGradAction ) |
| { |
| createGradientAction( pGradAction->GetPolyPolygon(), |
| pGradAction->GetGradient(), |
| rFactoryParms, |
| false, |
| bSubsettableActions ); |
| } |
| } |
| break; |
| } |
| } |
| } |
| // TODO(P2): Handle drawing layer strokes, via |
| // XPATHSTROKE_SEQ_BEGIN comment |
| |
| // Handle drawing layer fills |
| else if( pAct->GetComment().Equals( "XPATHFILL_SEQ_BEGIN" ) ) |
| { |
| const sal_uInt8* pData = pAct->GetData(); |
| if ( pData ) |
| { |
| SvMemoryStream aMemStm( (void*)pData, pAct->GetDataSize(), STREAM_READ ); |
| |
| SvtGraphicFill aFill; |
| aMemStm >> aFill; |
| |
| // TODO(P2): Also handle gradients and |
| // hatches like this |
| |
| // only evaluate comment for pure |
| // bitmap fills. If a transparency |
| // gradient is involved (denoted by |
| // the FloatTransparent action), take |
| // the normal meta actions. |
| if( aFill.getFillType() == SvtGraphicFill::fillTexture && |
| !isActionContained( rMtf, |
| "XPATHFILL_SEQ_END", |
| META_FLOATTRANSPARENT_ACTION ) ) |
| { |
| rendering::Texture aTexture; |
| |
| // TODO(F1): the SvtGraphicFill |
| // can also transport metafiles |
| // here, handle that case, too |
| Graphic aGraphic; |
| aFill.getGraphic( aGraphic ); |
| |
| BitmapEx aBmpEx( aGraphic.GetBitmapEx() ); |
| const ::Size aBmpSize( aBmpEx.GetSizePixel() ); |
| |
| ::SvtGraphicFill::Transform aTransform; |
| aFill.getTransform( aTransform ); |
| |
| ::basegfx::B2DHomMatrix aMatrix; |
| |
| // convert to basegfx matrix |
| aMatrix.set(0,0, aTransform.matrix[ 0 ] ); |
| aMatrix.set(0,1, aTransform.matrix[ 1 ] ); |
| aMatrix.set(0,2, aTransform.matrix[ 2 ] ); |
| aMatrix.set(1,0, aTransform.matrix[ 3 ] ); |
| aMatrix.set(1,1, aTransform.matrix[ 4 ] ); |
| aMatrix.set(1,2, aTransform.matrix[ 5 ] ); |
| |
| ::basegfx::B2DHomMatrix aScale; |
| aScale.scale( aBmpSize.Width(), |
| aBmpSize.Height() ); |
| |
| // post-multiply with the bitmap |
| // size (XCanvas' texture assumes |
| // the given bitmap to be |
| // normalized to [0,1]x[0,1] |
| // rectangle) |
| aMatrix = aMatrix * aScale; |
| |
| // pre-multiply with the |
| // logic-to-pixel scale factor |
| // (the metafile comment works in |
| // logical coordinates). |
| ::basegfx::B2DHomMatrix aLogic2PixelTransform; |
| aMatrix *= tools::calcLogic2PixelLinearTransform( aLogic2PixelTransform, |
| rVDev ); |
| |
| ::basegfx::unotools::affineMatrixFromHomMatrix( |
| aTexture.AffineTransform, |
| aMatrix ); |
| |
| aTexture.Alpha = 1.0 - aFill.getTransparency(); |
| aTexture.Bitmap = |
| ::vcl::unotools::xBitmapFromBitmapEx( |
| rCanvas->getUNOCanvas()->getDevice(), |
| aBmpEx ); |
| if( aFill.isTiling() ) |
| { |
| aTexture.RepeatModeX = rendering::TexturingMode::REPEAT; |
| aTexture.RepeatModeY = rendering::TexturingMode::REPEAT; |
| } |
| else |
| { |
| aTexture.RepeatModeX = rendering::TexturingMode::NONE; |
| aTexture.RepeatModeY = rendering::TexturingMode::NONE; |
| } |
| |
| ::PolyPolygon aPath; |
| aFill.getPath( aPath ); |
| |
| ::basegfx::B2DPolyPolygon aPoly( aPath.getB2DPolyPolygon() ); |
| aPoly.transform( getState( rStates ).mapModeTransform ); |
| ActionSharedPtr pPolyAction( |
| internal::PolyPolyActionFactory::createPolyPolyAction( |
| aPoly, |
| rCanvas, |
| getState( rStates ), |
| aTexture ) ); |
| |
| if( pPolyAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pPolyAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pPolyAction->getActionCount()-1; |
| } |
| |
| // skip broken-down render output |
| skipContent( rMtf, |
| "XPATHFILL_SEQ_END", |
| io_rCurrActionIndex ); |
| } |
| } |
| } |
| } |
| break; |
| |
| // ------------------------------------------------------------ |
| |
| // In the third part of this monster-switch, we |
| // handle all 'acting' meta actions. These are all |
| // processed by constructing function objects for |
| // them, which will later ease caching. |
| |
| // ------------------------------------------------------------ |
| |
| case META_POINT_ACTION: |
| { |
| const OutDevState& rState( getState( rStates ) ); |
| if( rState.lineColor.getLength() ) |
| { |
| ActionSharedPtr pPointAction( |
| internal::PointActionFactory::createPointAction( |
| rState.mapModeTransform * ::vcl::unotools::b2DPointFromPoint( |
| static_cast<MetaPointAction*>(pCurrAct)->GetPoint() ), |
| rCanvas, |
| rState ) ); |
| |
| if( pPointAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pPointAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pPointAction->getActionCount()-1; |
| } |
| } |
| } |
| break; |
| |
| case META_PIXEL_ACTION: |
| { |
| const OutDevState& rState( getState( rStates ) ); |
| if( rState.lineColor.getLength() ) |
| { |
| ActionSharedPtr pPointAction( |
| internal::PointActionFactory::createPointAction( |
| rState.mapModeTransform * ::vcl::unotools::b2DPointFromPoint( |
| static_cast<MetaPixelAction*>(pCurrAct)->GetPoint() ), |
| rCanvas, |
| rState, |
| static_cast<MetaPixelAction*>(pCurrAct)->GetColor() ) ); |
| |
| if( pPointAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pPointAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pPointAction->getActionCount()-1; |
| } |
| } |
| } |
| break; |
| |
| case META_LINE_ACTION: |
| { |
| const OutDevState& rState( getState( rStates ) ); |
| if( rState.lineColor.getLength() ) |
| { |
| MetaLineAction* pLineAct = static_cast<MetaLineAction*>(pCurrAct); |
| |
| const LineInfo& rLineInfo( pLineAct->GetLineInfo() ); |
| |
| const ::basegfx::B2DPoint aStartPoint( |
| rState.mapModeTransform * ::vcl::unotools::b2DPointFromPoint( pLineAct->GetStartPoint() )); |
| const ::basegfx::B2DPoint aEndPoint( |
| rState.mapModeTransform * ::vcl::unotools::b2DPointFromPoint( pLineAct->GetEndPoint() )); |
| |
| ActionSharedPtr pLineAction; |
| |
| if( rLineInfo.IsDefault() ) |
| { |
| // plain hair line |
| pLineAction = |
| internal::LineActionFactory::createLineAction( |
| aStartPoint, |
| aEndPoint, |
| rCanvas, |
| rState ); |
| |
| if( pLineAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pLineAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pLineAction->getActionCount()-1; |
| } |
| } |
| else if( LINE_NONE != rLineInfo.GetStyle() ) |
| { |
| // 'thick' line |
| rendering::StrokeAttributes aStrokeAttributes; |
| |
| setupStrokeAttributes( aStrokeAttributes, |
| rFactoryParms, |
| rLineInfo ); |
| |
| // XCanvas can only stroke polygons, |
| // not simple lines - thus, handle |
| // this case via the polypolygon |
| // action |
| ::basegfx::B2DPolygon aPoly; |
| aPoly.append( aStartPoint ); |
| aPoly.append( aEndPoint ); |
| pLineAction = |
| internal::PolyPolyActionFactory::createPolyPolyAction( |
| ::basegfx::B2DPolyPolygon( aPoly ), |
| rCanvas, rState, aStrokeAttributes ); |
| |
| if( pLineAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pLineAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pLineAction->getActionCount()-1; |
| } |
| } |
| // else: line style is default |
| // (i.e. invisible), don't generate action |
| } |
| } |
| break; |
| |
| case META_RECT_ACTION: |
| { |
| const Rectangle& rRect( |
| static_cast<MetaRectAction*>(pCurrAct)->GetRect() ); |
| |
| if( rRect.IsEmpty() ) |
| break; |
| |
| const OutDevState& rState( getState( rStates ) ); |
| const ::basegfx::B2DPoint aTopLeftPixel( |
| rState.mapModeTransform * ::vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ) ); |
| const ::basegfx::B2DPoint aBottomRightPixel( |
| rState.mapModeTransform * ::vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) + |
| // #121100# OutputDevice::DrawRect() fills |
| // rectangles Apple-like, i.e. with one |
| // additional pixel to the right and bottom. |
| ::basegfx::B2DPoint(1,1) ); |
| |
| createFillAndStroke( ::basegfx::tools::createPolygonFromRect( |
| ::basegfx::B2DRange( aTopLeftPixel, |
| aBottomRightPixel )), |
| rFactoryParms ); |
| break; |
| } |
| |
| case META_ROUNDRECT_ACTION: |
| { |
| const Rectangle& rRect( |
| static_cast<MetaRoundRectAction*>(pCurrAct)->GetRect()); |
| |
| if( rRect.IsEmpty() ) |
| break; |
| |
| ::basegfx::B2DPolygon aPoly( |
| ::basegfx::tools::createPolygonFromRect( |
| ::basegfx::B2DRange( |
| ::vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ), |
| ::vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) + |
| ::basegfx::B2DPoint(1,1) ), |
| static_cast<MetaRoundRectAction*>(pCurrAct)->GetHorzRound(), |
| static_cast<MetaRoundRectAction*>(pCurrAct)->GetVertRound() )); |
| aPoly.transform( getState( rStates ).mapModeTransform ); |
| |
| createFillAndStroke( aPoly, |
| rFactoryParms ); |
| } |
| break; |
| |
| case META_ELLIPSE_ACTION: |
| { |
| const Rectangle& rRect( |
| static_cast<MetaEllipseAction*>(pCurrAct)->GetRect() ); |
| |
| if( rRect.IsEmpty() ) |
| break; |
| |
| const ::basegfx::B2DRange aRange( |
| ::vcl::unotools::b2DPointFromPoint( rRect.TopLeft() ), |
| ::vcl::unotools::b2DPointFromPoint( rRect.BottomRight() ) + |
| ::basegfx::B2DPoint(1,1) ); |
| |
| ::basegfx::B2DPolygon aPoly( |
| ::basegfx::tools::createPolygonFromEllipse( |
| aRange.getCenter(), |
| aRange.getWidth(), |
| aRange.getHeight() )); |
| aPoly.transform( getState( rStates ).mapModeTransform ); |
| |
| createFillAndStroke( aPoly, |
| rFactoryParms ); |
| } |
| break; |
| |
| case META_ARC_ACTION: |
| { |
| // TODO(F1): Missing basegfx functionality. Mind empty rects! |
| const Polygon aToolsPoly( static_cast<MetaArcAction*>(pCurrAct)->GetRect(), |
| static_cast<MetaArcAction*>(pCurrAct)->GetStartPoint(), |
| static_cast<MetaArcAction*>(pCurrAct)->GetEndPoint(), POLY_ARC ); |
| ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() ); |
| aPoly.transform( getState( rStates ).mapModeTransform ); |
| |
| createFillAndStroke( aPoly, |
| rFactoryParms ); |
| } |
| break; |
| |
| case META_PIE_ACTION: |
| { |
| // TODO(F1): Missing basegfx functionality. Mind empty rects! |
| const Polygon aToolsPoly( static_cast<MetaPieAction*>(pCurrAct)->GetRect(), |
| static_cast<MetaPieAction*>(pCurrAct)->GetStartPoint(), |
| static_cast<MetaPieAction*>(pCurrAct)->GetEndPoint(), POLY_PIE ); |
| ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() ); |
| aPoly.transform( getState( rStates ).mapModeTransform ); |
| |
| createFillAndStroke( aPoly, |
| rFactoryParms ); |
| } |
| break; |
| |
| case META_CHORD_ACTION: |
| { |
| // TODO(F1): Missing basegfx functionality. Mind empty rects! |
| const Polygon aToolsPoly( static_cast<MetaChordAction*>(pCurrAct)->GetRect(), |
| static_cast<MetaChordAction*>(pCurrAct)->GetStartPoint(), |
| static_cast<MetaChordAction*>(pCurrAct)->GetEndPoint(), POLY_CHORD ); |
| ::basegfx::B2DPolygon aPoly( aToolsPoly.getB2DPolygon() ); |
| aPoly.transform( getState( rStates ).mapModeTransform ); |
| |
| createFillAndStroke( aPoly, |
| rFactoryParms ); |
| } |
| break; |
| |
| case META_POLYLINE_ACTION: |
| { |
| const OutDevState& rState( getState( rStates ) ); |
| if( rState.lineColor.getLength() || |
| rState.fillColor.getLength() ) |
| { |
| MetaPolyLineAction* pPolyLineAct = static_cast<MetaPolyLineAction*>(pCurrAct); |
| |
| const LineInfo& rLineInfo( pPolyLineAct->GetLineInfo() ); |
| ::basegfx::B2DPolygon aPoly( pPolyLineAct->GetPolygon().getB2DPolygon() ); |
| aPoly.transform( rState.mapModeTransform ); |
| |
| ActionSharedPtr pLineAction; |
| |
| if( rLineInfo.IsDefault() ) |
| { |
| // plain hair line polygon |
| pLineAction = |
| internal::PolyPolyActionFactory::createLinePolyPolyAction( |
| ::basegfx::B2DPolyPolygon(aPoly), |
| rCanvas, |
| rState ); |
| |
| if( pLineAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pLineAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pLineAction->getActionCount()-1; |
| } |
| } |
| else if( LINE_NONE != rLineInfo.GetStyle() ) |
| { |
| // 'thick' line polygon |
| rendering::StrokeAttributes aStrokeAttributes; |
| |
| setupStrokeAttributes( aStrokeAttributes, |
| rFactoryParms, |
| rLineInfo ); |
| |
| pLineAction = |
| internal::PolyPolyActionFactory::createPolyPolyAction( |
| ::basegfx::B2DPolyPolygon(aPoly), |
| rCanvas, |
| rState, |
| aStrokeAttributes ) ; |
| |
| if( pLineAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pLineAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pLineAction->getActionCount()-1; |
| } |
| } |
| // else: line style is default |
| // (i.e. invisible), don't generate action |
| } |
| } |
| break; |
| |
| case META_POLYGON_ACTION: |
| { |
| ::basegfx::B2DPolygon aPoly( static_cast<MetaPolygonAction*>(pCurrAct)->GetPolygon().getB2DPolygon() ); |
| aPoly.transform( getState( rStates ).mapModeTransform ); |
| createFillAndStroke( aPoly, |
| rFactoryParms ); |
| } |
| break; |
| |
| case META_POLYPOLYGON_ACTION: |
| { |
| ::basegfx::B2DPolyPolygon aPoly( static_cast<MetaPolyPolygonAction*>(pCurrAct)->GetPolyPolygon().getB2DPolyPolygon() ); |
| aPoly.transform( getState( rStates ).mapModeTransform ); |
| createFillAndStroke( aPoly, |
| rFactoryParms ); |
| } |
| break; |
| |
| case META_BMP_ACTION: |
| { |
| MetaBmpAction* pAct = static_cast<MetaBmpAction*>(pCurrAct); |
| |
| ActionSharedPtr pBmpAction( |
| internal::BitmapActionFactory::createBitmapAction( |
| pAct->GetBitmap(), |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), |
| rCanvas, |
| getState( rStates ) ) ); |
| |
| if( pBmpAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pBmpAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pBmpAction->getActionCount()-1; |
| } |
| } |
| break; |
| |
| case META_BMPSCALE_ACTION: |
| { |
| MetaBmpScaleAction* pAct = static_cast<MetaBmpScaleAction*>(pCurrAct); |
| |
| ActionSharedPtr pBmpAction( |
| internal::BitmapActionFactory::createBitmapAction( |
| pAct->GetBitmap(), |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DSizeFromSize( pAct->GetSize() ), |
| rCanvas, |
| getState( rStates ) ) ); |
| |
| if( pBmpAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pBmpAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pBmpAction->getActionCount()-1; |
| } |
| } |
| break; |
| |
| case META_BMPSCALEPART_ACTION: |
| { |
| MetaBmpScalePartAction* pAct = static_cast<MetaBmpScalePartAction*>(pCurrAct); |
| |
| // crop bitmap to given source rectangle (no |
| // need to copy and convert the whole bitmap) |
| Bitmap aBmp( pAct->GetBitmap() ); |
| const Rectangle aCropRect( pAct->GetSrcPoint(), |
| pAct->GetSrcSize() ); |
| aBmp.Crop( aCropRect ); |
| |
| ActionSharedPtr pBmpAction( |
| internal::BitmapActionFactory::createBitmapAction( |
| aBmp, |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ), |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DSizeFromSize( pAct->GetDestSize() ), |
| rCanvas, |
| getState( rStates ) ) ); |
| |
| if( pBmpAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pBmpAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pBmpAction->getActionCount()-1; |
| } |
| } |
| break; |
| |
| case META_BMPEX_ACTION: |
| { |
| MetaBmpExAction* pAct = static_cast<MetaBmpExAction*>(pCurrAct); |
| |
| ActionSharedPtr pBmpAction( |
| internal::BitmapActionFactory::createBitmapAction( |
| pAct->GetBitmapEx(), |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), |
| rCanvas, |
| getState( rStates ) ) ); |
| |
| if( pBmpAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pBmpAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pBmpAction->getActionCount()-1; |
| } |
| } |
| break; |
| |
| case META_BMPEXSCALE_ACTION: |
| { |
| MetaBmpExScaleAction* pAct = static_cast<MetaBmpExScaleAction*>(pCurrAct); |
| |
| ActionSharedPtr pBmpAction( |
| internal::BitmapActionFactory::createBitmapAction( |
| pAct->GetBitmapEx(), |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DSizeFromSize( pAct->GetSize() ), |
| rCanvas, |
| getState( rStates ) ) ); |
| |
| if( pBmpAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pBmpAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pBmpAction->getActionCount()-1; |
| } |
| } |
| break; |
| |
| case META_BMPEXSCALEPART_ACTION: |
| { |
| MetaBmpExScalePartAction* pAct = static_cast<MetaBmpExScalePartAction*>(pCurrAct); |
| |
| // crop bitmap to given source rectangle (no |
| // need to copy and convert the whole bitmap) |
| BitmapEx aBmp( pAct->GetBitmapEx() ); |
| const Rectangle aCropRect( pAct->GetSrcPoint(), |
| pAct->GetSrcSize() ); |
| aBmp.Crop( aCropRect ); |
| |
| ActionSharedPtr pBmpAction( |
| internal::BitmapActionFactory::createBitmapAction( |
| aBmp, |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ), |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DSizeFromSize( pAct->GetDestSize() ), |
| rCanvas, |
| getState( rStates ) ) ); |
| |
| if( pBmpAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pBmpAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pBmpAction->getActionCount()-1; |
| } |
| } |
| break; |
| |
| case META_MASK_ACTION: |
| { |
| MetaMaskAction* pAct = static_cast<MetaMaskAction*>(pCurrAct); |
| |
| // create masked BitmapEx right here, as the |
| // canvas does not provide equivalent |
| // functionality |
| BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(), |
| pAct->GetColor() )); |
| |
| ActionSharedPtr pBmpAction( |
| internal::BitmapActionFactory::createBitmapAction( |
| aBmp, |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), |
| rCanvas, |
| getState( rStates ) ) ); |
| |
| if( pBmpAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pBmpAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pBmpAction->getActionCount()-1; |
| } |
| } |
| break; |
| |
| case META_MASKSCALE_ACTION: |
| { |
| MetaMaskScaleAction* pAct = static_cast<MetaMaskScaleAction*>(pCurrAct); |
| |
| // create masked BitmapEx right here, as the |
| // canvas does not provide equivalent |
| // functionality |
| BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(), |
| pAct->GetColor() )); |
| |
| ActionSharedPtr pBmpAction( |
| internal::BitmapActionFactory::createBitmapAction( |
| aBmp, |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DSizeFromSize( pAct->GetSize() ), |
| rCanvas, |
| getState( rStates ) ) ); |
| |
| if( pBmpAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pBmpAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pBmpAction->getActionCount()-1; |
| } |
| } |
| break; |
| |
| case META_MASKSCALEPART_ACTION: |
| { |
| MetaMaskScalePartAction* pAct = static_cast<MetaMaskScalePartAction*>(pCurrAct); |
| |
| // create masked BitmapEx right here, as the |
| // canvas does not provide equivalent |
| // functionality |
| BitmapEx aBmp( createMaskBmpEx( pAct->GetBitmap(), |
| pAct->GetColor() )); |
| |
| // crop bitmap to given source rectangle (no |
| // need to copy and convert the whole bitmap) |
| const Rectangle aCropRect( pAct->GetSrcPoint(), |
| pAct->GetSrcSize() ); |
| aBmp.Crop( aCropRect ); |
| |
| ActionSharedPtr pBmpAction( |
| internal::BitmapActionFactory::createBitmapAction( |
| aBmp, |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DPointFromPoint( pAct->GetDestPoint() ), |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DSizeFromSize( pAct->GetDestSize() ), |
| rCanvas, |
| getState( rStates ) ) ); |
| |
| if( pBmpAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pBmpAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pBmpAction->getActionCount()-1; |
| } |
| } |
| break; |
| |
| case META_GRADIENTEX_ACTION: |
| // TODO(F1): use native Canvas gradients here |
| // action is ignored here, because redundant to META_GRADIENT_ACTION |
| break; |
| |
| case META_WALLPAPER_ACTION: |
| // TODO(F2): NYI |
| break; |
| |
| case META_TRANSPARENT_ACTION: |
| { |
| const OutDevState& rState( getState( rStates ) ); |
| if( rState.lineColor.getLength() || |
| rState.fillColor.getLength() ) |
| { |
| MetaTransparentAction* pAct = static_cast<MetaTransparentAction*>(pCurrAct); |
| ::basegfx::B2DPolyPolygon aPoly( pAct->GetPolyPolygon().getB2DPolyPolygon() ); |
| aPoly.transform( rState.mapModeTransform ); |
| |
| ActionSharedPtr pPolyAction( |
| internal::PolyPolyActionFactory::createPolyPolyAction( |
| aPoly, |
| rCanvas, |
| rState, |
| pAct->GetTransparence() ) ); |
| |
| if( pPolyAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pPolyAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pPolyAction->getActionCount()-1; |
| } |
| } |
| } |
| break; |
| |
| case META_FLOATTRANSPARENT_ACTION: |
| { |
| MetaFloatTransparentAction* pAct = static_cast<MetaFloatTransparentAction*>(pCurrAct); |
| |
| internal::MtfAutoPtr pMtf( |
| new ::GDIMetaFile( pAct->GetGDIMetaFile() ) ); |
| |
| // TODO(P2): Use native canvas gradients here (saves a lot of UNO calls) |
| internal::GradientAutoPtr pGradient( |
| new Gradient( pAct->GetGradient() ) ); |
| |
| DBG_TESTSOLARMUTEX(); |
| |
| ActionSharedPtr pFloatTransAction( |
| internal::TransparencyGroupActionFactory::createTransparencyGroupAction( |
| pMtf, |
| pGradient, |
| rParms, |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DPointFromPoint( pAct->GetPoint() ), |
| getState( rStates ).mapModeTransform * |
| ::vcl::unotools::b2DSizeFromSize( pAct->GetSize() ), |
| rCanvas, |
| getState( rStates ) ) ); |
| |
| if( pFloatTransAction ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pFloatTransAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pFloatTransAction->getActionCount()-1; |
| } |
| } |
| break; |
| |
| case META_TEXT_ACTION: |
| { |
| MetaTextAction* pAct = static_cast<MetaTextAction*>(pCurrAct); |
| XubString sText = XubString( pAct->GetText() ); |
| |
| if( rVDev.GetDigitLanguage()) |
| convertToLocalizedNumerals ( sText,rVDev.GetDigitLanguage() ); |
| |
| createTextAction( |
| pAct->GetPoint(), |
| sText, |
| pAct->GetIndex(), |
| pAct->GetLen() == (sal_uInt16)STRING_LEN ? pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen(), |
| NULL, |
| rFactoryParms, |
| bSubsettableActions ); |
| } |
| break; |
| |
| case META_TEXTARRAY_ACTION: |
| { |
| MetaTextArrayAction* pAct = static_cast<MetaTextArrayAction*>(pCurrAct); |
| XubString sText = XubString( pAct->GetText() ); |
| |
| if( rVDev.GetDigitLanguage()) |
| convertToLocalizedNumerals ( sText,rVDev.GetDigitLanguage() ); |
| |
| createTextAction( |
| pAct->GetPoint(), |
| sText, |
| pAct->GetIndex(), |
| pAct->GetLen() == (sal_uInt16)STRING_LEN ? pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen(), |
| pAct->GetDXArray(), |
| rFactoryParms, |
| bSubsettableActions ); |
| } |
| break; |
| |
| case META_TEXTLINE_ACTION: |
| { |
| MetaTextLineAction* pAct = static_cast<MetaTextLineAction*>(pCurrAct); |
| |
| const OutDevState& rState( getState( rStates ) ); |
| const ::Size aBaselineOffset( tools::getBaselineOffset( rState, |
| rVDev ) ); |
| const ::Point aStartPoint( pAct->GetStartPoint() ); |
| const ::basegfx::B2DSize aSize( rState.mapModeTransform * |
| ::basegfx::B2DSize(pAct->GetWidth(), |
| 0 )); |
| |
| ActionSharedPtr pPolyAction( |
| PolyPolyActionFactory::createPolyPolyAction( |
| tools::createTextLinesPolyPolygon( |
| rState.mapModeTransform * |
| ::basegfx::B2DPoint( |
| ::vcl::unotools::b2DPointFromPoint(pAct->GetStartPoint()) + |
| ::vcl::unotools::b2DSizeFromSize(aBaselineOffset)), |
| aSize.getX(), |
| tools::createTextLineInfo( rVDev, |
| rState )), |
| rCanvas, |
| rState ) ); |
| |
| if( pPolyAction.get() ) |
| { |
| maActions.push_back( |
| MtfAction( |
| pPolyAction, |
| io_rCurrActionIndex ) ); |
| |
| io_rCurrActionIndex += pPolyAction->getActionCount()-1; |
| } |
| } |
| break; |
| |
| case META_TEXTRECT_ACTION: |
| { |
| MetaTextRectAction* pAct = static_cast<MetaTextRectAction*>(pCurrAct); |
| |
| pushState( rStates, PUSH_ALL ); |
| |
| // use the VDev to break up the text rect |
| // action into readily formatted lines |
| GDIMetaFile aTmpMtf; |
| rVDev.AddTextRectActions( pAct->GetRect(), |
| pAct->GetText(), |
| pAct->GetStyle(), |
| aTmpMtf ); |
| |
| createActions( aTmpMtf, |
| rFactoryParms, |
| bSubsettableActions ); |
| |
| popState( rStates ); |
| |
| break; |
| } |
| |
| case META_STRETCHTEXT_ACTION: |
| { |
| MetaStretchTextAction* pAct = static_cast<MetaStretchTextAction*>(pCurrAct); |
| XubString sText = XubString( pAct->GetText() ); |
| |
| if( rVDev.GetDigitLanguage()) |
| convertToLocalizedNumerals ( sText,rVDev.GetDigitLanguage() ); |
| |
| const sal_uInt16 nLen( pAct->GetLen() == (sal_uInt16)STRING_LEN ? |
| pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen() ); |
| |
| // #i70897# Nothing to do, actually... |
| if( nLen == 0 ) |
| break; |
| |
| // have to fit the text into the given |
| // width. This is achieved by internally |
| // generating a DX array, and uniformly |
| // distributing the excess/insufficient width |
| // to every logical character. |
| ::boost::scoped_array< sal_Int32 > pDXArray( new sal_Int32[nLen] ); |
| |
| rVDev.GetTextArray( pAct->GetText(), pDXArray.get(), |
| pAct->GetIndex(), pAct->GetLen() ); |
| |
| const sal_Int32 nWidthDifference( pAct->GetWidth() - pDXArray[ nLen-1 ] ); |
| |
| // Last entry of pDXArray contains total width of the text |
| sal_Int32* p=pDXArray.get(); |
| for( sal_uInt16 i=1; i<=nLen; ++i ) |
| { |
| // calc ratio for every array entry, to |
| // distribute rounding errors 'evenly' |
| // across the characters. Note that each |
| // entry represents the 'end' position of |
| // the corresponding character, thus, we |
| // let i run from 1 to nLen. |
| *p++ += (sal_Int32)i*nWidthDifference/nLen; |
| } |
| |
| createTextAction( |
| pAct->GetPoint(), |
| sText, |
| pAct->GetIndex(), |
| pAct->GetLen() == (sal_uInt16)STRING_LEN ? pAct->GetText().Len() - pAct->GetIndex() : pAct->GetLen(), |
| pDXArray.get(), |
| rFactoryParms, |
| bSubsettableActions ); |
| } |
| break; |
| |
| default: |
| OSL_ENSURE( false, |
| "Unknown meta action type encountered" ); |
| break; |
| } |
| |
| // increment action index (each mtf action counts _at |
| // least_ one. Some count for more, therefore, |
| // io_rCurrActionIndex is sometimes incremented by |
| // pAct->getActionCount()-1 above, the -1 being the |
| // correction for the unconditional increment here). |
| ++io_rCurrActionIndex; |
| } |
| |
| return true; |
| } |
| |
| |
| namespace |
| { |
| class ActionRenderer |
| { |
| public: |
| ActionRenderer( const ::basegfx::B2DHomMatrix& rTransformation ) : |
| maTransformation( rTransformation ), |
| mbRet( true ) |
| { |
| } |
| |
| bool result() |
| { |
| return mbRet; |
| } |
| |
| void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction ) |
| { |
| // ANDing the result. We want to fail if at least |
| // one action failed. |
| mbRet &= rAction.mpAction->render( maTransformation ); |
| } |
| |
| void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction, |
| const Action::Subset& rSubset ) |
| { |
| // ANDing the result. We want to fail if at least |
| // one action failed. |
| mbRet &= rAction.mpAction->render( maTransformation, |
| rSubset ); |
| } |
| |
| private: |
| ::basegfx::B2DHomMatrix maTransformation; |
| bool mbRet; |
| }; |
| |
| class AreaQuery |
| { |
| public: |
| AreaQuery( const ::basegfx::B2DHomMatrix& rTransformation ) : |
| maTransformation( rTransformation ), |
| maBounds() |
| { |
| } |
| |
| bool result() |
| { |
| return true; // nothing can fail here |
| } |
| |
| void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction ) |
| { |
| maBounds.expand( rAction.mpAction->getBounds( maTransformation ) ); |
| } |
| |
| void operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rAction, |
| const Action::Subset& rSubset ) |
| { |
| maBounds.expand( rAction.mpAction->getBounds( maTransformation, |
| rSubset ) ); |
| } |
| |
| ::basegfx::B2DRange getBounds() const |
| { |
| return maBounds; |
| } |
| |
| private: |
| ::basegfx::B2DHomMatrix maTransformation; |
| ::basegfx::B2DRange maBounds; |
| }; |
| |
| // Doing that via inline class. Compilers tend to not inline free |
| // functions. |
| struct UpperBoundActionIndexComparator |
| { |
| bool operator()( const ::cppcanvas::internal::ImplRenderer::MtfAction& rLHS, |
| const ::cppcanvas::internal::ImplRenderer::MtfAction& rRHS ) |
| { |
| const sal_Int32 nLHSCount( rLHS.mpAction ? |
| rLHS.mpAction->getActionCount() : 0 ); |
| const sal_Int32 nRHSCount( rRHS.mpAction ? |
| rRHS.mpAction->getActionCount() : 0 ); |
| |
| // compare end of action range, to have an action selected |
| // by lower_bound even if the requested index points in |
| // the middle of the action's range |
| return rLHS.mnOrigIndex + nLHSCount < rRHS.mnOrigIndex + nRHSCount; |
| } |
| }; |
| |
| /** Algorithm to apply given functor to a subset range |
| |
| @tpl Functor |
| |
| Functor to call for each element of the subset |
| range. Must provide the following method signatures: |
| bool result() (returning false if operation failed) |
| |
| */ |
| template< typename Functor > bool |
| forSubsetRange( Functor& rFunctor, |
| ImplRenderer::ActionVector::const_iterator aRangeBegin, |
| ImplRenderer::ActionVector::const_iterator aRangeEnd, |
| sal_Int32 nStartIndex, |
| sal_Int32 nEndIndex, |
| const ImplRenderer::ActionVector::const_iterator& rEnd ) |
| { |
| if( aRangeBegin == aRangeEnd ) |
| { |
| // only a single action. Setup subset, and call functor |
| Action::Subset aSubset; |
| aSubset.mnSubsetBegin = ::std::max( sal_Int32( 0 ), |
| nStartIndex - aRangeBegin->mnOrigIndex ); |
| aSubset.mnSubsetEnd = ::std::min( aRangeBegin->mpAction->getActionCount(), |
| nEndIndex - aRangeBegin->mnOrigIndex ); |
| |
| ENSURE_OR_RETURN_FALSE( aSubset.mnSubsetBegin >= 0 && aSubset.mnSubsetEnd >= 0, |
| "ImplRenderer::forSubsetRange(): Invalid indices" ); |
| |
| rFunctor( *aRangeBegin, aSubset ); |
| } |
| else |
| { |
| // more than one action. |
| |
| // render partial first, full intermediate, and |
| // partial last action |
| Action::Subset aSubset; |
| aSubset.mnSubsetBegin = ::std::max( sal_Int32( 0 ), |
| nStartIndex - aRangeBegin->mnOrigIndex ); |
| aSubset.mnSubsetEnd = aRangeBegin->mpAction->getActionCount(); |
| |
| ENSURE_OR_RETURN_FALSE( aSubset.mnSubsetBegin >= 0 && aSubset.mnSubsetEnd >= 0, |
| "ImplRenderer::forSubsetRange(): Invalid indices" ); |
| |
| rFunctor( *aRangeBegin, aSubset ); |
| |
| // first action rendered, skip to next |
| ++aRangeBegin; |
| |
| // render full middle actions |
| while( aRangeBegin != aRangeEnd ) |
| rFunctor( *aRangeBegin++ ); |
| |
| if( aRangeEnd == rEnd || |
| aRangeEnd->mnOrigIndex > nEndIndex ) |
| { |
| // aRangeEnd denotes end of action vector, |
| // |
| // or |
| // |
| // nEndIndex references something _after_ |
| // aRangeBegin, but _before_ aRangeEnd |
| // |
| // either way: no partial action left |
| return rFunctor.result(); |
| } |
| |
| aSubset.mnSubsetBegin = 0; |
| aSubset.mnSubsetEnd = nEndIndex - aRangeEnd->mnOrigIndex; |
| |
| ENSURE_OR_RETURN_FALSE( aSubset.mnSubsetBegin >= 0 && aSubset.mnSubsetEnd >= 0, |
| "ImplRenderer::forSubsetRange(): Invalid indices" ); |
| |
| rFunctor( *aRangeEnd, aSubset ); |
| } |
| |
| return rFunctor.result(); |
| } |
| } |
| |
| bool ImplRenderer::getSubsetIndices( sal_Int32& io_rStartIndex, |
| sal_Int32& io_rEndIndex, |
| ActionVector::const_iterator& o_rRangeBegin, |
| ActionVector::const_iterator& o_rRangeEnd ) const |
| { |
| ENSURE_OR_RETURN_FALSE( io_rStartIndex<=io_rEndIndex, |
| "ImplRenderer::getSubsetIndices(): invalid action range" ); |
| |
| ENSURE_OR_RETURN_FALSE( !maActions.empty(), |
| "ImplRenderer::getSubsetIndices(): no actions to render" ); |
| |
| const sal_Int32 nMinActionIndex( maActions.front().mnOrigIndex ); |
| const sal_Int32 nMaxActionIndex( maActions.back().mnOrigIndex + |
| maActions.back().mpAction->getActionCount() ); |
| |
| // clip given range to permissible values (there might be |
| // ranges before and behind the valid indices) |
| io_rStartIndex = ::std::max( nMinActionIndex, |
| io_rStartIndex ); |
| io_rEndIndex = ::std::min( nMaxActionIndex, |
| io_rEndIndex ); |
| |
| if( io_rStartIndex == io_rEndIndex || |
| io_rStartIndex > io_rEndIndex ) |
| { |
| // empty range, don't render anything. The second |
| // condition e.g. happens if the requested range lies |
| // fully before or behind the valid action indices. |
| return false; |
| } |
| |
| |
| const ActionVector::const_iterator aBegin( maActions.begin() ); |
| const ActionVector::const_iterator aEnd( maActions.end() ); |
| |
| |
| // find start and end action |
| // ========================= |
| o_rRangeBegin = ::std::lower_bound( aBegin, aEnd, |
| MtfAction( ActionSharedPtr(), io_rStartIndex ), |
| UpperBoundActionIndexComparator() ); |
| o_rRangeEnd = ::std::lower_bound( aBegin, aEnd, |
| MtfAction( ActionSharedPtr(), io_rEndIndex ), |
| UpperBoundActionIndexComparator() ); |
| return true; |
| } |
| |
| |
| // Public methods |
| // ==================================================================== |
| |
| ImplRenderer::ImplRenderer( const CanvasSharedPtr& rCanvas, |
| const GDIMetaFile& rMtf, |
| const Parameters& rParams ) : |
| CanvasGraphicHelper( rCanvas ), |
| maActions() |
| { |
| RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::ImplRenderer::ImplRenderer(mtf)" ); |
| |
| OSL_ENSURE( rCanvas.get() != NULL && rCanvas->getUNOCanvas().is(), |
| "ImplRenderer::ImplRenderer(): Invalid canvas" ); |
| OSL_ENSURE( rCanvas->getUNOCanvas()->getDevice().is(), |
| "ImplRenderer::ImplRenderer(): Invalid graphic device" ); |
| |
| // make sure canvas and graphic device are valid; action |
| // creation don't check that every time |
| if( rCanvas.get() == NULL || |
| !rCanvas->getUNOCanvas().is() || |
| !rCanvas->getUNOCanvas()->getDevice().is() ) |
| { |
| // leave actions empty |
| return; |
| } |
| |
| VectorOfOutDevStates aStateStack; |
| |
| VirtualDevice aVDev; |
| aVDev.EnableOutput( sal_False ); |
| |
| // Setup VDev for state tracking and mapping |
| // ========================================= |
| |
| aVDev.SetMapMode( rMtf.GetPrefMapMode() ); |
| |
| const Size aMtfSize( rMtf.GetPrefSize() ); |
| const Size aMtfSizePixPre( aVDev.LogicToPixel( aMtfSize, |
| rMtf.GetPrefMapMode() ) ); |
| const Point aEmptyPt; |
| const Point aMtfOriginPix( aVDev.LogicToPixel( aEmptyPt ) ); |
| |
| // #i44110# correct null-sized output - there are shapes |
| // which have zero size in at least one dimension |
| const Size aMtfSizePix( ::std::max( aMtfSizePixPre.Width(), 1L ), |
| ::std::max( aMtfSizePixPre.Height(), 1L ) ); |
| |
| sal_Int32 nCurrActions(0); |
| ActionFactoryParameters aParms(aStateStack, |
| rCanvas, |
| aVDev, |
| rParams, |
| nCurrActions ); |
| |
| // init state stack |
| clearStateStack( aStateStack ); |
| |
| // Setup local state, such that the metafile renders |
| // itself into a one-by-one square at the origin for |
| // identity view and render transformations |
| getState( aStateStack ).transform.scale( 1.0 / aMtfSizePix.Width(), |
| 1.0 / aMtfSizePix.Height() ); |
| |
| tools::calcLogic2PixelAffineTransform( getState( aStateStack ).mapModeTransform, |
| aVDev ); |
| |
| ColorSharedPtr pColor( getCanvas()->createColor() ); |
| |
| // setup default text color to black |
| getState( aStateStack ).textColor = |
| getState( aStateStack ).textFillColor = |
| getState( aStateStack ).textLineColor = pColor->getDeviceColor( 0x000000FF ); |
| |
| // apply overrides from the Parameters struct |
| if( rParams.maFillColor.is_initialized() ) |
| { |
| getState( aStateStack ).isFillColorSet = true; |
| getState( aStateStack ).fillColor = pColor->getDeviceColor( *rParams.maFillColor ); |
| } |
| if( rParams.maLineColor.is_initialized() ) |
| { |
| getState( aStateStack ).isLineColorSet = true; |
| getState( aStateStack ).lineColor = pColor->getDeviceColor( *rParams.maLineColor ); |
| } |
| if( rParams.maTextColor.is_initialized() ) |
| { |
| getState( aStateStack ).isTextFillColorSet = true; |
| getState( aStateStack ).isTextLineColorSet = true; |
| getState( aStateStack ).textColor = |
| getState( aStateStack ).textFillColor = |
| getState( aStateStack ).textLineColor = pColor->getDeviceColor( *rParams.maTextColor ); |
| } |
| if( rParams.maFontName.is_initialized() || |
| rParams.maFontWeight.is_initialized() || |
| rParams.maFontLetterForm.is_initialized() || |
| rParams.maFontUnderline.is_initialized() || |
| rParams.maFontProportion.is_initialized() ) |
| { |
| ::cppcanvas::internal::OutDevState& rState = getState( aStateStack ); |
| |
| rState.xFont = createFont( rState.fontRotation, |
| ::Font(), // default font |
| aParms ); |
| } |
| |
| createActions( const_cast<GDIMetaFile&>(rMtf), // HACK(Q2): |
| // we're |
| // changing |
| // the |
| // current |
| // action |
| // in |
| // createActions! |
| aParms, |
| true // TODO(P1): make subsettability configurable |
| ); |
| } |
| |
| ImplRenderer::ImplRenderer( const CanvasSharedPtr& rCanvas, |
| const BitmapEx& rBmpEx, |
| const Parameters& rParams ) : |
| CanvasGraphicHelper( rCanvas ), |
| maActions() |
| { |
| // TODO(F3): property modification parameters are |
| // currently ignored for Bitmaps |
| (void)rParams; |
| |
| RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::ImplRenderer::ImplRenderer(bitmap)" ); |
| |
| OSL_ENSURE( rCanvas.get() != NULL && rCanvas->getUNOCanvas().is(), |
| "ImplRenderer::ImplRenderer(): Invalid canvas" ); |
| OSL_ENSURE( rCanvas->getUNOCanvas()->getDevice().is(), |
| "ImplRenderer::ImplRenderer(): Invalid graphic device" ); |
| |
| // make sure canvas and graphic device are valid; action |
| // creation don't check that every time |
| if( rCanvas.get() == NULL || |
| !rCanvas->getUNOCanvas().is() || |
| !rCanvas->getUNOCanvas()->getDevice().is() ) |
| { |
| // leave actions empty |
| return; |
| } |
| |
| OutDevState aState; |
| |
| const Size aBmpSize( rBmpEx.GetSizePixel() ); |
| |
| // Setup local state, such that the bitmap renders itself |
| // into a one-by-one square for identity view and render |
| // transformations |
| aState.transform.scale( 1.0 / aBmpSize.Width(), |
| 1.0 / aBmpSize.Height() ); |
| |
| // create a single action for the provided BitmapEx |
| maActions.push_back( |
| MtfAction( |
| BitmapActionFactory::createBitmapAction( |
| rBmpEx, |
| ::basegfx::B2DPoint(), |
| rCanvas, |
| aState), |
| 0 ) ); |
| } |
| |
| ImplRenderer::~ImplRenderer() |
| { |
| } |
| |
| bool ImplRenderer::drawSubset( sal_Int32 nStartIndex, |
| sal_Int32 nEndIndex ) const |
| { |
| RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::ImplRenderer::drawSubset()" ); |
| |
| ActionVector::const_iterator aRangeBegin; |
| ActionVector::const_iterator aRangeEnd; |
| |
| try |
| { |
| if( !getSubsetIndices( nStartIndex, nEndIndex, |
| aRangeBegin, aRangeEnd ) ) |
| return true; // nothing to render (but _that_ was successful) |
| |
| // now, aRangeBegin references the action in which the |
| // subset rendering must start, and aRangeEnd references |
| // the action in which the subset rendering must end (it |
| // might also end right at the start of the referenced |
| // action, such that zero of that action needs to be |
| // rendered). |
| |
| |
| // render subset of actions |
| // ======================== |
| |
| ::basegfx::B2DHomMatrix aMatrix; |
| ::canvas::tools::getRenderStateTransform( aMatrix, |
| getRenderState() ); |
| |
| ActionRenderer aRenderer( aMatrix ); |
| |
| return forSubsetRange( aRenderer, |
| aRangeBegin, |
| aRangeEnd, |
| nStartIndex, |
| nEndIndex, |
| maActions.end() ); |
| } |
| catch( uno::Exception& ) |
| { |
| OSL_ENSURE( false, |
| rtl::OUStringToOString( |
| comphelper::anyToString( cppu::getCaughtException() ), |
| RTL_TEXTENCODING_UTF8 ).getStr() ); |
| |
| // convert error to return value |
| return false; |
| } |
| } |
| |
| ::basegfx::B2DRange ImplRenderer::getSubsetArea( sal_Int32 nStartIndex, |
| sal_Int32 nEndIndex ) const |
| { |
| RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::ImplRenderer::getSubsetArea()" ); |
| |
| ActionVector::const_iterator aRangeBegin; |
| ActionVector::const_iterator aRangeEnd; |
| |
| if( !getSubsetIndices( nStartIndex, nEndIndex, |
| aRangeBegin, aRangeEnd ) ) |
| return ::basegfx::B2DRange(); // nothing to render -> empty range |
| |
| // now, aRangeBegin references the action in which the |
| // subset querying must start, and aRangeEnd references |
| // the action in which the subset querying must end (it |
| // might also end right at the start of the referenced |
| // action, such that zero of that action needs to be |
| // queried). |
| |
| |
| // query bounds for subset of actions |
| // ================================== |
| |
| ::basegfx::B2DHomMatrix aMatrix; |
| ::canvas::tools::getRenderStateTransform( aMatrix, |
| getRenderState() ); |
| |
| AreaQuery aQuery( aMatrix ); |
| forSubsetRange( aQuery, |
| aRangeBegin, |
| aRangeEnd, |
| nStartIndex, |
| nEndIndex, |
| maActions.end() ); |
| |
| return aQuery.getBounds(); |
| } |
| |
| bool ImplRenderer::draw() const |
| { |
| RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::ImplRenderer::draw()" ); |
| |
| ::basegfx::B2DHomMatrix aMatrix; |
| ::canvas::tools::getRenderStateTransform( aMatrix, |
| getRenderState() ); |
| |
| try |
| { |
| return ::std::for_each( maActions.begin(), maActions.end(), ActionRenderer( aMatrix ) ).result(); |
| } |
| catch( uno::Exception& ) |
| { |
| OSL_ENSURE( false, |
| rtl::OUStringToOString( |
| comphelper::anyToString( cppu::getCaughtException() ), |
| RTL_TEXTENCODING_UTF8 ).getStr() ); |
| |
| return false; |
| } |
| } |
| } |
| } |