blob: d00650cdfd51354d2c5b659609431562d23f61b5 [file] [log] [blame]
/**************************************************************
*
* 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_filter.hxx"
#include "swfwriter.hxx"
#include <vcl/virdev.hxx>
#include <vcl/gdimtf.hxx>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
using namespace ::swf;
using namespace ::std;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::io;
// -----------------------------------------------------------------------------
static MapMode aTWIPSMode( MAP_TWIP );
static MapMode a100thmmMode( MAP_100TH_MM );
static sal_Int32 map100thmm( sal_Int32 n100thMM )
{
Point aPoint( n100thMM, n100thMM );
sal_Int32 nX = OutputDevice::LogicToLogic( aPoint, a100thmmMode, aTWIPSMode ).X();
return nX;
}
// -----------------------------------------------------------------------------
Writer::Writer( sal_Int32 nTWIPWidthOutput, sal_Int32 nTWIPHeightOutput, sal_Int32 nDocWidthInput, sal_Int32 nDocHeightInput, sal_Int32 nJPEGcompressMode )
: mpClipPolyPolygon( NULL ),
mpTag( NULL ),
mpSprite( NULL ),
mnNextId( 1 ),
mnGlobalTransparency(0),
mnJPEGCompressMode(nJPEGcompressMode)
{
mpVDev = new VirtualDevice;
mpVDev->EnableOutput( sal_False );
maMovieTempFile.EnableKillingFile();
maFontsTempFile.EnableKillingFile();
mpMovieStream = maMovieTempFile.GetStream( STREAM_WRITE|STREAM_TRUNC );
mpFontsStream = maFontsTempFile.GetStream( STREAM_WRITE|STREAM_TRUNC );
mnFrames = 0;
mnDocWidth = map100thmm( nDocWidthInput );
mnDocHeight = map100thmm( nDocHeightInput );
mnDocXScale = (double)nTWIPWidthOutput / mnDocWidth;
mnDocYScale = (double)nTWIPHeightOutput / mnDocHeight;
#ifndef AUGUSTUS
// define an invisible button with the size of a page
Rectangle aRect( 0, 0, (long)( mnDocWidth * mnDocXScale ), (long)( mnDocHeight * mnDocYScale ) );
Polygon aPoly( aRect );
FillStyle aFill = FillStyle( Color(COL_WHITE) );
mnWhiteBackgroundShapeId = defineShape( aPoly, aFill );
::basegfx::B2DHomMatrix m; // #i73264#
mnPageButtonId = createID();
startTag( TAG_DEFINEBUTTON );
mpTag->addUI16( mnPageButtonId ); // character id for button
// button records
mpTag->addUI8( 0x08 ); // only hit state
mpTag->addUI16( mnWhiteBackgroundShapeId ); // shape id of background rectangle
mpTag->addUI16( 0 ); // depth for button DANGER!
mpTag->addMatrix( m ); // identity matrix
mpTag->addUI8( 0 ); // empty color transform
// mpTag->addUI8( 0 ); // end of button records
// action records
mpTag->addUI8( 0x06 ); // ActionPlay
mpTag->addUI8( 0 ); // end of action records
endTag();
// place a shape that clips shapes depth 2-3 to document boundaries
// placeShape( mnWhiteBackgroundShapeId, 1, 0, 0, 4 );
#endif
}
// -----------------------------------------------------------------------------
Writer::~Writer()
{
delete mpVDev;
delete mpSprite;
delete mpTag;
}
// -----------------------------------------------------------------------------
void ImplCopySvStreamToXOutputStream( SvStream& rIn, Reference< XOutputStream > &xOut )
{
sal_uInt32 nBufferSize = 64*1024;
rIn.Seek( STREAM_SEEK_TO_END );
sal_uInt32 nSize = rIn.Tell();
rIn.Seek( STREAM_SEEK_TO_BEGIN );
Sequence< sal_Int8 > aBuffer( min( nBufferSize, nSize ) );
while( nSize )
{
if( nSize < nBufferSize )
{
nBufferSize = nSize;
aBuffer.realloc( nSize );
}
sal_uInt32 nRead = rIn.Read( aBuffer.getArray(), nBufferSize );
DBG_ASSERT( nRead == nBufferSize, "ImplCopySvStreamToXOutputStream failed!" );
xOut->writeBytes( aBuffer );
if( nRead == 0 )
break;
nSize -= nRead;
}
}
// -----------------------------------------------------------------------------
void Writer::storeTo( Reference< XOutputStream > &xOutStream )
{
for(FontMap::iterator i = maFonts.begin(); i != maFonts.end(); i++)
{
FlashFont* pFont = (*i);
pFont->write( *mpFontsStream );
delete pFont;
}
// Endtag
*mpMovieStream << (sal_uInt16)0;
Tag aHeader( 0xff );
aHeader.addUI8( 'F' );
aHeader.addUI8( 'W' );
aHeader.addUI8( 'S' );
aHeader.addUI8( 5 );
sal_uInt32 nSizePos = aHeader.Tell();
aHeader << (sal_uInt32)0;
Rectangle aDocRect( 0, 0, static_cast<long>(mnDocWidth*mnDocXScale), static_cast<long>(mnDocHeight*mnDocYScale) );
aHeader.addRect( aDocRect );
// frame delay in 8.8 fixed number of frames per second
aHeader.addUI8( 0 );
aHeader.addUI8( 12 );
aHeader.addUI16( _uInt16(mnFrames) );
const sal_uInt32 nSize = aHeader.Tell() + mpFontsStream->Tell() + mpMovieStream->Tell();
aHeader.Seek( nSizePos );
aHeader << (sal_uInt32)nSize;
ImplCopySvStreamToXOutputStream( aHeader, xOutStream );
ImplCopySvStreamToXOutputStream( *mpFontsStream, xOutStream );
ImplCopySvStreamToXOutputStream( *mpMovieStream, xOutStream );
}
// -----------------------------------------------------------------------------
sal_uInt16 Writer::startSprite()
{
sal_uInt16 nShapeId = createID();
mvSpriteStack.push(mpSprite);
mpSprite = new Sprite( nShapeId );
return nShapeId;
}
// -----------------------------------------------------------------------------
void Writer::endSprite()
{
if( mpSprite )
{
startTag( TAG_END );
endTag();
mpSprite->write( *mpMovieStream );
delete mpSprite;
if (mvSpriteStack.size() > 0)
{
mpSprite = mvSpriteStack.top();
mvSpriteStack.pop();
}
else
mpSprite = NULL;
}
}
// -----------------------------------------------------------------------------
void Writer::placeShape( sal_uInt16 nID, sal_uInt16 nDepth, sal_Int32 x, sal_Int32 y, sal_uInt16 nClip, const char* pName )
{
startTag( TAG_PLACEOBJECT2 );
BitStream aBits;
aBits.writeUB( nClip != 0, 1 ); // Has Clip Actions?
aBits.writeUB( 0, 1 ); // reserved
aBits.writeUB( pName != NULL, 1 ); // has a name
aBits.writeUB( 0, 1 ); // no ratio
aBits.writeUB( 0, 1 ); // no color transform
aBits.writeUB( 1, 1 ); // has a matrix
aBits.writeUB( 1, 1 ); // places a character
aBits.writeUB( 0, 1 ); // does not define a character to be moved
mpTag->addBits( aBits );
mpTag->addUI16( nDepth ); // depth
mpTag->addUI16( nID ); // character Id
// #i73264#
const basegfx::B2DHomMatrix aMatrix(basegfx::tools::createTranslateB2DHomMatrix(
_Int16(static_cast<long>(map100thmm(x)*mnDocXScale)),
_Int16(static_cast<long>(map100thmm(y)*mnDocYScale))));
mpTag->addMatrix( aMatrix ); // transformation matrix
if( pName )
mpTag->addString( pName );
if( nClip != 0 )
mpTag->addUI16( nClip );
endTag();
}
#ifdef THEFUTURE
// -----------------------------------------------------------------------------
void Writer::moveShape( sal_uInt16 nDepth, sal_Int32 x, sal_Int32 y )
{
startTag( TAG_PLACEOBJECT2 );
BitStream aBits;
aBits.writeUB( 0, 1 ); // Has no Clip Actions
aBits.writeUB( 0, 1 ); // reserved
aBits.writeUB( 0, 1 ); // has no name
aBits.writeUB( 0, 1 ); // no ratio
aBits.writeUB( 0, 1 ); // no color transform
aBits.writeUB( 1, 1 ); // has a matrix
aBits.writeUB( 0, 1 ); // places a character
aBits.writeUB( 1, 1 ); // defines a character to be moved
mpTag->addBits( aBits );
mpTag->addUI16( nDepth ); // depth
// #i73264#
const basegfx::B2DHomMatrix aMatrix(basegfx::tools::createTranslateB2DHomMatrix(
_Int16(static_cast<long>(map100thmm(x)*mnDocXScale)),
_Int16(static_cast<long>(map100thmm(y)*mnDocYScale))));
mpTag->addMatrix( aMatrix ); // transformation matrix
endTag();
}
#endif
// -----------------------------------------------------------------------------
void Writer::removeShape( sal_uInt16 nDepth )
{
startTag( TAG_REMOVEOBJECT2 );
mpTag->addUI16( nDepth ); // depth
endTag();
}
// -----------------------------------------------------------------------------
void Writer::startTag( sal_uInt8 nTagId )
{
DBG_ASSERT( mpTag == NULL, "Last tag was not ended");
mpTag = new Tag( nTagId );
}
// -----------------------------------------------------------------------------
void Writer::endTag()
{
sal_uInt8 nTag = mpTag->getTagId();
if( mpSprite && ( (nTag == TAG_END) || (nTag == TAG_SHOWFRAME) || (nTag == TAG_DOACTION) || (nTag == TAG_STARTSOUND) || (nTag == TAG_PLACEOBJECT) || (nTag == TAG_PLACEOBJECT2) || (nTag == TAG_REMOVEOBJECT2) || (nTag == TAG_FRAMELABEL) ) )
{
mpSprite->addTag( mpTag );
mpTag = NULL;
}
else
{
mpTag->write( *mpMovieStream );
delete mpTag;
mpTag = NULL;
}
}
// -----------------------------------------------------------------------------
sal_uInt16 Writer::createID()
{
return mnNextId++;
}
// -----------------------------------------------------------------------------
void Writer::showFrame()
{
startTag( TAG_SHOWFRAME );
endTag();
if(NULL == mpSprite)
mnFrames++;
}
// -----------------------------------------------------------------------------
sal_uInt16 Writer::defineShape( const GDIMetaFile& rMtf, sal_Int16 x, sal_Int16 y )
{
mpVDev->SetMapMode( rMtf.GetPrefMapMode() );
Impl_writeActions( rMtf );
sal_uInt16 nId = 0;
sal_uInt16 iDepth = 1;
{
CharacterIdVector::iterator aIter( maShapeIds.begin() );
const CharacterIdVector::iterator aEnd( maShapeIds.end() );
sal_Bool bHaveShapes = aIter != aEnd;
if (bHaveShapes)
{
nId = startSprite();
while( aIter != aEnd )
{
placeShape( *aIter, iDepth++, x, y );
aIter++;
}
endSprite();
}
}
maShapeIds.clear();
return nId;
}
// -----------------------------------------------------------------------------
sal_uInt16 Writer::defineShape( const Polygon& rPoly, const FillStyle& rFillStyle )
{
const PolyPolygon aPolyPoly( rPoly );
return defineShape( aPolyPoly, rFillStyle );
}
// -----------------------------------------------------------------------------
sal_uInt16 Writer::defineShape( const PolyPolygon& rPolyPoly, const FillStyle& rFillStyle )
{
sal_uInt16 nShapeId = createID();
// start a DefineShape3 tag
startTag( TAG_DEFINESHAPE3 );
mpTag->addUI16( nShapeId );
mpTag->addRect( rPolyPoly.GetBoundRect() );
// FILLSTYLEARRAY
mpTag->addUI8( 1 ); // FillStyleCount
// FILLSTYLE
rFillStyle.addTo( mpTag );
// LINESTYLEARRAY
mpTag->addUI8( 0 ); // LineStyleCount
// Number of fill and line index bits to 1
mpTag->addUI8( 0x11 );
BitStream aBits;
const sal_uInt16 nCount = rPolyPoly.Count();
sal_uInt16 i;
for( i = 0; i < nCount; i++ )
{
const Polygon& rPoly = rPolyPoly[ i ];
if( rPoly.GetSize() )
Impl_addPolygon( aBits, rPoly, true );
}
Impl_addEndShapeRecord( aBits );
mpTag->addBits( aBits );
endTag();
return nShapeId;
}
// -----------------------------------------------------------------------------
sal_uInt16 Writer::defineShape( const PolyPolygon& rPolyPoly, sal_uInt16 nLineWidth, const Color& rLineColor )
{
sal_uInt16 nShapeId = createID();
// start a DefineShape3 tag
startTag( TAG_DEFINESHAPE3 );
mpTag->addUI16( nShapeId );
mpTag->addRect( rPolyPoly.GetBoundRect() );
// FILLSTYLEARRAY
mpTag->addUI8( 0 ); // FillStyleCount
// LINESTYLEARRAY
mpTag->addUI8( 1 ); // LineStyleCount
// LINESTYLE
mpTag->addUI16( nLineWidth ); // Width of line in twips
mpTag->addRGBA( rLineColor ); // Color
// Number of fill and line index bits to 1
mpTag->addUI8( 0x11 );
BitStream aBits;
const sal_uInt16 nCount = rPolyPoly.Count();
sal_uInt16 i;
for( i = 0; i < nCount; i++ )
{
const Polygon& rPoly = rPolyPoly[ i ];
if( rPoly.GetSize() )
Impl_addPolygon( aBits, rPoly, false );
}
Impl_addEndShapeRecord( aBits );
mpTag->addBits( aBits );
endTag();
return nShapeId;
}
#ifdef AUGUSTUS
enum {NO_COMPRESSION, ADPCM_COMPRESSION, MP3_COMPRESSION } COMPRESSION_TYPE;
sal_Bool Writer::streamSound( const char * filename )
{
SF_INFO info;
SNDFILE *sf = sf_open(filename, SFM_READ, &info);
if (NULL == sf)
return sal_False;
else
{
// AS: Start up lame.
m_lame_flags = lame_init();
// The default (if you set nothing) is a a J-Stereo, 44.1khz
// 128kbps CBR mp3 file at quality 5. Override various default settings
// as necessary, for example:
lame_set_num_channels(m_lame_flags,1);
lame_set_in_samplerate(m_lame_flags,22050);
lame_set_brate(m_lame_flags,48);
lame_set_mode(m_lame_flags,MONO);
lame_set_quality(m_lame_flags,2); /* 2=high 5 = medium 7=low */
// See lame.h for the complete list of options. Note that there are
// some lame_set_*() calls not documented in lame.h. These functions
// are experimental and for testing only. They may be removed in
// the future.
//4. Set more internal configuration based on data provided above,
// as well as checking for problems. Check that ret_code >= 0.
int ret_code = lame_init_params(m_lame_flags);
if (ret_code < 0)
throw 0;
int lame_frame_size = lame_get_framesize(m_lame_flags);
int samples_per_frame = 22050 / 12; // AS: (samples/sec) / (frames/sec) = samples/frame
int mp3buffer_size = static_cast<int>(samples_per_frame*1.25 + 7200 + 7200);
startTag(TAG_SOUNDSTREAMHEAD2);
mpTag->addUI8(2<<2 | 1<<1 | 0<<0); // Preferred mixer format ??
BitStream bs;
bs.writeUB(MP3_COMPRESSION,4);
bs.writeUB(2, 2); // AS: Reserved zero bits.
bs.writeUB(1, 1); // AS: 16 Bit
bs.writeUB(0, 1); // AS: Mono.
mpTag->addBits(bs);
mpTag->addUI16(samples_per_frame);
endTag();
short *sample_buff = new short[static_cast<int>(info.frames)];
sf_readf_short(sf, sample_buff, info.frames);
unsigned char* mp3buffer = new unsigned char[mp3buffer_size];
// 5. Encode some data. input pcm data, output (maybe) mp3 frames.
// This routine handles all buffering, resampling and filtering for you.
// The required mp3buffer_size can be computed from num_samples,
// samplerate and encoding rate, but here is a worst case estimate:
// mp3buffer_size (in bytes) = 1.25*num_samples + 7200.
// num_samples = the number of PCM samples in each channel. It is
// not the sum of the number of samples in the L and R channels.
//
// The return code = number of bytes output in mp3buffer. This can be 0.
// If it is <0, an error occured.
for (int samples_written = 0; samples_written < info.frames; samples_written += samples_per_frame)
{
startTag(TAG_SOUNDSTREAMBLOCK);
int samples_to_write = std::min((int)info.frames - samples_written, samples_per_frame);
// AS: Since we're mono, left and right sample buffs are the same
// ie, samplebuff (which is why we pass it twice).
int ret = lame_encode_buffer(m_lame_flags, sample_buff + samples_written,
sample_buff + samples_written,
samples_to_write, mp3buffer, mp3buffer_size);
if (ret < 0)
throw 0;
// 6. lame_encode_flush will flush the buffers and may return a
// final few mp3 frames. mp3buffer should be at least 7200 bytes.
// return code = number of bytes output to mp3buffer. This can be 0.
if (mp3buffer_size - ret < 7200)
throw 0;
int ret2 = lame_encode_flush(m_lame_flags, mp3buffer + ret, mp3buffer_size - ret);
if (ret2 < 0)
throw 0;
SvMemoryStream strm(mp3buffer, ret + ret2, STREAM_READWRITE);
mpTag->addUI16(samples_to_write); //lame_frame_size);
mpTag->addUI16(0);
mpTag->addStream(strm);
endTag();
showFrame();
}
delete[] mp3buffer;
delete[] sample_buff;
int err = sf_close(sf);
// 8. free the internal data structures.
lame_close(m_lame_flags);
}
return sal_True;
}
#endif // AUGUSTUS
// -----------------------------------------------------------------------------
void Writer::stop()
{
startTag( TAG_DOACTION );
mpTag->addUI8( 0x07 );
mpTag->addUI8( 0 );
endTag();
}
// -----------------------------------------------------------------------------
void Writer::waitOnClick( sal_uInt16 nDepth )
{
placeShape( _uInt16( mnPageButtonId ), nDepth, 0, 0 );
stop();
showFrame();
removeShape( nDepth );
}
// -----------------------------------------------------------------------------
/** inserts a doaction tag with an ActionGotoFrame */
void Writer::gotoFrame( sal_uInt16 nFrame )
{
startTag( TAG_DOACTION );
mpTag->addUI8( 0x81 );
mpTag->addUI16( 2 );
mpTag->addUI16( nFrame );
mpTag->addUI8( 0 );
endTag();
}