Line data Source code
1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /*
3 : * This file is part of the LibreOffice project.
4 : *
5 : * This Source Code Form is subject to the terms of the Mozilla Public
6 : * License, v. 2.0. If a copy of the MPL was not distributed with this
7 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 : *
9 : * This file incorporates work covered by the following license notice:
10 : *
11 : * Licensed to the Apache Software Foundation (ASF) under one or more
12 : * contributor license agreements. See the NOTICE file distributed
13 : * with this work for additional information regarding copyright
14 : * ownership. The ASF licenses this file to you under the Apache
15 : * License, Version 2.0 (the "License"); you may not use this file
16 : * except in compliance with the License. You may obtain a copy of
17 : * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 : */
19 :
20 : #include "swfwriter.hxx"
21 : #include <vcl/virdev.hxx>
22 : #include <vcl/gdimtf.hxx>
23 : #include <basegfx/matrix/b2dhommatrixtools.hxx>
24 :
25 : using namespace ::swf;
26 : using namespace ::std;
27 : using namespace ::com::sun::star::uno;
28 : using namespace ::com::sun::star::io;
29 :
30 : // -----------------------------------------------------------------------------
31 :
32 0 : static MapMode aTWIPSMode( MAP_TWIP );
33 0 : static MapMode a100thmmMode( MAP_100TH_MM );
34 :
35 0 : static sal_Int32 map100thmm( sal_Int32 n100thMM )
36 : {
37 0 : Point aPoint( n100thMM, n100thMM );
38 0 : sal_Int32 nX = OutputDevice::LogicToLogic( aPoint, a100thmmMode, aTWIPSMode ).X();
39 0 : return nX;
40 : }
41 :
42 : // -----------------------------------------------------------------------------
43 :
44 0 : Writer::Writer( sal_Int32 nTWIPWidthOutput, sal_Int32 nTWIPHeightOutput, sal_Int32 nDocWidthInput, sal_Int32 nDocHeightInput, sal_Int32 nJPEGcompressMode )
45 : : mpClipPolyPolygon( NULL ),
46 : mpTag( NULL ),
47 : mpSprite( NULL ),
48 : mnNextId( 1 ),
49 : mnGlobalTransparency(0),
50 0 : mnJPEGCompressMode(nJPEGcompressMode)
51 : {
52 0 : mpVDev = new VirtualDevice;
53 0 : mpVDev->EnableOutput( sal_False );
54 :
55 0 : maMovieTempFile.EnableKillingFile();
56 0 : maFontsTempFile.EnableKillingFile();
57 :
58 0 : mpMovieStream = maMovieTempFile.GetStream( STREAM_WRITE|STREAM_TRUNC );
59 0 : mpFontsStream = maFontsTempFile.GetStream( STREAM_WRITE|STREAM_TRUNC );
60 :
61 0 : mnFrames = 0;
62 :
63 0 : mnDocWidth = map100thmm( nDocWidthInput );
64 0 : mnDocHeight = map100thmm( nDocHeightInput );
65 :
66 0 : mnDocXScale = (double)nTWIPWidthOutput / mnDocWidth;
67 0 : mnDocYScale = (double)nTWIPHeightOutput / mnDocHeight;
68 :
69 : #ifndef AUGUSTUS
70 : // define an invisible button with the size of a page
71 0 : Rectangle aRect( 0, 0, (long)( mnDocWidth * mnDocXScale ), (long)( mnDocHeight * mnDocYScale ) );
72 0 : Polygon aPoly( aRect );
73 0 : FillStyle aFill = FillStyle( Color(COL_WHITE) );
74 0 : mnWhiteBackgroundShapeId = defineShape( aPoly, aFill );
75 :
76 0 : ::basegfx::B2DHomMatrix m; // #i73264#
77 0 : mnPageButtonId = createID();
78 0 : startTag( TAG_DEFINEBUTTON );
79 0 : mpTag->addUI16( mnPageButtonId ); // character id for button
80 :
81 : // button records
82 0 : mpTag->addUI8( 0x08 ); // only hit state
83 0 : mpTag->addUI16( mnWhiteBackgroundShapeId ); // shape id of background rectangle
84 0 : mpTag->addUI16( 0 ); // depth for button DANGER!
85 0 : mpTag->addMatrix( m ); // identity matrix
86 0 : mpTag->addUI8( 0 ); // empty color transform
87 :
88 : // mpTag->addUI8( 0 ); // end of button records
89 :
90 : // action records
91 0 : mpTag->addUI8( 0x06 ); // ActionPlay
92 0 : mpTag->addUI8( 0 ); // end of action records
93 :
94 0 : endTag();
95 :
96 : // place a shape that clips shapes depth 2-3 to document boundaries
97 : // placeShape( mnWhiteBackgroundShapeId, 1, 0, 0, 4 );
98 : #endif
99 0 : }
100 :
101 : // -----------------------------------------------------------------------------
102 :
103 0 : Writer::~Writer()
104 : {
105 0 : delete mpVDev;
106 0 : delete mpSprite;
107 0 : delete mpTag;
108 0 : }
109 :
110 : // -----------------------------------------------------------------------------
111 :
112 0 : void ImplCopySvStreamToXOutputStream( SvStream& rIn, Reference< XOutputStream > &xOut )
113 : {
114 0 : sal_uInt32 nBufferSize = 64*1024;
115 :
116 0 : rIn.Seek( STREAM_SEEK_TO_END );
117 0 : sal_uInt32 nSize = rIn.Tell();
118 0 : rIn.Seek( STREAM_SEEK_TO_BEGIN );
119 :
120 0 : Sequence< sal_Int8 > aBuffer( min( nBufferSize, nSize ) );
121 :
122 0 : while( nSize )
123 : {
124 0 : if( nSize < nBufferSize )
125 : {
126 0 : nBufferSize = nSize;
127 0 : aBuffer.realloc( nSize );
128 : }
129 :
130 0 : sal_uInt32 nRead = rIn.Read( aBuffer.getArray(), nBufferSize );
131 : DBG_ASSERT( nRead == nBufferSize, "ImplCopySvStreamToXOutputStream failed!" );
132 0 : xOut->writeBytes( aBuffer );
133 :
134 0 : if( nRead == 0 )
135 0 : break;
136 :
137 0 : nSize -= nRead;
138 0 : }
139 0 : }
140 :
141 : // -----------------------------------------------------------------------------
142 :
143 0 : void Writer::storeTo( Reference< XOutputStream > &xOutStream )
144 : {
145 0 : for(FontMap::iterator i = maFonts.begin(); i != maFonts.end(); ++i)
146 : {
147 0 : FlashFont* pFont = (*i);
148 0 : pFont->write( *mpFontsStream );
149 0 : delete pFont;
150 : }
151 :
152 : // Endtag
153 0 : *mpMovieStream << (sal_uInt16)0;
154 :
155 0 : Tag aHeader( 0xff );
156 :
157 0 : aHeader.addUI8( 'F' );
158 0 : aHeader.addUI8( 'W' );
159 0 : aHeader.addUI8( 'S' );
160 0 : aHeader.addUI8( 5 );
161 :
162 0 : sal_uInt32 nSizePos = aHeader.Tell();
163 :
164 0 : aHeader << (sal_uInt32)0;
165 :
166 0 : Rectangle aDocRect( 0, 0, static_cast<long>(mnDocWidth*mnDocXScale), static_cast<long>(mnDocHeight*mnDocYScale) );
167 :
168 0 : aHeader.addRect( aDocRect );
169 :
170 : // frame delay in 8.8 fixed number of frames per second
171 0 : aHeader.addUI8( 0 );
172 0 : aHeader.addUI8( 12 );
173 :
174 0 : aHeader.addUI16( _uInt16(mnFrames) );
175 :
176 0 : const sal_uInt32 nSize = aHeader.Tell() + mpFontsStream->Tell() + mpMovieStream->Tell();
177 :
178 0 : aHeader.Seek( nSizePos );
179 0 : aHeader << (sal_uInt32)nSize;
180 :
181 0 : ImplCopySvStreamToXOutputStream( aHeader, xOutStream );
182 0 : ImplCopySvStreamToXOutputStream( *mpFontsStream, xOutStream );
183 0 : ImplCopySvStreamToXOutputStream( *mpMovieStream, xOutStream );
184 0 : }
185 :
186 : // -----------------------------------------------------------------------------
187 :
188 0 : sal_uInt16 Writer::startSprite()
189 : {
190 0 : sal_uInt16 nShapeId = createID();
191 0 : mvSpriteStack.push(mpSprite);
192 0 : mpSprite = new Sprite( nShapeId );
193 0 : return nShapeId;
194 : }
195 :
196 : // -----------------------------------------------------------------------------
197 :
198 0 : void Writer::endSprite()
199 : {
200 0 : if( mpSprite )
201 : {
202 0 : startTag( TAG_END );
203 0 : endTag();
204 :
205 0 : mpSprite->write( *mpMovieStream );
206 0 : delete mpSprite;
207 :
208 0 : if (!mvSpriteStack.empty())
209 : {
210 0 : mpSprite = mvSpriteStack.top();
211 0 : mvSpriteStack.pop();
212 : }
213 : else
214 0 : mpSprite = NULL;
215 : }
216 0 : }
217 :
218 : // -----------------------------------------------------------------------------
219 :
220 0 : void Writer::placeShape( sal_uInt16 nID, sal_uInt16 nDepth, sal_Int32 x, sal_Int32 y, sal_uInt16 nClip, const char* pName )
221 : {
222 0 : startTag( TAG_PLACEOBJECT2 );
223 :
224 0 : BitStream aBits;
225 :
226 0 : aBits.writeUB( nClip != 0, 1 ); // Has Clip Actions?
227 0 : aBits.writeUB( 0, 1 ); // reserved
228 0 : aBits.writeUB( pName != NULL, 1 ); // has a name
229 0 : aBits.writeUB( 0, 1 ); // no ratio
230 0 : aBits.writeUB( 0, 1 ); // no color transform
231 0 : aBits.writeUB( 1, 1 ); // has a matrix
232 0 : aBits.writeUB( 1, 1 ); // places a character
233 0 : aBits.writeUB( 0, 1 ); // does not define a character to be moved
234 :
235 0 : mpTag->addBits( aBits );
236 0 : mpTag->addUI16( nDepth ); // depth
237 0 : mpTag->addUI16( nID ); // character Id
238 :
239 : // #i73264#
240 : const basegfx::B2DHomMatrix aMatrix(basegfx::tools::createTranslateB2DHomMatrix(
241 0 : _Int16(static_cast<long>(map100thmm(x)*mnDocXScale)),
242 0 : _Int16(static_cast<long>(map100thmm(y)*mnDocYScale))));
243 0 : mpTag->addMatrix( aMatrix ); // transformation matrix
244 :
245 0 : if( pName )
246 0 : mpTag->addString( pName );
247 :
248 0 : if( nClip != 0 )
249 0 : mpTag->addUI16( nClip );
250 :
251 0 : endTag();
252 0 : }
253 :
254 : #ifdef THEFUTURE
255 : // -----------------------------------------------------------------------------
256 :
257 : void Writer::moveShape( sal_uInt16 nDepth, sal_Int32 x, sal_Int32 y )
258 : {
259 : startTag( TAG_PLACEOBJECT2 );
260 :
261 : BitStream aBits;
262 : aBits.writeUB( 0, 1 ); // Has no Clip Actions
263 : aBits.writeUB( 0, 1 ); // reserved
264 : aBits.writeUB( 0, 1 ); // has no name
265 : aBits.writeUB( 0, 1 ); // no ratio
266 : aBits.writeUB( 0, 1 ); // no color transform
267 : aBits.writeUB( 1, 1 ); // has a matrix
268 : aBits.writeUB( 0, 1 ); // places a character
269 : aBits.writeUB( 1, 1 ); // defines a character to be moved
270 :
271 : mpTag->addBits( aBits );
272 : mpTag->addUI16( nDepth ); // depth
273 :
274 : // #i73264#
275 : const basegfx::B2DHomMatrix aMatrix(basegfx::tools::createTranslateB2DHomMatrix(
276 : _Int16(static_cast<long>(map100thmm(x)*mnDocXScale)),
277 : _Int16(static_cast<long>(map100thmm(y)*mnDocYScale))));
278 : mpTag->addMatrix( aMatrix ); // transformation matrix
279 :
280 : endTag();
281 : }
282 : #endif
283 :
284 : // -----------------------------------------------------------------------------
285 :
286 0 : void Writer::removeShape( sal_uInt16 nDepth )
287 : {
288 0 : startTag( TAG_REMOVEOBJECT2 );
289 0 : mpTag->addUI16( nDepth ); // depth
290 0 : endTag();
291 0 : }
292 :
293 : // -----------------------------------------------------------------------------
294 :
295 0 : void Writer::startTag( sal_uInt8 nTagId )
296 : {
297 : DBG_ASSERT( mpTag == NULL, "Last tag was not ended");
298 :
299 0 : mpTag = new Tag( nTagId );
300 0 : }
301 :
302 : // -----------------------------------------------------------------------------
303 :
304 0 : void Writer::endTag()
305 : {
306 0 : sal_uInt8 nTag = mpTag->getTagId();
307 :
308 0 : 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) ) )
309 : {
310 0 : mpSprite->addTag( mpTag );
311 0 : mpTag = NULL;
312 : }
313 : else
314 : {
315 0 : mpTag->write( *mpMovieStream );
316 0 : delete mpTag;
317 0 : mpTag = NULL;
318 : }
319 0 : }
320 :
321 : // -----------------------------------------------------------------------------
322 :
323 0 : sal_uInt16 Writer::createID()
324 : {
325 0 : return mnNextId++;
326 : }
327 :
328 : // -----------------------------------------------------------------------------
329 :
330 0 : void Writer::showFrame()
331 : {
332 0 : startTag( TAG_SHOWFRAME );
333 0 : endTag();
334 :
335 0 : if(NULL == mpSprite)
336 0 : mnFrames++;
337 0 : }
338 :
339 : // -----------------------------------------------------------------------------
340 :
341 0 : sal_uInt16 Writer::defineShape( const GDIMetaFile& rMtf, sal_Int16 x, sal_Int16 y )
342 : {
343 0 : mpVDev->SetMapMode( rMtf.GetPrefMapMode() );
344 0 : Impl_writeActions( rMtf );
345 :
346 0 : sal_uInt16 nId = 0;
347 : {
348 0 : CharacterIdVector::iterator aIter( maShapeIds.begin() );
349 0 : const CharacterIdVector::iterator aEnd( maShapeIds.end() );
350 :
351 0 : sal_Bool bHaveShapes = aIter != aEnd;
352 :
353 0 : if (bHaveShapes)
354 : {
355 0 : nId = startSprite();
356 :
357 0 : sal_uInt16 iDepth = 1;
358 0 : for(; aIter != aEnd; ++aIter)
359 : {
360 0 : placeShape( *aIter, iDepth++, x, y );
361 : }
362 :
363 0 : endSprite();
364 : }
365 : }
366 :
367 0 : maShapeIds.clear();
368 :
369 0 : return nId;
370 : }
371 :
372 : // -----------------------------------------------------------------------------
373 :
374 0 : sal_uInt16 Writer::defineShape( const Polygon& rPoly, const FillStyle& rFillStyle )
375 : {
376 0 : const PolyPolygon aPolyPoly( rPoly );
377 0 : return defineShape( aPolyPoly, rFillStyle );
378 : }
379 :
380 : // -----------------------------------------------------------------------------
381 :
382 0 : sal_uInt16 Writer::defineShape( const PolyPolygon& rPolyPoly, const FillStyle& rFillStyle )
383 : {
384 0 : sal_uInt16 nShapeId = createID();
385 :
386 : // start a DefineShape3 tag
387 0 : startTag( TAG_DEFINESHAPE3 );
388 :
389 0 : mpTag->addUI16( nShapeId );
390 0 : mpTag->addRect( rPolyPoly.GetBoundRect() );
391 :
392 :
393 : // FILLSTYLEARRAY
394 0 : mpTag->addUI8( 1 ); // FillStyleCount
395 :
396 : // FILLSTYLE
397 0 : rFillStyle.addTo( mpTag );
398 :
399 : // LINESTYLEARRAY
400 0 : mpTag->addUI8( 0 ); // LineStyleCount
401 :
402 : // Number of fill and line index bits to 1
403 0 : mpTag->addUI8( 0x11 );
404 :
405 0 : BitStream aBits;
406 :
407 0 : const sal_uInt16 nCount = rPolyPoly.Count();
408 : sal_uInt16 i;
409 0 : for( i = 0; i < nCount; i++ )
410 : {
411 0 : const Polygon& rPoly = rPolyPoly[ i ];
412 0 : if( rPoly.GetSize() )
413 0 : Impl_addPolygon( aBits, rPoly, true );
414 : }
415 :
416 0 : Impl_addEndShapeRecord( aBits );
417 :
418 0 : mpTag->addBits( aBits );
419 0 : endTag();
420 :
421 0 : return nShapeId;
422 : }
423 :
424 : // -----------------------------------------------------------------------------
425 :
426 0 : sal_uInt16 Writer::defineShape( const PolyPolygon& rPolyPoly, sal_uInt16 nLineWidth, const Color& rLineColor )
427 : {
428 0 : sal_uInt16 nShapeId = createID();
429 :
430 : // start a DefineShape3 tag
431 0 : startTag( TAG_DEFINESHAPE3 );
432 :
433 0 : mpTag->addUI16( nShapeId );
434 0 : mpTag->addRect( rPolyPoly.GetBoundRect() );
435 :
436 :
437 : // FILLSTYLEARRAY
438 0 : mpTag->addUI8( 0 ); // FillStyleCount
439 :
440 : // LINESTYLEARRAY
441 0 : mpTag->addUI8( 1 ); // LineStyleCount
442 :
443 : // LINESTYLE
444 0 : mpTag->addUI16( nLineWidth ); // Width of line in twips
445 0 : mpTag->addRGBA( rLineColor ); // Color
446 :
447 : // Number of fill and line index bits to 1
448 0 : mpTag->addUI8( 0x11 );
449 :
450 0 : BitStream aBits;
451 :
452 0 : const sal_uInt16 nCount = rPolyPoly.Count();
453 : sal_uInt16 i;
454 0 : for( i = 0; i < nCount; i++ )
455 : {
456 0 : const Polygon& rPoly = rPolyPoly[ i ];
457 0 : if( rPoly.GetSize() )
458 0 : Impl_addPolygon( aBits, rPoly, false );
459 : }
460 :
461 0 : Impl_addEndShapeRecord( aBits );
462 :
463 0 : mpTag->addBits( aBits );
464 0 : endTag();
465 :
466 0 : return nShapeId;
467 : }
468 :
469 : #ifdef AUGUSTUS
470 : enum {NO_COMPRESSION, ADPCM_COMPRESSION, MP3_COMPRESSION } COMPRESSION_TYPE;
471 : sal_Bool Writer::streamSound( const char * filename )
472 : {
473 : SF_INFO info;
474 : SNDFILE *sf = sf_open(filename, SFM_READ, &info);
475 :
476 : if (NULL == sf)
477 : return sal_False;
478 : else
479 : {
480 : // AS: Start up lame.
481 : m_lame_flags = lame_init();
482 :
483 : // The default (if you set nothing) is a a J-Stereo, 44.1khz
484 : // 128kbps CBR mp3 file at quality 5. Override various default settings
485 : // as necessary, for example:
486 :
487 : lame_set_num_channels(m_lame_flags,1);
488 : lame_set_in_samplerate(m_lame_flags,22050);
489 : lame_set_brate(m_lame_flags,48);
490 : lame_set_mode(m_lame_flags,MONO);
491 : lame_set_quality(m_lame_flags,2); /* 2=high 5 = medium 7=low */
492 :
493 : // See lame.h for the complete list of options. Note that there are
494 : // some lame_set_*() calls not documented in lame.h. These functions
495 : // are experimental and for testing only. They may be removed in
496 : // the future.
497 :
498 : //4. Set more internal configuration based on data provided above,
499 : // as well as checking for problems. Check that ret_code >= 0.
500 :
501 : int ret_code = lame_init_params(m_lame_flags);
502 :
503 : if (ret_code < 0)
504 : throw 0;
505 :
506 : int samples_per_frame = 22050 / 12; // AS: (samples/sec) / (frames/sec) = samples/frame
507 : int mp3buffer_size = static_cast<int>(samples_per_frame*1.25 + 7200 + 7200);
508 :
509 :
510 : startTag(TAG_SOUNDSTREAMHEAD2);
511 :
512 : mpTag->addUI8(2<<2 | 1<<1 | 0<<0); // Preferred mixer format ??
513 :
514 : BitStream bs;
515 :
516 : bs.writeUB(MP3_COMPRESSION,4);
517 : bs.writeUB(2, 2); // AS: Reserved zero bits.
518 : bs.writeUB(1, 1); // AS: 16 Bit
519 : bs.writeUB(0, 1); // AS: Mono.
520 :
521 : mpTag->addBits(bs);
522 :
523 : mpTag->addUI16(samples_per_frame);
524 : endTag();
525 :
526 : short *sample_buff = new short[static_cast<int>(info.frames)];
527 : sf_readf_short(sf, sample_buff, info.frames);
528 :
529 : unsigned char* mp3buffer = new unsigned char[mp3buffer_size];
530 :
531 : // 5. Encode some data. input pcm data, output (maybe) mp3 frames.
532 : // This routine handles all buffering, resampling and filtering for you.
533 : // The required mp3buffer_size can be computed from num_samples,
534 : // samplerate and encoding rate, but here is a worst case estimate:
535 : // mp3buffer_size (in bytes) = 1.25*num_samples + 7200.
536 : // num_samples = the number of PCM samples in each channel. It is
537 : // not the sum of the number of samples in the L and R channels.
538 : //
539 : // The return code = number of bytes output in mp3buffer. This can be 0.
540 : // If it is <0, an error occurred.
541 :
542 :
543 : for (int samples_written = 0; samples_written < info.frames; samples_written += samples_per_frame)
544 : {
545 : startTag(TAG_SOUNDSTREAMBLOCK);
546 :
547 : int samples_to_write = std::min((int)info.frames - samples_written, samples_per_frame);
548 :
549 : // AS: Since we're mono, left and right sample buffs are the same
550 : // ie, samplebuff (which is why we pass it twice).
551 : int ret = lame_encode_buffer(m_lame_flags, sample_buff + samples_written,
552 : sample_buff + samples_written,
553 : samples_to_write, mp3buffer, mp3buffer_size);
554 :
555 : if (ret < 0)
556 : throw 0;
557 :
558 : // 6. lame_encode_flush will flush the buffers and may return a
559 : // final few mp3 frames. mp3buffer should be at least 7200 bytes.
560 : // return code = number of bytes output to mp3buffer. This can be 0.
561 :
562 : if (mp3buffer_size - ret < 7200)
563 : throw 0;
564 :
565 : int ret2 = lame_encode_flush(m_lame_flags, mp3buffer + ret, mp3buffer_size - ret);
566 :
567 : if (ret2 < 0)
568 : throw 0;
569 :
570 :
571 : SvMemoryStream strm(mp3buffer, ret + ret2, STREAM_READWRITE);
572 :
573 : mpTag->addUI16(samples_to_write);
574 : mpTag->addUI16(0);
575 : mpTag->addStream(strm);
576 :
577 : endTag();
578 :
579 : showFrame();
580 : }
581 :
582 :
583 : delete[] mp3buffer;
584 :
585 : delete[] sample_buff;
586 : sf_close(sf);
587 :
588 : // 8. free the internal data structures.
589 : lame_close(m_lame_flags);
590 : }
591 :
592 : return sal_True;
593 : }
594 : #endif // AUGUSTUS
595 :
596 :
597 : // -----------------------------------------------------------------------------
598 :
599 0 : void Writer::stop()
600 : {
601 0 : startTag( TAG_DOACTION );
602 0 : mpTag->addUI8( 0x07 );
603 0 : mpTag->addUI8( 0 );
604 0 : endTag();
605 0 : }
606 :
607 : // -----------------------------------------------------------------------------
608 :
609 0 : void Writer::waitOnClick( sal_uInt16 nDepth )
610 : {
611 0 : placeShape( _uInt16( mnPageButtonId ), nDepth, 0, 0 );
612 0 : stop();
613 0 : showFrame();
614 0 : removeShape( nDepth );
615 0 : }
616 :
617 : // -----------------------------------------------------------------------------
618 :
619 : /** inserts a doaction tag with an ActionGotoFrame */
620 0 : void Writer::gotoFrame( sal_uInt16 nFrame )
621 : {
622 0 : startTag( TAG_DOACTION );
623 0 : mpTag->addUI8( 0x81 );
624 0 : mpTag->addUI16( 2 );
625 0 : mpTag->addUI16( nFrame );
626 0 : mpTag->addUI8( 0 );
627 0 : endTag();
628 0 : }
629 :
630 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|