diff options
Diffstat (limited to 'filter/source/flash/swfwriter1.cxx')
-rw-r--r-- | filter/source/flash/swfwriter1.cxx | 2126 |
1 files changed, 2126 insertions, 0 deletions
diff --git a/filter/source/flash/swfwriter1.cxx b/filter/source/flash/swfwriter1.cxx new file mode 100644 index 000000000000..cce0314436f8 --- /dev/null +++ b/filter/source/flash/swfwriter1.cxx @@ -0,0 +1,2126 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_filter.hxx" + +#include <com/sun/star/i18n/ScriptType.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <comphelper/processfactory.hxx> +#include "swfwriter.hxx" +#include <vcl/metaact.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/bmpacc.hxx> +#include <vcl/virdev.hxx> +#include <vcl/metric.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <svtools/filter.hxx> +#include <vcl/graphictools.hxx> + +#ifndef _ZLIB_H +#ifdef SYSTEM_ZLIB +#include <zlib.h> +#else +#include <external/zlib/zlib.h> +#endif +#endif + +#include <vcl/salbtype.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> + +using namespace ::swf; +using namespace ::std; +using namespace ::rtl; +using namespace ::com::sun::star::i18n; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::io; +using namespace ::com::sun::star::beans; + +extern sal_uInt16 getMaxBitsUnsigned( sal_uInt32 nValue ); +extern sal_uInt16 getMaxBitsSigned( sal_Int32 nValue ); + +static MapMode aTWIPSMode( MAP_TWIP ); +static MapMode a100thmmMode( MAP_100TH_MM ); + +// ----------------------------------------------------------------------------- + +Point Writer::map( const Point& rPoint ) const +{ + const MapMode& aSourceMapMode = mpVDev->GetMapMode(); + + Point retPoint = mpVDev->LogicToLogic( rPoint, &aSourceMapMode, &aTWIPSMode ); + + // AS: Produces a 'possible loss of data' warning that we can't fix without + // hurting code readability. + retPoint.X() = (long)( retPoint.X() * mnDocXScale ); + retPoint.Y() = (long)( retPoint.Y() * mnDocYScale ); + + return retPoint; +} + +// ----------------------------------------------------------------------------- + +Size Writer::map( const Size& rSize ) const +{ + const MapMode& aSourceMapMode = mpVDev->GetMapMode(); + + Size retSize = mpVDev->LogicToLogic( rSize, &aSourceMapMode, &aTWIPSMode ); + + // AS: Produces a 'possible loss of data' warning that we can't fix without + // hurting code readability. + retSize.Width() = (long)( retSize.Width() * mnDocXScale ); + retSize.Height() = (long)( retSize.Height() * mnDocYScale ); + + return retSize; +} + +// ----------------------------------------------------------------------------- + +void Writer::map( PolyPolygon& rPolyPolygon ) const +{ + const sal_uInt16 nPolyCount = rPolyPolygon.Count(); + if( nPolyCount ) + { + sal_uInt16 nPoly, nPoint, nPointCount; + for( nPoly = 0; nPoly < nPolyCount; nPoly++ ) + { + Polygon& rPoly = rPolyPolygon[nPoly]; + nPointCount = rPoly.GetSize(); + + for( nPoint = 0; nPoint < nPointCount; nPoint++ ) + { + rPoly[nPoint] = map( rPoly[nPoint] ); + } + } + } +} + +// ----------------------------------------------------------------------------- + +sal_Int32 Writer::mapRelative( sal_Int32 n100thMM ) const +{ + MapMode aSourceMapMode( mpVDev->GetMapMode() ); + aSourceMapMode.SetOrigin( Point() ); + + sal_Int32 nTwips = mpVDev->LogicToLogic( Point( n100thMM, n100thMM ), &aSourceMapMode, &aTWIPSMode ).X(); + return nTwips; +} + +// ----------------------------------------------------------------------------- + +/** +*/ +void Writer::Impl_addPolygon( BitStream& rBits, const Polygon& rPoly, sal_Bool bFilled ) +{ + Point aLastPoint( rPoly[0] ); + + Impl_addShapeRecordChange( rBits, _Int16(aLastPoint.X()),_Int16(aLastPoint.Y()), bFilled ); + + USHORT i = 0, nSize = rPoly.GetSize(); + + double d = 16.0f; + + // points + while( ( i + 1 ) < nSize ) + { + if( ( i + 3 ) < nSize ) + { + PolyFlags P1( rPoly.GetFlags( i ) ); + PolyFlags P4( rPoly.GetFlags( i + 3 ) ); + + if( ( POLY_NORMAL == P1 || POLY_SMOOTH == P1 || POLY_SYMMTR == P1 ) && + ( POLY_CONTROL == rPoly.GetFlags( i + 1 ) ) && + ( POLY_CONTROL == rPoly.GetFlags( i + 2 ) ) && + ( POLY_NORMAL == P4 || POLY_SMOOTH == P4 || POLY_SYMMTR == P4 ) ) + { + Impl_quadBezierApprox( rBits, aLastPoint, d*d, + rPoly.GetPoint( i ).X(), rPoly.GetPoint( i ).Y(), + rPoly.GetPoint( i+1 ).X(), rPoly.GetPoint( i+1 ).Y(), + rPoly.GetPoint( i+2 ).X(), rPoly.GetPoint( i+2 ).Y(), + rPoly.GetPoint( i+3 ).X(), rPoly.GetPoint( i+3 ).Y() ); + i += 3; + continue; + } + } + + ++i; + + const Point aPolyPoint( rPoly[ i ] ); + if( aPolyPoint != aLastPoint ) + { + Impl_addStraightEdgeRecord( rBits, _Int16(aPolyPoint.X() - aLastPoint.X()),_Int16(aPolyPoint.Y() - aLastPoint.Y())); + aLastPoint = aPolyPoint; + } + } + + if( bFilled && (rPoly[0] != rPoly[nSize-1])) + { + const Point aPolyPoint( rPoly[ 0 ] ); + if( aPolyPoint != aLastPoint ) + { + Impl_addStraightEdgeRecord( rBits, _Int16(aPolyPoint.X() - aLastPoint.X()),_Int16(aPolyPoint.Y() - aLastPoint.Y())); + } + } +} + +// ----------------------------------------------------------------------------- + +/** exports a style change record with a move to (x,y) and depending on bFilled a line style 1 or fill style 1 +*/ +void Writer::Impl_addShapeRecordChange( BitStream& rBits, sal_Int16 dx, sal_Int16 dy, sal_Bool bFilled ) +{ + rBits.writeUB( 0, 1 ); // TypeFlag + rBits.writeUB( 0, 1 ); // StateNewStyles + rBits.writeUB( !bFilled, 1 ); // StateLineStyle + rBits.writeUB( 0, 1 ); // StateFillStyle0 + rBits.writeUB( bFilled, 1 ); // StateFillStyle1 + rBits.writeUB( 1, 1 ); // StateMoveTo + + sal_uInt16 nMoveBits = max( getMaxBitsSigned( dx ), getMaxBitsSigned( dy ) ); + + rBits.writeUB( nMoveBits, 5 ); // Number of bits per value + // TODO: Optimize horizontal and vertical lines + rBits.writeSB( dx, nMoveBits ); // DeltaX + rBits.writeSB( dy, nMoveBits ); // DeltaY + + rBits.writeUB( 1, 1 ); // set FillStyle1 or LineStyle to 1 +} + +// ----------------------------------------------------------------------------- + +/** exports a straight edge record +*/ +void Writer::Impl_addStraightEdgeRecord( BitStream& rBits, sal_Int16 dx, sal_Int16 dy ) +{ + rBits.writeUB( 1, 1 ); // TypeFlag + rBits.writeUB( 1, 1 ); // StraightFlag + + sal_uInt16 nBits = max( getMaxBitsSigned( dx ), getMaxBitsSigned( dy ) ); + + rBits.writeUB( nBits - 2, 4 ); // Number of bits per value + + if( (dx != 0) && (dy != 0) ) + { + rBits.writeUB( 1, 1 ); // GeneralLineFlag + rBits.writeSB( dx, nBits ); // DeltaX + rBits.writeSB( dy, nBits ); // DeltaY + } + else + { + rBits.writeUB( 0, 1 ); + rBits.writeUB( ( dx == 0 ), 1 ); + if( dx == 0 ) + { + rBits.writeSB( dy, nBits ); // DeltaY + } + else + { + rBits.writeSB( dx, nBits ); // DeltaX + } + } +} + +// ----------------------------------------------------------------------------- + +/** exports a curved edge record +*/ +void Writer::Impl_addCurvedEdgeRecord( BitStream& rBits, sal_Int16 control_dx, sal_Int16 control_dy, sal_Int16 anchor_dx, sal_Int16 anchor_dy ) +{ + rBits.writeUB( 1, 1 ); // TypeFlag + rBits.writeUB( 0, 1 ); // CurvedFlag + + sal_uInt8 nBits = static_cast<sal_uInt8>( + max( getMaxBitsSigned( control_dx ), + max( getMaxBitsSigned( control_dy ), + max( getMaxBitsSigned( anchor_dx ), + max( getMaxBitsSigned( anchor_dy ), (sal_uInt16)3 ) ) ) ) ); + + rBits.writeUB( nBits - 2, 4 ); // Number of bits per value + + rBits.writeSB( control_dx, nBits ); // DeltaX + rBits.writeSB( control_dy, nBits ); // DeltaY + rBits.writeSB( anchor_dx, nBits ); // DeltaX + rBits.writeSB( anchor_dy, nBits ); // DeltaY +} + +// ----------------------------------------------------------------------------- + +/** exports a end shape record +*/ +void Writer::Impl_addEndShapeRecord( BitStream& rBits ) +{ + rBits.writeUB( 0, 6 ); +} + +// ----------------------------------------------------------------------------- + +void Writer::Impl_writePolygon( const Polygon& rPoly, sal_Bool bFilled ) +{ + PolyPolygon aPolyPoly( rPoly ); + Impl_writePolyPolygon( aPolyPoly, bFilled ); +} + +// ----------------------------------------------------------------------------- + +void Writer::Impl_writePolygon( const Polygon& rPoly, sal_Bool bFilled, const Color& rFillColor, const Color& rLineColor ) +{ + PolyPolygon aPolyPoly( rPoly ); + Impl_writePolyPolygon( aPolyPoly, bFilled, rFillColor, rLineColor ); +} + +// ----------------------------------------------------------------------------- + +void Writer::Impl_writePolyPolygon( const PolyPolygon& rPolyPoly, sal_Bool bFilled, sal_uInt8 nTransparence /* = 0 */ ) +{ + Color aLineColor( mpVDev->GetLineColor() ); + if( 0 == aLineColor.GetTransparency() ) + aLineColor.SetTransparency( nTransparence ); + Color aFillColor( mpVDev->GetFillColor() ); + if( 0 == aFillColor.GetTransparency() ) + aFillColor.SetTransparency( nTransparence ); + Impl_writePolyPolygon(rPolyPoly, bFilled, aFillColor, aLineColor ); +} + +// ----------------------------------------------------------------------------- + +void Writer::Impl_writePolyPolygon( const PolyPolygon& rPolyPoly, sal_Bool bFilled, const Color& rFillColor, const Color& rLineColor ) +{ + PolyPolygon aPolyPoly( rPolyPoly ); + + if( aPolyPoly.Count() ) + { + map( aPolyPoly ); + + if( mpClipPolyPolygon ) + rPolyPoly.GetIntersection( *mpClipPolyPolygon, aPolyPoly ); + + sal_uInt16 nID; + if( bFilled ) + { + Color aFillColor( rFillColor ); + if( 0 != mnGlobalTransparency ) + aFillColor.SetTransparency( mnGlobalTransparency ); + + FillStyle aStyle( aFillColor ); + nID = defineShape( aPolyPoly, aStyle ); + } + else + { + Color aLineColor( rLineColor ); + if( 0 != mnGlobalTransparency ) + aLineColor.SetTransparency( mnGlobalTransparency ); + + nID = defineShape( aPolyPoly, 1, aLineColor ); + } + maShapeIds.push_back( nID ); + } +} + +// ----------------------------------------------------------------------------- + +/** a gradient is a transition from one color to another, rendered inside a given polypolygon */ +void Writer::Impl_writeGradientEx( const PolyPolygon& rPolyPoly, const Gradient& rGradient ) +{ + if( rPolyPoly.Count() ) + { + PolyPolygon aPolyPolygon( rPolyPoly ); + map( aPolyPolygon ); + + if( (rGradient.GetStyle() == GRADIENT_LINEAR && rGradient.GetAngle() == 900) || (rGradient.GetStyle() == GRADIENT_RADIAL) ) + { + const Rectangle aBoundRect( aPolyPolygon.GetBoundRect() ); + + FillStyle aFillStyle( aBoundRect, rGradient ); + + sal_uInt16 nShapeId = defineShape( aPolyPolygon, aFillStyle ); + maShapeIds.push_back( nShapeId ); + } + else + { + setClipping( &aPolyPolygon ); + + // render the gradient filling to simple polygons + { + GDIMetaFile aTmpMtf; + mpVDev->AddGradientActions( aPolyPolygon.GetBoundRect(), rGradient, aTmpMtf ); + Impl_writeActions( aTmpMtf ); + } + + setClipping( NULL ); + } + } +} + +// ----------------------------------------------------------------------------- + +void Writer::setClipping( const PolyPolygon* pClipPolyPolygon ) +{ + mpClipPolyPolygon = pClipPolyPolygon; +} + +// ----------------------------------------------------------------------------- + +// AS: Just comparing fonts straight up is too literal. There are some +// differences in font that actually require different glyphs to be defined, +// and some that don't. This function is meant to capture all the differences +// that we care about. +bool compare_fonts_for_me(const Font& rFont1, const Font& rFont2) +{ + return rFont1.GetName() == rFont2.GetName() && + rFont1.GetWeight() == rFont2.GetWeight() && + rFont1.GetItalic() == rFont2.GetItalic() && + rFont1.IsOutline() == rFont2.IsOutline() && + rFont1.IsShadow() == rFont2.IsShadow() && + rFont1.GetRelief() == rFont2.GetRelief(); +} + +// ----------------------------------------------------------------------------- + +FlashFont& Writer::Impl_getFont( const Font& rFont ) +{ + FontMap::iterator aIter( maFonts.begin() ); + const FontMap::iterator aEnd( maFonts.end() ); + + while( aIter != aEnd ) + { + const Font tempFont = (*aIter)->getFont(); + if( compare_fonts_for_me(tempFont, rFont) ) + { + return **aIter; + } + + aIter++; + } + + FlashFont* pFont = new FlashFont( rFont, createID() ); + maFonts.push_back( pFont ); + return *pFont; +} + +// ----------------------------------------------------------------------------- + +void Writer::Impl_writeText( const Point& rPos, const String& rText, const sal_Int32* pDXArray, long nWidth ) +{ + const FontMetric aMetric( mpVDev->GetFontMetric() ); + + bool bTextSpecial = aMetric.IsShadow() || aMetric.IsOutline() || (aMetric.GetRelief() != RELIEF_NONE); + + if( !bTextSpecial ) + { + Impl_writeText( rPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); + } + else + { + if( aMetric.GetRelief() != RELIEF_NONE ) + { + Color aReliefColor( COL_LIGHTGRAY ); + Color aTextColor( mpVDev->GetTextColor() ); + + if ( aTextColor.GetColor() == COL_BLACK ) + aTextColor = Color( COL_WHITE ); + + if ( aTextColor.GetColor() == COL_WHITE ) + aReliefColor = Color( COL_BLACK ); + + + Point aPos( rPos ); + Point aOffset( 6,6 ); + + if ( aMetric.GetRelief() == RELIEF_ENGRAVED ) + { + aPos -= aOffset; + } + else + { + aPos += aOffset; + } + + Impl_writeText( aPos, rText, pDXArray, nWidth, aReliefColor ); + Impl_writeText( rPos, rText, pDXArray, nWidth, aTextColor ); + } + else + { + if( aMetric.IsShadow() ) + { + long nOff = 1 + ((aMetric.GetLineHeight()-24)/24); + if ( aMetric.IsOutline() ) + nOff += 6; + + Color aTextColor( mpVDev->GetTextColor() ); + Color aShadowColor = Color( COL_BLACK ); + + if ( (aTextColor.GetColor() == COL_BLACK) || (aTextColor.GetLuminance() < 8) ) + aShadowColor = Color( COL_LIGHTGRAY ); + + Point aPos( rPos ); + aPos += Point( nOff, nOff ); + Impl_writeText( aPos, rText, pDXArray, nWidth, aShadowColor ); + + if( !aMetric.IsOutline() ) + { + Impl_writeText( rPos, rText, pDXArray, nWidth, aTextColor ); + } + } + + if( aMetric.IsOutline() ) + { + Point aPos = rPos + Point( -6, -6 ); + Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); + aPos = rPos + Point(+6,+6); + Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); + aPos = rPos + Point(-6,+0); + Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); + aPos = rPos + Point(-6,+6); + Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); + aPos = rPos + Point(+0,+6); + Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); + aPos = rPos + Point(+0,-6); + Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); + aPos = rPos + Point(+6,-1); + Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); + aPos = rPos + Point(+6,+0); + Impl_writeText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() ); + + Impl_writeText( rPos, rText, pDXArray, nWidth, Color( COL_WHITE ) ); + } + } + } +} + +void Writer::Impl_writeText( const Point& rPos, const String& rText, const sal_Int32* pDXArray, long nWidth, Color aTextColor ) +{ + sal_uInt32 nLen = rText.Len(); + + if( !nLen ) + return; + + const bool bRTL = (mpVDev->GetLayoutMode() & TEXT_LAYOUT_BIDI_RTL) != 0; + + sal_Int16 nScriptType = ScriptType::LATIN; + Reference < XBreakIterator > xBI( Impl_GetBreakIterator() ); + if( xBI.is() ) + { + const OUString oText( rText ); + nScriptType = xBI->getScriptType( oText, 0 ); + } + + // if the text is either right to left or complex or asian, we + // ask the output device for a polygon representation. + // On complex and asian text, each unicode character can have + // different glyph representation, based on context. Also positioning + // is not trivial so we let the output device do it for us. + if( bRTL || (nScriptType != ScriptType::LATIN) ) + { + // todo: optimize me as this will generate a huge amount of duplicate polygons + PolyPolygon aPolyPoygon; + mpVDev->GetTextOutline( aPolyPoygon, rText, 0, 0, (USHORT)nLen, TRUE, nWidth, pDXArray ); + aPolyPoygon.Translate( rPos ); + Impl_writePolyPolygon( aPolyPoygon, sal_True, aTextColor, aTextColor ); + } + else + { + Size aNormSize; + sal_Int32* pOwnArray; + sal_Int32* pDX; + + // get text sizes + if( pDXArray ) + { + pOwnArray = NULL; + aNormSize = Size( mpVDev->GetTextWidth( rText ), 0 ); + pDX = (sal_Int32*) pDXArray; + } + else + { + pOwnArray = new sal_Int32[ nLen ]; + aNormSize = Size( mpVDev->GetTextArray( rText, pOwnArray ), 0 ); + pDX = pOwnArray; + } + + if( nLen > 1 ) + { + aNormSize.Width() = pDX[ nLen - 2 ] + mpVDev->GetTextWidth( rText.GetChar( (USHORT) nLen - 1 ) ); + + if( nWidth && aNormSize.Width() && ( nWidth != aNormSize.Width() ) ) + { + const double fFactor = (double) nWidth / aNormSize.Width(); + + sal_uInt32 i; + for( i = 0; i < ( nLen - 1 ); i++ ) + pDX[ i ] = FRound( pDX[ i ] * fFactor ); + } + } + + Font aOldFont( mpVDev->GetFont() ); + Point aBaseLinePos( rPos ); + + Font aFont(aOldFont); + short nOrientation = aFont.GetOrientation(); + aFont.SetOrientation( 0 ); + aFont.SetUnderline(UNDERLINE_NONE); + aFont.SetStrikeout(STRIKEOUT_NONE); + mpVDev->SetFont( aFont ); + + const FontMetric aMetric( mpVDev->GetFontMetric() ); + + FlashFont& rFlashFont = Impl_getFont( aFont ); + + // always adjust text position to match baseline alignment + switch( aOldFont.GetAlign() ) + { + case( ALIGN_TOP ): + aBaseLinePos.Y() += aMetric.GetAscent(); + break; + + case( ALIGN_BOTTOM ): + aBaseLinePos.Y() -= aMetric.GetDescent(); + break; + + default: + break; + } + + // get mapped text position + const Point aPt( map( aBaseLinePos ) ); + + // write text element + +/* test code to create a bound rect, not realy working for rotated text + Size aTextSize( map( Size( mpVDev->GetTextWidth( rText ), mpVDev->GetTextHeight() ) ) ); + Point aMetricPoint( map( Point( aMetric.GetLeading(), aMetric.GetAscent() ) ) ); + + Point aTmpPoint( map( Point( - aMetric.GetLeading(), - aMetric.GetAscent() ) ) ); ; + Rectangle aTmpRectangle(aTmpPoint, aTextSize ); + Polygon aPoly( aTmpRectangle ); + + aPoly.Rotate( aTmpPoint, (USHORT) nOrientation ); + + Rectangle aTextBoundRect( aPoly.GetBoundRect() ); + + aPoly.Move( aPt.X(), aPt.Y() - map( Size( 0, aMetric.GetDescent() ) ).Height() ); + +*/ + +#if 0 // makes the calculated bound rect visible for debuging +{ + Polygon aTmpPoly( aPoly ); + sal_uInt16 nID = FlashGeometryExporter::writePolygonShape( aMovieStream, aTmpPoly, false, Color(COL_MAGENTA), Color(COL_MAGENTA), mpClipPolyPolygon ); + ImplPlaceObject( nID ); +} +#endif + + // CL: This is still a hack until we figure out how to calculate a correct bound rect + // for rotatet text + Rectangle textBounds( 0, 0, static_cast<long>(mnDocWidth*mnDocXScale), static_cast<long>(mnDocHeight*mnDocYScale) ); + double scale = 1.0; + + // scale width if we have a stretched text + if( 0 != aFont.GetSize().Width() ) + { + Font aTmpFont( aFont ); + aTmpFont.SetWidth(0); + mpVDev->SetFont( aTmpFont ); + + const FontMetric aMetric2( mpVDev->GetFontMetric() ); + mpVDev->SetFont( aFont ); + + const long n1 = aFont.GetSize().Width(); + const long n2 = aMetric2.GetSize().Width(); + scale = (double)n1 / (double)n2; + } + + basegfx::B2DHomMatrix m(basegfx::tools::createRotateB2DHomMatrix(static_cast<double>(nOrientation) * F_PI1800)); + m.translate( double(aPt.X() / scale), double(aPt.Y()) ); + m.scale( scale, scale ); + + sal_Int16 nHeight = _Int16( map( Size( 0, aFont.GetHeight() ) ).Height() ); + + startTag( TAG_DEFINETEXT ); + + sal_uInt16 nTextId = createID(); + + mpTag->addUI16( nTextId ); + mpTag->addRect( textBounds ); + mpTag->addMatrix( m ); + + sal_uInt8 nGlyphBits = 16; + sal_uInt8 nAdvanceBits = 16; + + mpTag->addUI8( nGlyphBits ); + mpTag->addUI8( nAdvanceBits ); + + // text style change record + mpTag->addUI8( 0x8c ); + mpTag->addUI16( rFlashFont.getID() ); + mpTag->addRGB( aTextColor ); + mpTag->addUI16( _uInt16( nHeight ) ); + + DBG_ASSERT( nLen <= 127, "TODO: handle text with more than 127 characters" ); + + // Glyph record + mpTag->addUI8( (sal_uInt8) nLen ); + + BitStream aBits; + + sal_Int32 nLastDX = 0; + sal_Int32 nAdvance; + sal_uInt32 i; + for( i = 0; i < nLen; i++ ) + { + if( i < (nLen-1) ) + { + nAdvance = pDX[i] - nLastDX; + nLastDX = pDX[i]; + } + else + { + nAdvance = 0; + } + + aBits.writeUB( rFlashFont.getGlyph(rText.GetChar(_uInt16(i)),mpVDev), nGlyphBits ); + aBits.writeSB( _Int16(map( Size( (long)( nAdvance / scale ), 0 ) ).Width() ), nAdvanceBits ); + } + + mpTag->addBits( aBits ); + mpTag->addUI8( 0 ); + + endTag(); + + maShapeIds.push_back( nTextId ); + + // AS: Write strikeout and underline, if neccessary. This code was originally taken from the SVG + // export facility, although the positioning had to be tweaked a little. I can't explain the + // numbers, but the flash lines up very well with the original OOo document. All of this should + // probably be converted to polygons as part of the meta file, though, as we don't handle any + // fancy lines (like dashes). + if( ( aOldFont.GetStrikeout() != STRIKEOUT_NONE ) || ( aOldFont.GetUnderline() != UNDERLINE_NONE ) ) + { + Polygon aPoly( 4 ); + const long nLineHeight = Max( (long) FRound( aMetric.GetLineHeight() * 0.05 ), (long) 1 ); + + if( aOldFont.GetStrikeout() != STRIKEOUT_NONE ) + { + aPoly[ 0 ].X() = aBaseLinePos.X(); + aPoly[ 0 ].Y() = aBaseLinePos.Y() - FRound( aMetric.GetAscent() * 0.26 ) - nLineHeight; + aPoly[ 1 ].X() = aPoly[ 0 ].X() + aNormSize.Width() - 1; + aPoly[ 1 ].Y() = aPoly[ 0 ].Y(); + aPoly[ 2 ].X() = aPoly[ 1 ].X(); + aPoly[ 2 ].Y() = aPoly[ 1 ].Y() + nLineHeight - 1; + aPoly[ 3 ].X() = aPoly[ 0 ].X(); + aPoly[ 3 ].Y() = aPoly[ 2 ].Y(); + + Impl_writePolygon( aPoly, sal_True, aTextColor, aTextColor ); + } + + // AS: The factor of 1.5 on the nLineHeight is a magic number. I'm not sure why it works, + // but it looks good to me. + if( aOldFont.GetUnderline() != UNDERLINE_NONE ) + { + aPoly[ 0 ].X() = aBaseLinePos.X(); + aPoly[ 0 ].Y() = static_cast<long>(aBaseLinePos.Y() + 1.5*nLineHeight); + aPoly[ 1 ].X() = aPoly[ 0 ].X() + aNormSize.Width() - 1; + aPoly[ 1 ].Y() = aPoly[ 0 ].Y(); + aPoly[ 2 ].X() = aPoly[ 1 ].X(); + aPoly[ 2 ].Y() = aPoly[ 1 ].Y() + nLineHeight - 1; + aPoly[ 3 ].X() = aPoly[ 0 ].X(); + aPoly[ 3 ].Y() = aPoly[ 2 ].Y(); + + Impl_writePolygon( aPoly, sal_True, aTextColor, aTextColor ); + } + } + + mpVDev->SetFont( aOldFont ); + delete[] pOwnArray; + } +} + +// ----------------------------------------------------------------------------- +// AS: Because JPEGs require the alpha channel provided seperately (JPEG does not +// natively support alpha channel, but SWF lets you provide it seperately), we +// extract the alpha channel into a seperate array here. +void getBitmapData( const BitmapEx& aBmpEx, sal_uInt8*& tgadata, sal_uInt8*& tgaAlphadata, sal_uInt32& nWidth, sal_uInt32& nHeight ) +{ + if( !aBmpEx.IsEmpty() ) + { + Bitmap aBmp( aBmpEx.GetBitmap() ); + BitmapReadAccess* pRAcc = aBmp.AcquireReadAccess(); + + if( pRAcc ) + { + AlphaMask aAlpha; + nWidth = pRAcc->Width(); + nHeight = pRAcc->Height(); + tgadata = new sal_uInt8[nWidth*nHeight*4]; + tgaAlphadata = new sal_uInt8[nWidth*nHeight]; + sal_uInt8* p = tgadata, *pAlpha = tgaAlphadata; + + + if( aBmpEx.IsAlpha() ) + aAlpha = aBmpEx.GetAlpha(); + else if( aBmpEx.IsTransparent() ) + aAlpha = aBmpEx.GetMask(); + else + { + sal_uInt8 cAlphaVal = 0; + aAlpha = AlphaMask( aBmp.GetSizePixel(), &cAlphaVal ); + } + + BitmapReadAccess* pAAcc = aAlpha.AcquireReadAccess(); + + if( pAAcc ) + { + for( sal_uInt32 nY = 0; nY < nHeight; nY++ ) + { + for( sal_uInt32 nX = 0; nX < nWidth; nX++ ) + { + const sal_uInt8 nAlpha = pAAcc->GetPixel( nY, nX ).GetIndex(); + const BitmapColor aPixelColor( pRAcc->GetColor( nY, nX ) ); + + if( nAlpha == 0xff ) + { + *p++ = 0; + *p++ = 0; + *p++ = 0; + *p++ = 0; + } + else + { + *p++ = 0xff-nAlpha; + *p++ = aPixelColor.GetRed(); + *p++ = aPixelColor.GetGreen(); + *p++ = aPixelColor.GetBlue(); + } + *pAlpha++ = 0xff - nAlpha; + } + } + + aAlpha.ReleaseAccess( pAAcc ); + } + + aBmp.ReleaseAccess( pRAcc ); + } + } +} + +// ----------------------------------------------------------------------------- +sal_uInt16 Writer::defineBitmap( const BitmapEx &bmpSource, sal_Int32 nJPEGQualityLevel ) +{ + ULONG bmpChecksum = bmpSource.GetChecksum(); + + ChecksumCache::iterator it = mBitmapCache.find(bmpChecksum); + + // AS: We already exported this bitmap, so just return its ID. + if (mBitmapCache.end() != it) + return it->second; + + sal_uInt16 nBitmapId = createID(); + mBitmapCache[bmpChecksum] = nBitmapId; + + // AS: OK, we have a good image, so now we decide whether or not to JPEG it or + // or Lossless compress it. + + //Figure out lossless size + sal_uInt8 *pImageData, *pAlphaData; + sal_uInt32 width, height; + + getBitmapData( bmpSource, pImageData, pAlphaData, width, height ); + sal_uInt32 raw_size = width * height * 4; + uLongf compressed_size = raw_size + (sal_uInt32)(raw_size/100) + 12; + sal_uInt8 *pCompressed = new sal_uInt8[ compressed_size ]; + +#ifdef DBG_UTIL + if(compress2(pCompressed, &compressed_size, pImageData, raw_size, Z_BEST_COMPRESSION) != Z_OK) + { + DBG_ASSERT( false, "compress2 failed!" ); ((void)0); + } +#else + compress2(pCompressed, &compressed_size, pImageData, raw_size, Z_BEST_COMPRESSION); +#endif + + // AS: SWF files let you provide an Alpha mask for JPEG images, but we have + // to ZLIB compress the alpha channel seperately. + uLong alpha_compressed_size = 0; + sal_uInt8 *pAlphaCompressed = NULL; + if (bmpSource.IsAlpha() || bmpSource.IsTransparent()) + { + alpha_compressed_size = uLongf(width * height + (sal_uInt32)(raw_size/100) + 12); + pAlphaCompressed = new sal_uInt8[ compressed_size ]; + +#ifdef DBG_UTIL + if(compress2(pAlphaCompressed, &alpha_compressed_size, pAlphaData, width * height, Z_BEST_COMPRESSION) != Z_OK) + { + DBG_ASSERT( false, "compress2 failed!" ); ((void)0); + } +#else + compress2(pAlphaCompressed, &alpha_compressed_size, pAlphaData, width * height, Z_BEST_COMPRESSION); +#endif + } + + //Figure out JPEG size + const sal_uInt8* pJpgData = NULL;; + sal_uInt32 nJpgDataLength = 0xffffffff; + + Graphic aGraphic( bmpSource ); + SvMemoryStream aDstStm( 65535, 65535 ); + + GraphicFilter aFilter; + + Sequence< PropertyValue > aFilterData(nJPEGQualityLevel != -1); + if( nJPEGQualityLevel != -1 ) + { + aFilterData[0].Name = OUString( RTL_CONSTASCII_USTRINGPARAM("Quality")); + aFilterData[0].Value <<= nJPEGQualityLevel; + } + +#if 0 + // Debug code to see what we export to swf + { + SvFileStream aDstStm( String( RTL_CONSTASCII_USTRINGPARAM("e:\\test.jpg") ), STREAM_READ | STREAM_WRITE | STREAM_TRUNC ); + aFilter.ExportGraphic( aGraphic, String(), aDstStm, + aFilter.GetExportFormatNumberForShortName( OUString( RTL_CONSTASCII_USTRINGPARAM( JPG_SHORTNAME ) ) ), &aFilterData ); + } +#endif + + if( aFilter.ExportGraphic( aGraphic, String(), aDstStm, + aFilter.GetExportFormatNumberForShortName( OUString( RTL_CONSTASCII_USTRINGPARAM( JPG_SHORTNAME ) ) ), &aFilterData ) == ERRCODE_NONE ) + { + pJpgData = reinterpret_cast<const sal_uInt8*>(aDstStm.GetData()); + nJpgDataLength = aDstStm.Seek( STREAM_SEEK_TO_END ); + } + + // AS: Ok, now go ahead and use whichever is smaller. If JPEG is smaller, then + // we have to export as TAG_DEFINEBITSJPEG3 in the case that there is alpha + // channel data. + if ( pJpgData && ( nJpgDataLength + alpha_compressed_size < compressed_size) ) + Impl_writeJPEG(nBitmapId, pJpgData, nJpgDataLength, pAlphaCompressed, alpha_compressed_size ); + else + Impl_writeBmp( nBitmapId, width, height, pCompressed, compressed_size ); + + delete[] pCompressed; + delete[] pAlphaCompressed; + delete[] pImageData; + delete[] pAlphaData; + + return nBitmapId; +} + +// ----------------------------------------------------------------------------- + +void Writer::Impl_writeImage( const BitmapEx& rBmpEx, const Point& rPt, const Size& rSz, const Point& /* rSrcPt */, const Size& /* rSrcSz */, const Rectangle& rClipRect, bool bNeedToMapClipRect ) +{ + if( !!rBmpEx ) + { + BitmapEx bmpSource( rBmpEx ); + + Rectangle originalPixelRect = Rectangle(Point(), bmpSource.GetSizePixel()); + + Point srcPt( map(rPt) ); + Size srcSize( map(rSz) ); + Rectangle destRect( srcPt, srcSize ); + + // AS: Christian, my scaling factors are different than yours, and work better for me. + // However, I can't explain why exactly. I got some of this by trial and error. + double XScale = static_cast<double>(originalPixelRect.GetWidth())/destRect.GetWidth(); + double YScale = static_cast<double>(originalPixelRect.GetHeight())/destRect.GetHeight(); + + // AS: If rClipRect has a value set, then we need to crop the bmp appropriately. + // If a map event already occurred in the metafile, then we do not need to map + // the clip rect as it's already been done. + if (!rClipRect.IsEmpty()) + { + // AS: Christian, I also don't understand why bNeedToMapClipRect is necessary, but it + // works like a charm. Usually, the map event in the meta file does not cause the + // clipping rectangle to get mapped. However, sometimes there are multiple layers + // of mapping which eventually do cause the clipping rect to be mapped. + Size clipSize( bNeedToMapClipRect ? map(rClipRect.GetSize()) : rClipRect.GetSize() ); + Rectangle clipRect = Rectangle(Point(), clipSize); + destRect.Intersection( clipRect ); + + Rectangle cropRect(destRect); + + // AS: The bmp origion is always 0,0 so we have to adjust before we crop. + cropRect.Move(-srcPt.X(), -srcPt.Y()); + // AS: Rectangle has no scale function (?!) so I do it manually... + Rectangle cropPixelRect(static_cast<long>(cropRect.Left()*XScale), + static_cast<long>(cropRect.Top()*YScale), + static_cast<long>(cropRect.Right()*XScale), + static_cast<long>(cropRect.Bottom()*YScale)); + + bmpSource.Crop(cropPixelRect); + } + + if( !!bmpSource ) + { + // #105949# fix images that are under 16 pixels width or height by + // expanding them. Some swf players can't display such small + // bitmaps + const Size& rSizePixel = bmpSource.GetSizePixel(); + if( (rSizePixel.Width() < 16) || (rSizePixel.Height() < 16) ) + { + const sal_uInt32 nDX = rSizePixel.Width() < 16 ? 16 - rSizePixel.Width() : 0; + const sal_uInt32 nDY = rSizePixel.Height() < 16 ? 16 - rSizePixel.Height() : 0; + bmpSource.Expand( nDX, nDY ); + } + + sal_Int32 nJPEGQuality = mnJPEGCompressMode; + + Size szDestPixel = mpVDev->LogicToPixel(srcSize, aTWIPSMode); + + double pixXScale = static_cast<double>(szDestPixel.Width()) / originalPixelRect.GetWidth(); + double pixYScale = static_cast<double>(szDestPixel.Height()) / originalPixelRect.GetHeight(); + + // AS: If the image has been scaled down, then scale down the quality + // that we use for JPEG compression. + if (pixXScale < 1.0 && pixYScale < 1.0) + { + + double qualityScale = (pixXScale + pixYScale)/2; + + nJPEGQuality = (sal_Int32)( nJPEGQuality * qualityScale ); + + if (nJPEGQuality < 10) + nJPEGQuality += 3; + } + + sal_uInt16 nBitmapId = defineBitmap(bmpSource, nJPEGQuality); + + Polygon aPoly( destRect ); + + // AS: Since images are being cropped now, no translation is normally necessary. + // However, some things like graphical bullet points are still get translated. + ::basegfx::B2DHomMatrix m; // #i73264# + m.scale(1.0/XScale, 1.0/YScale ); + if (destRect.Left() || destRect.Top()) + m.translate(destRect.Left(), destRect.Top()); + + FillStyle aFillStyle( nBitmapId, true, m ); + + sal_uInt16 nShapeId = defineShape( aPoly, aFillStyle ); + + maShapeIds.push_back( nShapeId ); + } + } +} +// ----------------------------------------------------------------------------- + +void Writer::Impl_writeBmp( sal_uInt16 nBitmapId, sal_uInt32 width, sal_uInt32 height, sal_uInt8 *pCompressed, sal_uInt32 compressed_size ) +{ + startTag( TAG_DEFINEBITSLOSSLESS2 ); + + mpTag->addUI16( nBitmapId ); + mpTag->addUI8( 5 ); + mpTag->addUI16( _uInt16(width) ); + mpTag->addUI16( _uInt16(height) ); + + mpTag->Write( pCompressed, compressed_size ); + + endTag(); +} + +// ----------------------------------------------------------------------------- + +void Writer::Impl_writeJPEG(sal_uInt16 nBitmapId, const sal_uInt8* pJpgData, sal_uInt32 nJpgDataLength, sal_uInt8 *pAlphaCompressed, sal_uInt32 alpha_compressed_size ) +{ + // AS: Go through the actuall JPEG bits, seperating out the + // header fields from the actual image fields. Fields are + // identifed by 0xFFXX where XX is the field type. Both + // the header and the image need start and stop (D8 and D9), + // so that's why you see those written to both. I don't + // really know what the rest of these are, I got it to work + // kind of by trial and error and by comparing with known + // good SWF files. + sal_uInt8 cType = 0x01; + const sal_uInt8* pJpgSearch = pJpgData; + + int nLength = 0; + + SvMemoryStream EncodingTableStream; + SvMemoryStream ImageBitsStream; + for (;pJpgSearch < pJpgData + nJpgDataLength; pJpgSearch += nLength) + { + +#ifdef DBG_UTIL + if (0xFF != *pJpgSearch) + { + DBG_ERROR( "Expected JPEG marker." ); ((void)0); + } +#endif + + cType = *(pJpgSearch + 1); + + if (0xD8 == cType || 0xD9 == cType) + { + nLength = 2; + } + else if (0xDA == cType) + { + //AS: This is the actual image data, and runs to the + // end of the file (as best I know), minus 2 bytes + // for the closing 0xFFD9. + nLength = nJpgDataLength - (pJpgSearch - pJpgData) - 2; + } + else + { + // AS: Lengths are big endian. + + // Beware. pJpgSearch is not necessarily word-aligned, + // so we access it byte-wise. + + // AS: Add 2 to the length to include the 0xFFXX itself. + nLength = 2 + (pJpgSearch[2]<<8) + pJpgSearch[3]; + } + + // AS: I'm refering to libjpeg for a list of what these + // markers are. See jdmarker.c for a list. + // AS: I'm ignoring application specific markers 0xE1...0xEF + // and comments 0xFE. I don't know what + // 0xF0 or 0xFD are for, and they don't come up. + // Additionally, 0xDE and 0xDF aren't clear to me. + switch(cType) + { + case 0xD8: + case 0xD9: + EncodingTableStream.Write( pJpgSearch, nLength ); + ImageBitsStream.Write( pJpgSearch, nLength ); + break; + + case 0x01: + case 0xDB: + case 0xDC: + case 0xDD: + case 0xC4: + EncodingTableStream.Write( pJpgSearch, nLength ); + break; + + case 0xC0: + case 0xC1: + case 0xC2: + case 0xC3: + case 0xC5: + case 0xC6: + case 0xC7: +// case 0xC8: Apparently reserved for JPEG extensions? + case 0xC9: + case 0xCA: + case 0xCB: + case 0xCD: + case 0xCE: + case 0xCF: + case 0xDA: + case 0xE0: + ImageBitsStream.Write( pJpgSearch, nLength ); + break; + + default: + DBG_ERROR( "JPEG marker I didn't handle!" ); + + } + } + + EncodingTableStream.Seek( STREAM_SEEK_TO_END ); + sal_uInt32 nEncodingTableSize = EncodingTableStream.Tell(); + EncodingTableStream.Seek( STREAM_SEEK_TO_BEGIN ); + + ImageBitsStream.Seek( STREAM_SEEK_TO_END ); + sal_uInt32 nImageBitsSize = ImageBitsStream.Tell(); + ImageBitsStream.Seek( STREAM_SEEK_TO_BEGIN ); + + // AS: If we need alpha support, use TAG_DEFINEBITSJPEG3. + if (alpha_compressed_size > 0) + { + startTag( TAG_DEFINEBITSJPEG3 ); + + mpTag->addUI16( nBitmapId ); + + mpTag->addUI32( nEncodingTableSize + nImageBitsSize ); + + mpTag->Write(EncodingTableStream.GetData(), nEncodingTableSize); + mpTag->Write(ImageBitsStream.GetData(), nImageBitsSize); + + mpTag->Write( pAlphaCompressed, alpha_compressed_size ); + + endTag(); + } + else + { + startTag( TAG_DEFINEBITSJPEG2 ); + + mpTag->addUI16( nBitmapId ); + + mpTag->Write(EncodingTableStream.GetData(), nEncodingTableSize); + mpTag->Write(ImageBitsStream.GetData(), nImageBitsSize); + + endTag(); + } +} + +// ----------------------------------------------------------------------------- + +void Writer::Impl_writeLine( const Point& rPt1, const Point& rPt2, const Color* pLineColor ) +{ + Color aOldColor( mpVDev->GetLineColor() ); + if( pLineColor ) + mpVDev->SetLineColor( *pLineColor ); + + const Point aPtAry[2] = { rPt1, rPt2 }; + Polygon aPoly( 2, aPtAry ); + Impl_writePolyPolygon( aPoly, false ); + + mpVDev->SetLineColor( aOldColor ); +} + +// ----------------------------------------------------------------------------- + +void Writer::Impl_writeRect( const Rectangle& rRect, long nRadX, long nRadY ) +{ + if( (rRect.nTop == rRect.nBottom) || (rRect.nLeft == rRect.nRight) ) + { + Color aColor( mpVDev->GetFillColor() ); + Impl_writeLine( rRect.TopLeft(), rRect.BottomRight(), &aColor ); + } + else + { + Polygon aPoly( rRect, nRadX, nRadY ); + Impl_writePolyPolygon( aPoly, true ); + } +} + +// ----------------------------------------------------------------------------- + +void Writer::Impl_writeEllipse( const Point& rCenter, long nRadX, long nRadY ) +{ + Polygon aPoly( rCenter, nRadX, nRadY ); + Impl_writePolyPolygon( aPoly, false ); +} + + +/** writes the stroke defined by SvtGraphicStroke and returns true or it returns + false if it can't handle this stroke. +*/ +bool Writer::Impl_writeStroke( SvtGraphicStroke& rStroke ) +{ + Polygon aPolygon; + rStroke.getPath( aPolygon ); + PolyPolygon aPolyPolygon( aPolygon ); + + Rectangle aOldRect( aPolyPolygon.GetBoundRect() ); + + map( aPolyPolygon ); + + Rectangle aNewRect( aPolyPolygon.GetBoundRect() ); + + // as log as not LINESTYLE2 and DefineShape4 is used (which + // added support for LineJoin), only round LineJoins are + // supported. Fallback to META_POLYLINE_ACTION and META_LINE_ACTION + if(SvtGraphicStroke::joinRound != rStroke.getJoinType()) + return false; + + PolyPolygon aStartArrow; + rStroke.getStartArrow( aStartArrow ); + if( 0 != aStartArrow.Count() ) + return false; // todo: Implement line ends + + PolyPolygon aEndArrow; + rStroke.getEndArrow( aEndArrow ); + if( 0 != aEndArrow.Count() ) + return false; // todo: Implement line ends + + SvtGraphicStroke::DashArray aDashArray; + rStroke.getDashArray( aDashArray ); + if( 0 != aDashArray.size() ) + return false; // todo: implement dashes + + Color aColor( mpVDev->GetLineColor() ); + + if( 0.0 != rStroke.getTransparency() ) + aColor.SetTransparency( sal::static_int_cast<UINT8>( MinMax( (long int)( rStroke.getTransparency() * 0xff ), 0, 0xff ) ) ); + + sal_uInt16 nShapeId = defineShape( aPolyPolygon, sal::static_int_cast<sal_uInt16>( mapRelative( (sal_Int32)( rStroke.getStrokeWidth() ) ) ), aColor ); + maShapeIds.push_back( nShapeId ); + return true; +} + +// ----------------------------------------------------------------------------- + +/** writes the filling defined by SvtGraphicFill and returns true or it returns + false if it can't handle this filling. +*/ +bool Writer::Impl_writeFilling( SvtGraphicFill& rFilling ) +{ + PolyPolygon aPolyPolygon; + rFilling.getPath( aPolyPolygon ); + + Rectangle aOldRect( aPolyPolygon.GetBoundRect() ); + + map( aPolyPolygon ); + + Rectangle aNewRect( aPolyPolygon.GetBoundRect() ); + + switch( rFilling.getFillType() ) + { + case SvtGraphicFill::fillSolid: + { + Color aColor( rFilling.getFillColor() ); + + if( 0.0 != rFilling.getTransparency() ) + aColor.SetTransparency( sal::static_int_cast<UINT8>( MinMax( (long int)( rFilling.getTransparency() * 0xff ) , 0, 0xff ) ) ); + + FillStyle aFillStyle( aColor ); + + sal_uInt16 nShapeId = defineShape( aPolyPolygon, aFillStyle ); + maShapeIds.push_back( nShapeId ); + } + break; + case SvtGraphicFill::fillGradient: + return false; + case SvtGraphicFill::fillHatch: + return false; + case SvtGraphicFill::fillTexture: + { + Graphic aGraphic; + rFilling.getGraphic( aGraphic ); + + // CL->AS: Should we also scale down the quality here depending on image scale? + sal_uInt16 nBitmapId = defineBitmap( aGraphic.GetBitmapEx(), mnJPEGCompressMode ); + + ::basegfx::B2DHomMatrix aMatrix; // #i73264# + + SvtGraphicFill::Transform aTransform; + + rFilling.getTransform( aTransform ); + + sal_uInt16 a,b; + for( a = 0; a < 2; a++ ) + { + for( b = 0; b < 3; b++ ) + { + aMatrix.set(a, b, aTransform.matrix[a*3+b]); + } + } + aMatrix.set(2, 0, 0.0); + aMatrix.set(2, 1, 0.0); + aMatrix.set(2, 2, 1.0); + + // scale bitmap + Rectangle originalPixelRect = Rectangle(Point(), aGraphic.GetBitmapEx().GetSizePixel()); + + double XScale = (double)aNewRect.GetWidth()/aOldRect.GetWidth(); + double YScale = (double)aNewRect.GetHeight()/aOldRect.GetHeight(); + + aMatrix.scale( XScale, YScale ); + + FillStyle aFillStyle( nBitmapId, !rFilling.IsTiling(), aMatrix ); + + sal_uInt16 nShapeId = defineShape( aPolyPolygon, aFillStyle ); + maShapeIds.push_back( nShapeId ); + } + break; + } + return true; +} + +// ----------------------------------------------------------------------------- + +/* CL: The idea was to export page fields as text fields that get theire + string from a variable set with actionscript by each page. This didn't + work out since the formating is always wrong when text follows the + page number field since pages greater one may require more space than + page 1 +*/ +#if 0 +bool Writer::Impl_writePageField( Rectangle& rTextBounds ) +{ + startTag( TAG_DEFINEEDITTEXT ); + + sal_uInt16 nTextId = createID(); + + mpTag->addUI16( nTextId ); + mpTag->addRect( rTextBounds ); + + BitStream aBits; + aBits.writeUB( 1, 1 ); // HasText + aBits.writeUB( 0, 1 ); // WordWrap + aBits.writeUB( 0, 1 ); // MultiLine + aBits.writeUB( 0, 1 ); // Password + aBits.writeUB( 1, 1 ); // HasTextColor + aBits.writeUB( 0, 1 ); // HasMaxLength + aBits.writeUB( 0, 1 ); // HasFont + aBits.writeUB( 0, 1 ); // Reserved + aBits.writeUB( 0, 1 ); // AutoSize + aBits.writeUB( 0, 1 ); // HasLayout + aBits.writeUB( 1, 1 ); // NoSelect + aBits.writeUB( 1, 1 ); // Border + aBits.writeUB( 0, 1 ); // Reserved + aBits.writeUB( 0, 1 ); // HTML + aBits.writeUB( 0, 1 ); // UseOutlines + mpTag->addBits( aBits ); + + Color aColor( COL_BLACK ); + mpTag->addRGB( aColor ); + mpTag->addString( "PageNumber" ); + mpTag->addString( "XXX" ); + + endTag(); + + maShapeIds.push_back( nTextId ); + + return true; +} +#endif + +// ----------------------------------------------------------------------------- + +void Writer::Impl_handleLineInfoPolyPolygons(const LineInfo& rInfo, const basegfx::B2DPolygon& rLinePolygon) +{ + if(rLinePolygon.count()) + { + basegfx::B2DPolyPolygon aLinePolyPolygon(rLinePolygon); + basegfx::B2DPolyPolygon aFillPolyPolygon; + + rInfo.applyToB2DPolyPolygon(aLinePolyPolygon, aFillPolyPolygon); + + if(aLinePolyPolygon.count()) + { + for(sal_uInt32 a(0); a < aLinePolyPolygon.count(); a++) + { + const basegfx::B2DPolygon aCandidate(aLinePolyPolygon.getB2DPolygon(a)); + Impl_writePolygon(Polygon(aCandidate), sal_False ); + } + } + + if(aFillPolyPolygon.count()) + { + const Color aOldLineColor(mpVDev->GetLineColor()); + const Color aOldFillColor(mpVDev->GetFillColor()); + + mpVDev->SetLineColor(); + mpVDev->SetFillColor(aOldLineColor); + + for(sal_uInt32 a(0); a < aFillPolyPolygon.count(); a++) + { + const Polygon aPolygon(aFillPolyPolygon.getB2DPolygon(a)); + Impl_writePolyPolygon(PolyPolygon(Polygon(aPolygon)), sal_True ); + } + + mpVDev->SetLineColor(aOldLineColor); + mpVDev->SetFillColor(aOldFillColor); + } + } +} + +// ----------------------------------------------------------------------------- + +void Writer::Impl_writeActions( const GDIMetaFile& rMtf ) +{ + Rectangle clipRect; + int bMap = 0; + for( ULONG i = 0, nCount = rMtf.GetActionCount(); i < nCount; i++ ) + { + const MetaAction* pAction = rMtf.GetAction( i ); + const USHORT nType = pAction->GetType(); + + switch( nType ) + { + case( META_PIXEL_ACTION ): + { + const MetaPixelAction* pA = (const MetaPixelAction*) pAction; + + Impl_writeLine( pA->GetPoint(), pA->GetPoint(), &pA->GetColor() ); + } + break; + + case( META_POINT_ACTION ): + { + const MetaPointAction* pA = (const MetaPointAction*) pAction; + + Impl_writeLine( pA->GetPoint(), pA->GetPoint() ); + } + break; + + case( META_LINE_ACTION ): + { + const MetaLineAction* pA = (const MetaLineAction*) pAction; + + if(pA->GetLineInfo().IsDefault()) + { + Impl_writeLine( pA->GetStartPoint(), pA->GetEndPoint() ); + } + else + { + // LineInfo used; handle Dash/Dot and fat lines + basegfx::B2DPolygon aPolygon; + aPolygon.append(basegfx::B2DPoint(pA->GetStartPoint().X(), pA->GetStartPoint().Y())); + aPolygon.append(basegfx::B2DPoint(pA->GetEndPoint().X(), pA->GetEndPoint().Y())); + Impl_handleLineInfoPolyPolygons(pA->GetLineInfo(), aPolygon); + } + } + break; + + case( META_RECT_ACTION ): + { + Impl_writeRect( ( (const MetaRectAction*) pAction )->GetRect(), 0, 0 ); + } + break; + + case( META_ROUNDRECT_ACTION ): + { + const MetaRoundRectAction* pA = (const MetaRoundRectAction*) pAction; + + Impl_writeRect( pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound() ); + } + break; + + case( META_ELLIPSE_ACTION ): + { + const MetaEllipseAction* pA = (const MetaEllipseAction*) pAction; + const Rectangle& rRect = pA->GetRect(); + + Impl_writeEllipse( rRect.Center(), rRect.GetWidth() >> 1, rRect.GetHeight() >> 1 ); + } + break; + + case( META_ARC_ACTION ): + case( META_PIE_ACTION ): + case( META_CHORD_ACTION ): + case( META_POLYGON_ACTION ): + { + Polygon aPoly; + + switch( nType ) + { + case( META_ARC_ACTION ): + { + const MetaArcAction* pA = (const MetaArcAction*) pAction; + aPoly = Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), POLY_ARC ); + } + break; + + case( META_PIE_ACTION ): + { + const MetaPieAction* pA = (const MetaPieAction*) pAction; + aPoly = Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), POLY_PIE ); + } + break; + + case( META_CHORD_ACTION ): + { + const MetaChordAction* pA = (const MetaChordAction*) pAction; + aPoly = Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), POLY_CHORD ); + } + break; + + case( META_POLYGON_ACTION ): + aPoly = ( (const MetaPolygonAction*) pAction )->GetPolygon(); + break; + } + + if( aPoly.GetSize() ) + { + Impl_writePolygon( aPoly, sal_True ); + } + } + break; + + case( META_POLYLINE_ACTION ): + { + const MetaPolyLineAction* pA = (const MetaPolyLineAction*) pAction; + const Polygon& rPoly = pA->GetPolygon(); + + if( rPoly.GetSize() ) + { + if(pA->GetLineInfo().IsDefault()) + { + Impl_writePolygon( rPoly, sal_False ); + } + else + { + // LineInfo used; handle Dash/Dot and fat lines + Impl_handleLineInfoPolyPolygons(pA->GetLineInfo(), rPoly.getB2DPolygon()); + } + } + } + break; + + case( META_POLYPOLYGON_ACTION ): + { + const MetaPolyPolygonAction* pA = (const MetaPolyPolygonAction*) pAction; + const PolyPolygon& rPolyPoly = pA->GetPolyPolygon(); + + if( rPolyPoly.Count() ) + Impl_writePolyPolygon( rPolyPoly, sal_True ); + } + break; + + case( META_GRADIENT_ACTION ): + { + const MetaGradientAction* pA = (const MetaGradientAction*) pAction; + + Polygon aPoly( pA->GetRect() ); + Impl_writeGradientEx( aPoly, pA->GetGradient() ); + } + break; + + case( META_GRADIENTEX_ACTION ): + { + const MetaGradientExAction* pA = (const MetaGradientExAction*) pAction; + Impl_writeGradientEx( pA->GetPolyPolygon(), pA->GetGradient() ); + } + break; + + case META_HATCH_ACTION: + { + const MetaHatchAction* pA = (const MetaHatchAction*) pAction; + GDIMetaFile aTmpMtf; + + mpVDev->AddHatchActions( pA->GetPolyPolygon(), pA->GetHatch(), aTmpMtf ); + Impl_writeActions( aTmpMtf ); + } + break; + + case( META_TRANSPARENT_ACTION ): + { + const MetaTransparentAction* pA = (const MetaTransparentAction*) pAction; + const PolyPolygon& rPolyPoly = pA->GetPolyPolygon(); + + if( rPolyPoly.Count() ) + { + // convert transparence from percent into 0x00 - 0xff + sal_uInt8 nTransparence = (sal_uInt8) MinMax( FRound( pA->GetTransparence() * 2.55 ), 0, 255 ); + Impl_writePolyPolygon( rPolyPoly, sal_True, nTransparence ); + } + } + break; + + case( META_FLOATTRANSPARENT_ACTION ): + { + const MetaFloatTransparentAction* pA = (const MetaFloatTransparentAction*) pAction; + GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() ); + Point aSrcPt( aTmpMtf.GetPrefMapMode().GetOrigin() ); + const Size aSrcSize( aTmpMtf.GetPrefSize() ); + const Point aDestPt( pA->GetPoint() ); + const Size aDestSize( pA->GetSize() ); + const double fScaleX = aSrcSize.Width() ? (double) aDestSize.Width() / aSrcSize.Width() : 1.0; + const double fScaleY = aSrcSize.Height() ? (double) aDestSize.Height() / aSrcSize.Height() : 1.0; + long nMoveX, nMoveY; + + if( fScaleX != 1.0 || fScaleY != 1.0 ) + { + aTmpMtf.Scale( fScaleX, fScaleY ); + aSrcPt.X() = FRound( aSrcPt.X() * fScaleX ); + aSrcPt.Y() = FRound( aSrcPt.Y() * fScaleY ); + } + + nMoveX = aDestPt.X() - aSrcPt.X(), nMoveY = aDestPt.Y() - aSrcPt.Y(); + + if( nMoveX || nMoveY ) + aTmpMtf.Move( nMoveX, nMoveY ); + + const Gradient& rGradient = pA->GetGradient(); + sal_uInt32 nLuminance = ((sal_Int32)rGradient.GetStartColor().GetLuminance() + (sal_Int32)rGradient.GetEndColor().GetLuminance() ) >> 1; + + sal_uInt8 nOldGlobalTransparency = mnGlobalTransparency; + mnGlobalTransparency = (sal_uInt8)MinMax( nLuminance, 0, 0xff ); + + mpVDev->Push(); + Impl_writeActions( aTmpMtf ); + mpVDev->Pop(); + + mnGlobalTransparency = nOldGlobalTransparency; + } + break; + + case( META_EPS_ACTION ): + { + const MetaEPSAction* pA = (const MetaEPSAction*) pAction; + const GDIMetaFile aGDIMetaFile( pA->GetSubstitute() ); + sal_Bool bFound = sal_False; + + for( ULONG j = 0, nC = aGDIMetaFile.GetActionCount(); ( j < nC ) && !bFound; j++ ) + { + const MetaAction* pSubstAct = aGDIMetaFile.GetAction( j ); + + if( pSubstAct->GetType() == META_BMPSCALE_ACTION ) + { + bFound = sal_True; + const MetaBmpScaleAction* pBmpScaleAction = (const MetaBmpScaleAction*) pSubstAct; + Impl_writeImage( pBmpScaleAction->GetBitmap(), + pA->GetPoint(), pA->GetSize(), + Point(), pBmpScaleAction->GetBitmap().GetSizePixel(), clipRect, 1 == bMap ); + } + } + } + break; + + case( META_COMMENT_ACTION ): + { + const MetaCommentAction* pA = (const MetaCommentAction*) pAction; + const BYTE* pData = pA->GetData(); + String aSkipComment; + + if( pA->GetComment().CompareIgnoreCaseToAscii( "XGRAD_SEQ_BEGIN" ) == COMPARE_EQUAL ) + { + const MetaGradientExAction* pGradAction = NULL; + sal_Bool bDone = sal_False; + + while( !bDone && ( ++i < nCount ) ) + { + pAction = rMtf.GetAction( i ); + + if( pAction->GetType() == META_GRADIENTEX_ACTION ) + pGradAction = (const MetaGradientExAction*) pAction; + else if( ( pAction->GetType() == META_COMMENT_ACTION ) && + ( ( (const MetaCommentAction*) pAction )->GetComment().CompareIgnoreCaseToAscii( "XGRAD_SEQ_END" ) == COMPARE_EQUAL ) ) + { + bDone = sal_True; + } + } + + if( pGradAction ) + Impl_writeGradientEx( pGradAction->GetPolyPolygon(), pGradAction->GetGradient()); + } + else if( pA->GetComment().CompareIgnoreCaseToAscii( "XPATHFILL_SEQ_BEGIN" ) == COMPARE_EQUAL && + pData ) + { + + // this comment encapsulates all high level information for a filling that caused + // the meta actions between the "XPATHFILL_SEQ_BEGIN" and "XPATHFILL_SEQ_END" comment. + + SvtGraphicFill aFilling; + SvMemoryStream aMemStm( (void*)pData, pA->GetDataSize(), STREAM_READ ); + + // read the fill info + aMemStm >> aFilling; + + // if impl_writeFilling can handle this high level filling, it returns true and we + // skip all meta actions until "XPATHFILL_SEQ_END" + if( Impl_writeFilling( aFilling ) ) + { + bool bDone = sal_False; + + while( !bDone && ( ++i < nCount ) ) + { + pAction = rMtf.GetAction( i ); + + if( ( pAction->GetType() == META_COMMENT_ACTION ) && + ( ( (const MetaCommentAction*) pAction )->GetComment().CompareIgnoreCaseToAscii( "XPATHFILL_SEQ_END" ) == COMPARE_EQUAL ) ) + { + bDone = sal_True; + } + } + } + } + else if( pA->GetComment().CompareIgnoreCaseToAscii( "XPATHSTROKE_SEQ_BEGIN" ) == COMPARE_EQUAL && + pData ) + { + + // this comment encapsulates all high level information for a filling that caused + // the meta actions between the "XPATHFILL_SEQ_BEGIN" and "XPATHFILL_SEQ_END" comment. + + SvtGraphicStroke aStroke; + SvMemoryStream aMemStm( (void*)pData, pA->GetDataSize(), STREAM_READ ); + + // read the fill info + aMemStm >> aStroke; + + // if impl_writeStroke can handle this high level stroke, it returns true and we + // skip all meta actions until "XPATHSTROKE_SEQ_END" + if( Impl_writeStroke( aStroke ) ) + { + bool bDone = sal_False; + + while( !bDone && ( ++i < nCount ) ) + { + pAction = rMtf.GetAction( i ); + + if( ( pAction->GetType() == META_COMMENT_ACTION ) && + ( ( (const MetaCommentAction*) pAction )->GetComment().CompareIgnoreCaseToAscii( "XPATHSTROKE_SEQ_END" ) == COMPARE_EQUAL ) ) + { + bDone = sal_True; + } + } + } + } +#if 0 + else if( pA->GetComment().CompareIgnoreCaseToAscii( "FIELD_SEQ_BEGIN;PageField" ) == COMPARE_EQUAL ) + { + bool bDone = sal_False; + + while( !bDone && ( ++i < nCount ) ) + { + pAction = rMtf.GetAction( i ); + + if( pAction->GetType() == META_TEXTARRAY_ACTION ) + { + const MetaTextArrayAction* pA = (const MetaTextArrayAction*) pAction; + Rectangle aRect( pA->GetPoint(), Size( 100, 100 ) ); + Impl_writePageField( aRect ); + } + + if( ( pAction->GetType() == META_COMMENT_ACTION ) && + ( ( (const MetaCommentAction*) pAction )->GetComment().CompareIgnoreCaseToAscii( "FIELD_SEQ_END" ) == COMPARE_EQUAL ) ) + { + bDone = sal_True; + } + } + } +#endif + } + break; + + case( META_BMPSCALE_ACTION ): + { + const MetaBmpScaleAction* pA = (const MetaBmpScaleAction*) pAction; + + Impl_writeImage( pA->GetBitmap(), + pA->GetPoint(), pA->GetSize(), + Point(), pA->GetBitmap().GetSizePixel(), clipRect, 1 == bMap ); + } + break; + + case( META_BMP_ACTION ): + { + const MetaBmpAction* pA = (const MetaBmpAction*) pAction; + Impl_writeImage( pA->GetBitmap(), + pA->GetPoint(), mpVDev->PixelToLogic( pA->GetBitmap().GetSizePixel()), + Point(), pA->GetBitmap().GetSizePixel(), clipRect, 1 ==bMap ); + } + break; + + case( META_BMPSCALEPART_ACTION ): + { + const MetaBmpScalePartAction* pA = (const MetaBmpScalePartAction*) pAction; + Impl_writeImage( pA->GetBitmap(), + pA->GetDestPoint(), pA->GetDestSize(), + pA->GetSrcPoint(), pA->GetSrcSize(), clipRect, 1 == bMap ); + } + break; + + case( META_BMPEX_ACTION ): + { + const MetaBmpExAction* pA = (const MetaBmpExAction*) pAction; + Impl_writeImage( pA->GetBitmapEx(), + pA->GetPoint(), mpVDev->PixelToLogic( pA->GetBitmapEx().GetSizePixel() ), + Point(), pA->GetBitmapEx().GetSizePixel(), clipRect, 1 == bMap ); + } + break; + + case( META_BMPEXSCALE_ACTION ): + { + const MetaBmpExScaleAction* pA = (const MetaBmpExScaleAction*) pAction; + Impl_writeImage( pA->GetBitmapEx(), + pA->GetPoint(), pA->GetSize(), + Point(), pA->GetBitmapEx().GetSizePixel(), clipRect, 1 == bMap ); + } + break; + + case( META_BMPEXSCALEPART_ACTION ): + { + const MetaBmpExScalePartAction* pA = (const MetaBmpExScalePartAction*) pAction; + Impl_writeImage( pA->GetBitmapEx(), + pA->GetDestPoint(), pA->GetDestSize(), + pA->GetSrcPoint(), pA->GetSrcSize(), clipRect, 1 == bMap ); + } + break; + + case( META_TEXT_ACTION ): + { + const MetaTextAction* pA = (const MetaTextAction*) pAction; + Impl_writeText( pA->GetPoint(), String( pA->GetText(), pA->GetIndex(), pA->GetLen() ), NULL, 0); + } + break; + + case( META_TEXTRECT_ACTION ): + { + const MetaTextRectAction* pA = (const MetaTextRectAction*) pAction; + Impl_writeText( pA->GetRect().TopLeft(), pA->GetText(), NULL, 0 ); + } + break; + + case( META_TEXTARRAY_ACTION ): + { + const MetaTextArrayAction* pA = (const MetaTextArrayAction*) pAction; + Impl_writeText( pA->GetPoint(), String( pA->GetText(), pA->GetIndex(), pA->GetLen() ), pA->GetDXArray(), 0 ); + } + break; + + case( META_STRETCHTEXT_ACTION ): + { + const MetaStretchTextAction* pA = (const MetaStretchTextAction*) pAction; + Impl_writeText( pA->GetPoint(), String( pA->GetText(), pA->GetIndex(), pA->GetLen() ), NULL, pA->GetWidth() ); + } + break; + + case( META_ISECTRECTCLIPREGION_ACTION ): + { + const MetaISectRectClipRegionAction* pA = (const MetaISectRectClipRegionAction*) pAction; + clipRect = pA->GetRect(); + } + case( META_CLIPREGION_ACTION ): + case( META_ISECTREGIONCLIPREGION_ACTION ): + case( META_MOVECLIPREGION_ACTION ): + { + ( (MetaAction*) pAction )->Execute( mpVDev ); +// mbClipAttrChanged = sal_True; + } + break; + + case( META_MAPMODE_ACTION ): + { +// const MetaMapModeAction *pA = (const MetaMapModeAction*) pAction; +// MapMode mm = pA->GetMapMode(); +// MapUnit mu = mm.GetMapUnit(); +// +// Point pt = mm.GetOrigin(); +// Fraction fx = mm.GetScaleX(); +// Fraction fy = mm.GetScaleY(); + + bMap++; + } + case( META_REFPOINT_ACTION ): + case( META_LINECOLOR_ACTION ): + case( META_FILLCOLOR_ACTION ): + case( META_TEXTLINECOLOR_ACTION ): + case( META_TEXTFILLCOLOR_ACTION ): + case( META_TEXTCOLOR_ACTION ): + case( META_TEXTALIGN_ACTION ): + case( META_FONT_ACTION ): + case( META_PUSH_ACTION ): + case( META_POP_ACTION ): + case( META_LAYOUTMODE_ACTION ): + { + ( (MetaAction*) pAction )->Execute( mpVDev ); + } + break; + + case( META_RASTEROP_ACTION ): + case( META_MASK_ACTION ): + case( META_MASKSCALE_ACTION ): + case( META_MASKSCALEPART_ACTION ): + case( META_WALLPAPER_ACTION ): + case( META_TEXTLINE_ACTION ): + { + // !!! >>> we don't want to support these actions + } + break; + + default: + //DBG_ERROR( "FlashActionWriter::ImplWriteActions: unsupported MetaAction #" ); + break; + } + } +} + + +///////////////////////////////////////////////////////////////////////// + + +void Writer::Impl_addStraightLine( BitStream& rBits, Point& rLastPoint, + const double P2x, const double P2y ) +{ + Point aPoint( FRound(P2x), FRound(P2y) ); + + Impl_addStraightEdgeRecord( rBits, _Int16(aPoint.X() - rLastPoint.X()),_Int16(aPoint.Y() - rLastPoint.Y())); + rLastPoint = aPoint; + +} + +// ----------------------------------------------------------------------------- + +void Writer::Impl_addQuadBezier( BitStream& rBits, Point& rLastPoint, + const double P2x, const double P2y, + const double P3x, const double P3y ) +{ + + Point aControlPoint( FRound(P2x), FRound(P2y) ); + Point aAnchorPoint( FRound(P3x), FRound(P3y) ); + + Impl_addCurvedEdgeRecord( rBits, + _Int16(aControlPoint.X() - rLastPoint.X()),_Int16(aControlPoint.Y() - rLastPoint.Y()), + _Int16(aAnchorPoint.X() - aControlPoint.X()),_Int16(aAnchorPoint.Y() - aControlPoint.Y()) ); + rLastPoint = aAnchorPoint; +} + +// ----------------------------------------------------------------------------- + +/* Approximate given cubic bezier curve by quadratic bezier segments */ +void Writer::Impl_quadBezierApprox( BitStream& rBits, + Point& rLastPoint, + const double d2, + const double P1x, const double P1y, + const double P2x, const double P2y, + const double P3x, const double P3y, + const double P4x, const double P4y ) +{ + // Check for degenerate case, where the given cubic bezier curve + // is already quadratic: P4 == 3P3 - 3P2 + P1 + if( P4x == 3.0*P3x - 3.0*P2x + P1x && + P4y == 3.0*P3y - 3.0*P2y + P1y ) + { + Impl_addQuadBezier( rBits, rLastPoint, + 3.0/2.0*P2x - 1.0/2.0*P1x, 3.0/2.0*P2y - 1.0/2.0*P1y, + P4x, P4y); + } + else + { + // Create quadratic segment for given cubic: + // Start and end point must coincide, determine quadratic control + // point in such a way that it lies on the intersection of the + // tangents at start and end point, resp. Thus, both cubic and + // quadratic curve segments will match in 0th and 1st derivative + // at the start and end points + + // Intersection of P2P1 and P4P3 + // (P2y-P4y)(P3x-P4x)-(P2x-P4x)(P3y-P4y) + // lambda = ------------------------------------- + // (P1x-P2x)(P3y-P4y)-(P1y-P2y)(P3x-P4x) + // + // Intersection point IP is now + // IP = P2 + lambda(P1-P2) + // + const double nominator( (P2y-P4y)*(P3x-P4x) - (P2x-P4x)*(P3y-P4y) ); + const double denominator( (P1x-P2x)*(P3y-P4y) - (P1y-P2y)*(P3x-P4x) ); + const double lambda( nominator / denominator ); + + const double IPx( P2x + lambda*( P1x - P2x) ); + const double IPy( P2y + lambda*( P1y - P2y) ); + + // Introduce some alias names: quadratic start point is P1, end + // point is P4, control point is IP + const double QP1x( P1x ); + const double QP1y( P1y ); + const double QP2x( IPx ); + const double QP2y( IPy ); + const double QP3x( P4x ); + const double QP3y( P4y ); + + // Adapted bezier flatness test (lecture notes from R. Schaback, + // Mathematics of Computer-Aided Design, Uni Goettingen, 2000) + // + // ||C(t) - Q(t)|| <= max ||c_j - q_j|| + // 0<=j<=n + // + // In this case, we don't need the distance from the cubic bezier + // to a straight line, but to a quadratic bezier. The c_j's are + // the cubic bezier's bernstein coefficients, the q_j's the + // quadratic bezier's. We have the c_j's given, the q_j's can be + // calculated from QPi like this (sorry, mixed index notation, we + // use [1,n], formulas use [0,n-1]): + // + // q_0 = QP1 = P1 + // q_1 = 1/3 QP1 + 2/3 QP2 + // q_2 = 2/3 QP2 + 1/3 QP3 + // q_3 = QP3 = P4 + // + // We can drop case 0 and 3, since there the curves coincide + // (distance is zero) + + // calculate argument of max for j=1 and j=2 + const double fJ1x( P2x - 1.0/3.0*QP1x - 2.0/3.0*QP2x ); + const double fJ1y( P2y - 1.0/3.0*QP1y - 2.0/3.0*QP2y ); + const double fJ2x( P3x - 2.0/3.0*QP2x - 1.0/3.0*QP3x ); + const double fJ2y( P3y - 2.0/3.0*QP2y - 1.0/3.0*QP3y ); + + // stop if distance from cubic curve is guaranteed to be bounded by d + // Should denominator be 0: then P1P2 and P3P4 are parallel (P1P2^T R[90,P3P4] = 0.0), + // meaning that either we have a straight line or an inflexion point (see else block below) + if( 0.0 != denominator && + ::std::max( fJ1x*fJ1x + fJ1y*fJ1y, + fJ2x*fJ2x + fJ2y*fJ2y) < d2 ) + { + // requested resolution reached. + // Add end points to output file. + // order is preserved, since this is so to say depth first traversal. + Impl_addQuadBezier( rBits, rLastPoint, + QP2x, QP2y, + QP3x, QP3y); + } + else + { + // Maybe subdivide further + + // This is for robustness reasons, since the line intersection + // method below gets instable if the curve gets closer to a + // straight line. If the given cubic bezier does not deviate by + // more than d/4 from a straight line, either: + // - take the line (that's what we do here) + // - express the line by a quadratic bezier + + // Perform bezier flatness test (lecture notes from R. Schaback, + // Mathematics of Computer-Aided Design, Uni Goettingen, 2000) + // + // ||P(t) - L(t)|| <= max ||b_j - b_0 - j/n(b_n - b_0)|| + // 0<=j<=n + // + // What is calculated here is an upper bound to the distance from + // a line through b_0 and b_3 (P1 and P4 in our notation) and the + // curve. We can drop 0 and n from the running indices, since the + // argument of max becomes zero for those cases. + const double fJ1x2( P2x - P1x - 1.0/3.0*(P4x - P1x) ); + const double fJ1y2( P2y - P1y - 1.0/3.0*(P4y - P1y) ); + const double fJ2x2( P3x - P1x - 2.0/3.0*(P4x - P1x) ); + const double fJ2y2( P3y - P1y - 2.0/3.0*(P4y - P1y) ); + + // stop if distance from line is guaranteed to be bounded by d/4 + if( ::std::max( fJ1x2*fJ1x2 + fJ1y2*fJ1y2, + fJ2x2*fJ2x2 + fJ2y2*fJ2y2) < d2/16.0 ) + { + // do not subdivide further, add straight line instead + Impl_addStraightLine( rBits, rLastPoint, P4x, P4y); + } + else + { + // deCasteljau bezier arc, split at t=0.5 + // Foley/vanDam, p. 508 + const double L1x( P1x ), L1y( P1y ); + const double L2x( (P1x + P2x)*0.5 ), L2y( (P1y + P2y)*0.5 ); + const double Hx ( (P2x + P3x)*0.5 ), Hy ( (P2y + P3y)*0.5 ); + const double L3x( (L2x + Hx)*0.5 ), L3y( (L2y + Hy)*0.5 ); + const double R4x( P4x ), R4y( P4y ); + const double R3x( (P3x + P4x)*0.5 ), R3y( (P3y + P4y)*0.5 ); + const double R2x( (Hx + R3x)*0.5 ), R2y( (Hy + R3y)*0.5 ); + const double R1x( (L3x + R2x)*0.5 ), R1y( (L3y + R2y)*0.5 ); + const double L4x( R1x ), L4y( R1y ); + + // subdivide further + Impl_quadBezierApprox(rBits, rLastPoint, d2, L1x, L1y, L2x, L2y, L3x, L3y, L4x, L4y); + Impl_quadBezierApprox(rBits, rLastPoint, d2, R1x, R1y, R2x, R2y, R3x, R3y, R4x, R4y); + } + } + } +} + +Reference < XBreakIterator > Writer::Impl_GetBreakIterator() +{ + if ( !mxBreakIterator.is() ) + { + Reference< XMultiServiceFactory > xMSF( ::comphelper::getProcessServiceFactory() ); + mxBreakIterator.set( xMSF->createInstance( OUString::createFromAscii( "com.sun.star.i18n.BreakIterator" ) ), UNO_QUERY ); + } + return mxBreakIterator; +} |