Source code: com/anotherbigidea/flash/writers/TagWriter.java
1 /****************************************************************
2 * Copyright (c) 2001, David N. Main, All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or
5 * without modification, are permitted provided that the
6 * following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above
9 * copyright notice, this list of conditions and the following
10 * disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above
13 * copyright notice, this list of conditions and the following
14 * disclaimer in the documentation and/or other materials
15 * provided with the distribution.
16 *
17 * 3. The name of the author may not be used to endorse or
18 * promote products derived from this software without specific
19 * prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
22 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
23 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
24 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
31 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
32 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 ****************************************************************/
34 package com.anotherbigidea.flash.writers;
35
36 import java.io.*;
37 import java.util.*;
38 import java.util.zip.*;
39 import com.anotherbigidea.io.*;
40 import com.anotherbigidea.flash.*;
41 import com.anotherbigidea.flash.structs.*;
42 import com.anotherbigidea.flash.interfaces.*;
43
44 /**
45 * A writer that implements the SWFTagTypes interface and writes
46 * to a SWFTags interface
47 */
48 public class TagWriter implements SWFTagTypes, SWFConstants, SWFFileSignature
49 {
50 protected SWFTags mTags;
51
52 protected OutStream out;
53 protected ByteArrayOutputStream bytes;
54 protected int tagType;
55 protected boolean longTag;
56 protected int version;
57 protected String mStringEncoding = SWFConstants.STRING_ENCODING_MX;
58
59 public TagWriter( SWFTags tags )
60 {
61 mTags = tags;
62 }
63
64 protected OutStream getOutStream() { return out; }
65
66 protected SWFActions factorySWFActions()
67 {
68 return new ActionWriter( this, version );
69 }
70
71 protected SWFShape factorySWFShape( boolean hasAlpha, boolean hasStyle )
72 {
73 return new SWFShapeImpl( this, hasAlpha, hasStyle );
74 }
75
76 /**
77 * @see SWFFileSignature#signature(String)
78 */
79 public void signature( String sig ) {
80 if( mTags instanceof SWFFileSignature ) {
81 ((SWFFileSignature) mTags).signature( sig );
82 }
83 }
84
85 /**
86 * Start a new tag context
87 */
88 protected void startTag( int tagType, boolean longTag )
89 {
90 this.tagType = tagType;
91 this.longTag = longTag;
92
93 bytes = new ByteArrayOutputStream( 10000 );
94 out = new OutStream( bytes );
95 }
96
97 /**
98 * Start a new definition tag context
99 */
100 protected void startTag( int tagType, int id, boolean longTag )
101 throws IOException
102 {
103 startTag( tagType, longTag );
104 out.writeUI16( id );
105 }
106
107 /**
108 * Finish the tag context and write the tag
109 */
110 protected void completeTag() throws IOException
111 {
112 out.flush();
113 byte[] contents = bytes.toByteArray();
114
115 out = null;
116 bytes = null;
117
118 mTags.tag( tagType, longTag, contents );
119 }
120
121 /**
122 * SWFTags interface
123 */
124 public void tag( int tagType, boolean longTag, byte[] contents )
125 throws IOException
126 {
127 mTags.tag( tagType, longTag, contents );
128 }
129
130 /**
131 * SWFHeader interface.
132 * Sets movie length to -1 to force a recalculation since the length
133 * cannot be guaranteed to be the same as the original.
134 */
135 public void header( int version, long length,
136 int twipsWidth, int twipsHeight,
137 int frameRate, int frameCount ) throws IOException
138 {
139 this.version = version;
140 mTags.header( version, -1, twipsWidth, twipsHeight, frameRate, frameCount );
141
142 //set encoding to Ascii if pre-MX
143 if( version < SWFConstants.FLASH_MX_VERSION ) {
144 mStringEncoding = SWFConstants.STRING_ENCODING_PRE_MX;
145 }
146 }
147
148 /**
149 * SWFTagTypes interface
150 */
151 public void tagEnd() throws IOException
152 {
153 mTags.tag( TAG_END, false, null );
154 }
155
156 /**
157 * SWFTagTypes interface
158 */
159 public void tagShowFrame() throws IOException
160 {
161 mTags.tag( TAG_SHOWFRAME, false, null );
162 }
163
164 /**
165 * SWFTagTypes interface
166 */
167 public void tagDefineSound( int id, int format, int frequency,
168 boolean bits16, boolean stereo,
169 int sampleCount, byte[] soundData )
170 throws IOException
171 {
172 startTag( TAG_DEFINESOUND, id, true );
173 out.writeUBits( 4, format );
174 out.writeUBits( 2, frequency );
175 out.writeUBits( 1, bits16 ? 1 : 0 );
176 out.writeUBits( 1, stereo ? 1 : 0 );
177 out.writeUI32 ( sampleCount );
178 out.write( soundData );
179 completeTag();
180 }
181
182 /**
183 * SWFTagTypes interface
184 */
185 public void tagDefineButtonSound( int buttonId,
186 int rollOverSoundId, SoundInfo rollOverSoundInfo,
187 int rollOutSoundId, SoundInfo rollOutSoundInfo,
188 int pressSoundId, SoundInfo pressSoundInfo,
189 int releaseSoundId, SoundInfo releaseSoundInfo )
190 throws IOException
191 {
192 startTag( TAG_DEFINEBUTTONSOUND, buttonId, true );
193
194 out.writeUI16( rollOverSoundId );
195 if( rollOverSoundId != 0 ) rollOverSoundInfo.write( out );
196
197 out.writeUI16( rollOutSoundId );
198 if( rollOutSoundId != 0 ) rollOutSoundInfo.write( out );
199
200 out.writeUI16( pressSoundId );
201 if( pressSoundId != 0 ) pressSoundInfo.write( out );
202 out.writeUI16( releaseSoundId );
203 if( releaseSoundId != 0 ) releaseSoundInfo.write( out );
204
205 completeTag();
206 }
207
208 /**
209 * SWFTagTypes interface
210 */
211 public void tagStartSound( int soundId, SoundInfo info ) throws IOException
212 {
213 startTag( TAG_STARTSOUND, soundId, false );
214 info.write( out );
215 completeTag();
216 }
217
218 /**
219 * SWFTagTypes interface
220 */
221 public void tagSoundStreamHead(
222 int playbackFrequency, boolean playback16bit, boolean playbackStereo,
223 int streamFormat, int streamFrequency, boolean stream16bit, boolean streamStereo,
224 int averageSampleCount ) throws IOException
225 {
226 writeSoundStreamHead( TAG_SOUNDSTREAMHEAD,
227 playbackFrequency, playback16bit, playbackStereo,
228 streamFormat, streamFrequency, stream16bit, streamStereo,
229 averageSampleCount );
230 }
231
232 /**
233 * SWFTagTypes interface
234 */
235 public void tagSoundStreamHead2(
236 int playbackFrequency, boolean playback16bit, boolean playbackStereo,
237 int streamFormat, int streamFrequency, boolean stream16bit, boolean streamStereo,
238 int averageSampleCount ) throws IOException
239 {
240 writeSoundStreamHead( TAG_SOUNDSTREAMHEAD2,
241 playbackFrequency, playback16bit, playbackStereo,
242 streamFormat, streamFrequency, stream16bit, streamStereo,
243 averageSampleCount );
244 }
245
246 public void writeSoundStreamHead( int tag,
247 int playbackFrequency, boolean playback16bits, boolean playbackStereo,
248 int streamFormat, int streamFrequency, boolean stream16bits, boolean streamStereo,
249 int averageSampleCount ) throws IOException
250 {
251 startTag( tag, false );
252
253 out.writeUBits(4,0);
254 out.writeUBits(2,playbackFrequency);
255 out.writeUBits(1, playback16bits ? 1 : 0 );
256 out.writeUBits(1, playbackStereo ? 1 : 0 );
257
258 out.writeUBits(4,streamFormat);
259 out.writeUBits(2,streamFrequency);
260 out.writeUBits(1, stream16bits ? 1 : 0 );
261 out.writeUBits(1, streamStereo ? 1 : 0 );
262 out.writeUI16 (averageSampleCount);
263
264 if( streamFormat == SWFConstants.SOUND_FORMAT_MP3 )
265 {
266 out.writeUI16 (0); //unknown
267 }
268
269 completeTag();
270 }
271
272 /**
273 * SWFTagTypes interface
274 */
275 public void tagSoundStreamBlock( byte[] soundData ) throws IOException
276 {
277 startTag( TAG_SOUNDSTREAMBLOCK, true );
278 out.write( soundData );
279 completeTag();
280 }
281
282 /**
283 * SWFTagTypes interface
284 */
285 public void tagSerialNumber( String serialNumber ) throws IOException
286 {
287 startTag( TAG_SERIALNUMBER, false );
288 out.writeString( serialNumber, mStringEncoding );
289 completeTag();
290 }
291
292 /**
293 * SWFTagTypes interface
294 */
295 public void tagGenerator( byte[] data ) throws IOException
296 {
297 startTag( TAG_FLASHGENERATOR, false );
298 out.write( data );
299 completeTag();
300 }
301
302 /**
303 * SWFTagTypes interface
304 */
305 public void tagGeneratorText( byte[] data ) throws IOException
306 {
307 startTag( TAG_GENERATOR_TEXT, false );
308 out.write( data );
309 completeTag();
310 }
311
312 /**
313 * SWFTagTypes interface
314 */
315 public void tagGeneratorCommand( byte[] data ) throws IOException
316 {
317 startTag( TAG_TEMPLATECOMMAND, false );
318 out.write( data );
319 completeTag();
320 }
321
322 /**
323 * SWFTagTypes interface
324 */
325 public void tagGeneratorFont( byte[] data ) throws IOException
326 {
327 startTag( TAG_GEN_EXTERNAL_FONT, false );
328 out.write( data );
329 completeTag();
330 }
331
332 /**
333 * SWFTagTypes interface
334 */
335 public void tagNameCharacter( byte[] data ) throws IOException
336 {
337 startTag( TAG_NAMECHARACTER, false );
338 out.write( data );
339 completeTag();
340 }
341
342 /**
343 * SWFTagTypes interface
344 */
345 public void tagDefineBits( int id, byte[] imageData ) throws IOException
346 {
347 startTag( TAG_DEFINEBITS, id, true );
348 out.write( imageData );
349 completeTag();
350 }
351
352 /**
353 * SWFTagTypes interface
354 */
355 public void tagJPEGTables( byte[] jpegEncodingData ) throws IOException
356 {
357 startTag( TAG_JPEGTABLES, true );
358 out.write( jpegEncodingData );
359 completeTag();
360 }
361
362 /**
363 * SWFTagTypes interface
364 */
365 public void tagDefineBitsJPEG3( int id, byte[] imageData, byte[] alphaData ) throws IOException
366 {
367 startTag( TAG_DEFINEBITSJPEG3, id, true );
368 out.writeUI32( imageData.length );
369 out.write( imageData );
370 out.write( alphaData );
371 completeTag();
372 }
373
374 /**
375 * SWFTagTypes interface
376 */
377 public SWFActions tagDoAction() throws IOException
378 {
379 startTag( TAG_DOACTION, true );
380 return factorySWFActions();
381 }
382
383 /**
384 * SWFTagTypes interface
385 */
386 public SWFActions tagDoInitAction( int spriteId ) throws IOException {
387 startTag( TAG_DOINITACTION, spriteId, true );
388 return factorySWFActions();
389 }
390
391 /**
392 * SWFTagTypes interface
393 */
394 public SWFShape tagDefineShape( int id, Rect outline ) throws IOException
395 {
396 startShape( TAG_DEFINESHAPE, id, outline );
397 return factorySWFShape( false, true );
398 }
399
400 /**
401 * SWFTagTypes interface
402 */
403 public SWFShape tagDefineShape2( int id, Rect outline ) throws IOException
404 {
405 startShape( TAG_DEFINESHAPE2, id, outline );
406 return factorySWFShape( false, true );
407 }
408
409 /**
410 * SWFTagTypes interface
411 */
412 public SWFShape tagDefineShape3( int id, Rect outline ) throws IOException
413 {
414 startShape( TAG_DEFINESHAPE3, id, outline );
415 return factorySWFShape( true, true );
416 }
417
418 /**
419 * SWFTagTypes interface
420 */
421 public void tagFreeCharacter( int charId ) throws IOException
422 {
423 startTag( TAG_FREECHARACTER, false );
424 out.writeUI16( charId );
425 completeTag();
426 }
427
428 /**
429 * SWFTagTypes interface
430 */
431 public void tagPlaceObject( int charId, int depth,
432 Matrix matrix, AlphaTransform cxform )
433 throws IOException
434 {
435 startTag( TAG_PLACEOBJECT, false );
436 out.writeUI16( charId );
437 out.writeUI16( depth );
438 matrix.write ( out );
439 if( cxform != null ) cxform.write( out );
440 completeTag();
441 }
442
443 /**
444 * SWFTagTypes interface
445 */
446 public SWFActions tagPlaceObject2( boolean isMove,
447 int clipDepth,
448 int depth,
449 int charId,
450 Matrix matrix,
451 AlphaTransform cxform,
452 int ratio,
453 String name,
454 int clipActionFlags )
455 throws IOException
456 {
457 boolean hasClipActions = ( clipActionFlags != 0 );
458
459 startTag( TAG_PLACEOBJECT2, false );
460
461 out.writeUBits( 1, hasClipActions ? 1 : 0 );
462 out.writeUBits( 1, (clipDepth > 0) ? 1 : 0 );
463 out.writeUBits( 1, (name != null) ? 1 : 0 );
464 out.writeUBits( 1, (ratio >= 0) ? 1 : 0 );
465 out.writeUBits( 1, (cxform != null) ? 1 : 0 );
466 out.writeUBits( 1, (matrix != null) ? 1 : 0 );
467 out.writeUBits( 1, (charId > 0) ? 1 : 0 );
468 out.writeUBits( 1, isMove ? 1 : 0 );
469
470 out.writeUI16( depth );
471
472 if( charId > 0 ) out .writeUI16( charId );
473 if( matrix != null ) matrix.write ( out );
474 if( cxform != null ) cxform.write ( out );
475 if( ratio >= 0 ) out .writeUI16( ratio );
476 if( clipDepth > 0 ) out .writeUI16( clipDepth );
477
478 if( name != null )
479 {
480 out.writeString( name, mStringEncoding );
481 longTag = true;
482 }
483
484 if( hasClipActions )
485 {
486 out.writeUI16( 0 ); //unknown
487 out.writeUI16( clipActionFlags );
488
489 return new ActionWriter( this, version )
490 {
491 public void start( int conditions ) throws IOException
492 {
493 super.start( conditions );
494 tagWriter.out.writeUI16( conditions );
495 }
496
497 protected void writeBytes( byte[] bytes ) throws IOException
498 {
499 tagWriter.out.writeUI32( bytes.length );
500 super.writeBytes( bytes );
501 }
502
503 public void done() throws IOException
504 {
505 tagWriter.out.writeUI16(0);
506 super.done();
507 }
508 };
509 }
510
511 completeTag();
512 return null;
513 }
514
515 /**
516 * SWFTagTypes interface
517 */
518 public void tagRemoveObject( int charId, int depth ) throws IOException
519 {
520 startTag( TAG_REMOVEOBJECT, false );
521 out.writeUI16( charId );
522 out.writeUI16( depth );
523 completeTag();
524 }
525
526 /**
527 * SWFTagTypes interface
528 */
529 public void tagRemoveObject2(int depth ) throws IOException
530 {
531 startTag( TAG_REMOVEOBJECT2, false );
532 out.writeUI16( depth );
533 completeTag();
534 }
535
536 /**
537 * SWFTagTypes interface
538 */
539 public void tagSetBackgroundColor( Color color ) throws IOException
540 {
541 startTag( TAG_SETBACKGROUNDCOLOR, false );
542 color.writeRGB( out );
543 completeTag();
544 }
545
546 /**
547 * SWFTagTypes interface
548 */
549 public void tagFrameLabel( String label ) throws IOException
550 {
551 tagFrameLabel( label, false );
552 }
553
554 /**
555 * SWFTagTypes interface
556 */
557 public void tagFrameLabel( String label, boolean isAnchor ) throws IOException {
558 startTag( TAG_FRAMELABEL, true );
559 out.writeString( label, mStringEncoding );
560 if( isAnchor ) out.writeUI8( 1 );
561 completeTag();
562 }
563
564 /**
565 * SWFTagTypes interface
566 */
567 public SWFTagTypes tagDefineSprite( int id ) throws IOException
568 {
569 startTag( TAG_DEFINESPRITE, id, true );
570
571 out.writeUI16( 0 ); //framecount - to be filled in later
572
573 TagWriter writer = new TagWriter( new SpriteTags() );
574 writer.version = version;
575
576 return writer;
577 }
578
579 /**
580 * SWFTagTypes interface
581 */
582 public void tagProtect( byte[] password ) throws IOException
583 {
584 mTags.tag( TAG_PROTECT, false, password );
585 }
586
587 /**
588 * SWFTagTypes interface
589 */
590 public void tagEnableDebug( byte[] password ) throws IOException
591 {
592 mTags.tag( TAG_ENABLEDEBUG, false, password );
593 }
594
595 /**
596 * SWFTagTypes interface
597 */
598 public void tagEnableDebug2( byte[] password ) throws IOException
599 {
600 startTag( TAG_ENABLEDEBUGGER2, 0, false );
601 out.write( password );
602 completeTag();
603 }
604
605 /**
606 * SWFTagTypes interface
607 */
608 public SWFVectors tagDefineFont( int id, int numGlyphs ) throws IOException
609 {
610 startTag( TAG_DEFINEFONT, id, true );
611
612 return new SWFShapeImpl( this, numGlyphs );
613 }
614
615 /**
616 * SWFTagTypes interface
617 */
618 public void tagDefineFontInfo( int fontId, String fontName, int flags, int[] codes )
619 throws IOException
620 {
621 startTag( TAG_DEFINEFONTINFO, true );
622 out.writeUI16( fontId );
623
624 byte[] chars = fontName.getBytes();
625 out.writeUI8( chars.length );
626 out.write( chars );
627
628 out.writeUI8( flags );
629
630 boolean wide = (flags & FONT_WIDECHARS) != 0;
631
632 for( int i = 0; i < codes.length; i++ )
633 {
634 if( wide ) out.writeUI16( codes[i] );
635 else out.writeUI8 ( codes[i] );
636 }
637
638 completeTag();
639 }
640
641 /**
642 * SWFTagTypes interface
643 */
644 public void tagDefineFontInfo2( int fontId, String fontName, int flags, int[] codes, int languageCode )
645 throws IOException
646 {
647 startTag( TAG_DEFINEFONTINFO2, true );
648 out.writeUI16( fontId );
649
650 byte[] chars = fontName.getBytes();
651 out.writeUI8( chars.length );
652 out.write( chars );
653
654 flags |= SWFConstants.FONT_WIDECHARS; //always wide
655 out.writeUI8( flags );
656 out.writeUI8( languageCode );
657
658 for( int i = 0; i < codes.length; i++ )
659 {
660 out.writeUI16( codes[i] );
661 }
662
663 completeTag();
664 }
665
666 /**
667 * SWFTagTypes interface
668 */
669 public SWFVectors tagDefineFont2( int id, int flags, String name, int numGlyphs,
670 int ascent, int descent, int leading,
671 int[] codes, int[] advances, Rect[] bounds,
672 int[] kernCodes1, int[] kernCodes2,
673 int[] kernAdjustments ) throws IOException
674 {
675 startTag( TAG_DEFINEFONT2, id, true );
676
677 out.writeUI8( flags );
678 out.writeUI8( 0 ); //reserved flags
679
680 byte[] nameBytes = name.getBytes();
681 out.writeUI8 ( nameBytes.length );
682 out.write ( nameBytes );
683 out.writeUI16( numGlyphs );
684
685 return new Font2ShapeImpl( this, flags, numGlyphs, ascent, descent, leading,
686 codes, advances, bounds,
687 kernCodes1, kernCodes2, kernAdjustments );
688 }
689
690 /**
691 * SWFTagTypes interface
692 */
693 public void tagDefineTextField( int fieldId, String fieldName,
694 String initialText, Rect boundary, int flags,
695 AlphaColor textColor, int alignment, int fontId, int fontSize,
696 int charLimit, int leftMargin, int rightMargin, int indentation,
697 int lineSpacing )
698 throws IOException
699 {
700 flags |= 0x2005;
701
702 startTag( TAG_DEFINETEXTFIELD, fieldId, true );
703
704 boundary.write( out );
705
706 out.writeUI16( flags );
707 out.writeUI16( fontId );
708 out.writeUI16( fontSize );
709 textColor.write( out );
710
711 if( (flags & TEXTFIELD_LIMIT_CHARS ) != 0 )
712 {
713 out.writeUI16( charLimit );
714 }
715
716 out.writeUI8 ( alignment );
717 out.writeUI16( leftMargin );
718 out.writeUI16( rightMargin );
719 out.writeUI16( indentation );
720 out.writeUI16( lineSpacing );
721
722 out.writeString( fieldName, mStringEncoding );
723
724 if( (flags & TEXTFIELD_HAS_TEXT ) != 0 )
725 {
726 out.writeString( initialText, mStringEncoding );
727 }
728
729 completeTag();
730 }
731
732 /**
733 * SWFTagTypes interface
734 */
735 public SWFText tagDefineText( int id, Rect bounds, Matrix matrix )
736 throws IOException
737 {
738 startTag( TAG_DEFINETEXT, id, true );
739 return defineText( bounds, matrix, false );
740 }
741
742 /**
743 * SWFTagTypes interface
744 */
745 public SWFText tagDefineText2( int id, Rect bounds, Matrix matrix ) throws IOException
746 {
747 startTag( TAG_DEFINETEXT2, id, true );
748 return defineText( bounds, matrix, true );
749 }
750
751 /**
752 * SWFTagTypes interface
753 */
754 public SWFActions tagDefineButton( int id, Vector buttonRecords )
755 throws IOException
756 {
757 startTag( TAG_DEFINEBUTTON, id, true );
758
759 ButtonRecord.write( out, buttonRecords );
760 System.out.println( "BUTTON" );
761 return new ActionWriter( this, version );
762 }
763
764 /**
765 * SWFTagTypes interface
766 */
767 public void tagButtonCXForm( int buttonId, ColorTransform transform )
768 throws IOException
769 {
770 startTag( TAG_DEFINEBUTTONCXFORM, buttonId, false );
771 transform.writeWithoutAlpha( out );
772 completeTag();
773 }
774
775 /**
776 * SWFTagTypes interface
777 */
778 public SWFActions tagDefineButton2( int id,
779 boolean trackAsMenu,
780 Vector buttonRecord2s )
781 throws IOException
782 {
783 startTag( TAG_DEFINEBUTTON2, id, true );
784 out.writeUI8( trackAsMenu ? 1 : 0 );
785
786 return new ButtonActionWriter( this, version, buttonRecord2s );
787 }
788
789 /**
790 * SWFTagTypes interface
791 */
792 public void tagExport( String[] names, int[] ids ) throws IOException
793 {
794 startTag( TAG_EXPORT, true );
795
796 int count = ids.length;
797
798 out.writeUI16( count );
799
800 for( int i = 0; i < count; i++ )
801 {
802 //System.out.println( "Exporting " + ids[i] + " as " + names[i] );
803 out.writeUI16 ( ids [i] );
804 out.writeString( names[i], mStringEncoding );
805 }
806
807 completeTag();
808 }
809
810 /**
811 * SWFTagTypes interface
812 */
813 public void tagImport( String movieName, String[] names, int[] ids )
814 throws IOException
815 {
816 startTag( TAG_IMPORT, true );
817
818 int count = ids.length;
819
820 out.writeString( movieName, mStringEncoding );
821 out.writeUI16 ( count );
822
823 for( int i = 0; i < count; i++ )
824 {
825 //System.out.println( "Importing " + names[i] + " as " + ids[i] + " from " + movieName );
826 out.writeUI16 ( ids [i] );
827 out.writeString( names[i], mStringEncoding );
828 }
829
830 completeTag();
831 }
832
833 /**
834 * SWFTagTypes interface
835 */
836 public void tagDefineQuickTimeMovie( int id, String filename ) throws IOException
837 {
838 startTag( TAG_DEFINEQUICKTIMEMOVIE, id, true );
839 out.writeString( filename, mStringEncoding );
840 completeTag();
841 }
842
843 /**
844 * SWFTagTypes interface
845 */
846 public void tagDefineBitsJPEG2( int id, byte[] data ) throws IOException
847 {
848 startTag( TAG_DEFINEBITSJPEG2, id, true );
849 out.write( data );
850 completeTag();
851 }
852 /**
853 * SWFTagTypes interface
854 */
855 public void tagDefineBitsLossless( int id, int format, int width, int height,
856 Color[] colors, byte[] imageData )
857 throws IOException
858 {
859 writeBitsLossless( id, format, width, height, colors, imageData, false );
860 }
861
862 /**
863 * SWFTagTypes interface
864 */
865 public void tagDefineBitsLossless2( int id, int format, int width, int height,
866 Color[] colors, byte[] imageData )
867 throws IOException
868 {
869 writeBitsLossless( id, format, width, height, colors, imageData, true );
870 }
871
872
873 public void writeBitsLossless( int id, int format, int width, int height,
874 Color[] colors, byte[] imageData, boolean hasAlpha )
875 throws IOException
876 {
877 startTag( hasAlpha ? TAG_DEFINEBITSLOSSLESS2 : TAG_DEFINEBITSLOSSLESS,
878 id, true );
879
880 out.writeUI8 ( format );
881 out.writeUI16( width );
882 out.writeUI16( height );
883
884 switch( format )
885 {
886 case BITMAP_FORMAT_8_BIT: out.writeUI8 ( colors.length - 1 ); break;
887 case BITMAP_FORMAT_16_BIT: out.writeUI16( colors.length - 1 ); break;
888 case BITMAP_FORMAT_32_BIT: break;
889 default: throw new IOException( "unknown bitmap format: " + format );
890 }
891
892 //--zip up the colors and the bitmap data
893 DeflaterOutputStream deflater = new DeflaterOutputStream( bytes );
894 OutStream zipOut = new OutStream( deflater );
895
896 if( format == BITMAP_FORMAT_8_BIT || format == BITMAP_FORMAT_16_BIT )
897 {
898 for( int i = 0; i < colors.length; i++ )
899 {
900 if( hasAlpha ) colors[i].writeWithAlpha( zipOut );
901 else colors[i].writeRGB( zipOut );
902 }
903 }
904 zipOut.write( imageData );
905 zipOut.flush();
906 deflater.finish();
907
908 completeTag();
909 }
910
911 /**
912 * SWFTagTypes interface
913 */
914 public void tagDefineBitsJPEG2( int id, InputStream jpegImage ) throws IOException
915 {
916 startTag( TAG_DEFINEBITSJPEG2, id, true );
917
918 //--Write stream terminator/header
919 out.writeUI8( 0xff );
920 out.writeUI8( 0xd9 );
921 out.writeUI8( 0xff );
922 out.writeUI8( 0xd8 );
923
924 int read = 0;
925 byte[] bytes = new byte[10000];
926
927 while( (read = jpegImage.read( bytes )) >= 0 )
928 {
929 out.write( bytes, 0, read );
930 }
931
932 jpegImage.close();
933 completeTag();
934 }
935
936 /**
937 * SWFTagTypes interface
938 */
939 public SWFShape tagDefineMorphShape( int id, Rect startBounds, Rect endBounds )
940 throws IOException
941 {
942 startTag( TAG_DEFINEMORPHSHAPE, id, true );
943 startBounds.write( out );
944 endBounds .write( out );
945 return new MorphShapeImpl( this );
946 }
947
948 //-----------------------------------------------------------------------
949
950
951 protected static class ButtonActionWriter extends ActionWriter
952 {
953 //offsets is a vector of int[]{ offsetPtr, offsetValue }
954 protected Vector offsets = new Vector();
955 protected int lastPtr;
956 protected OutStream tagout;
957
958 public ButtonActionWriter( TagWriter tagWriter,
959 int flashVersion,
960 Vector buttonRecs )
961 throws IOException
962 {
963 super( tagWriter, flashVersion );
964
965 tagout = tagWriter.getOutStream();
966
967 //--save ptr to first offset location
968 lastPtr = (int)tagout.getCount();
969
970 tagout.writeUI16( 0 ); //will be calculated later
971
972 //--write the button records
973 ButtonRecord2.write( tagout, buttonRecs );
974 }
975
976 public void start( int conditions ) throws IOException
977 {
978 super.start( conditions );
979
980 //--save ptr to offset location and offset value
981 int ptr = (int)tagout.getCount();
982 int offset = ptr - lastPtr;
983
984 offsets.addElement( new int[] { lastPtr, offset } );
985
986 lastPtr = ptr;
987
988 tagout.writeUI16( 0 ); //will be calculated later
989 tagout.writeUI16( conditions );
990 }
991
992 public void done() throws IOException
993 {
994 //--save last offset
995 offsets.addElement( new int[] { lastPtr, 0 } );
996
997 tagout.flush();
998 byte[] contents = tagWriter.bytes.toByteArray();
999
1000 tagWriter.out = null;
1001 tagWriter.bytes = null;
1002
1003 //--fix the offsets
1004 for( Enumeration enum = offsets.elements(); enum.hasMoreElements(); )
1005 {
1006 int[] offInfo = (int[])enum.nextElement();
1007 int ptr = offInfo[0];
1008 int off = offInfo[1];
1009
1010 byte[] offbytes = OutStream.uintTo2Bytes( off );
1011 contents[ ptr ] = offbytes[0];
1012 contents[ ptr + 1 ] = offbytes[1];
1013 }
1014
1015 tagWriter.mTags.tag( tagWriter.tagType, true, contents );
1016 }
1017 }
1018
1019 protected SWFText defineText( Rect bounds, Matrix matrix, boolean hasAlpha )
1020 throws IOException
1021 {
1022 bounds.write( out );
1023 matrix.write( out );
1024
1025 return new SWFTextImpl( hasAlpha );
1026 }
1027
1028 protected class SWFTextImpl implements SWFText
1029 {
1030 protected boolean hasAlpha;
1031
1032 protected int maxGlyphIndex = 0;
1033 protected int maxAdvance = 0;
1034
1035 // Text records are stored as
1036 // Object[] { int[]{ font, height }, int[]{ x }, int[]{ y }, Color }, or
1037 // Object[] { int[] glyphs, int[] advances }
1038 protected Vector recs = new Vector();
1039 protected Object[] currentStyleRecord = null;
1040
1041
1042 public SWFTextImpl( boolean hasAlpha )
1043 {
1044 this.hasAlpha = hasAlpha;
1045 }
1046
1047 protected Object[] getCurrentStyle()
1048 {
1049 if( currentStyleRecord == null )
1050 {
1051 currentStyleRecord = new Object[4];
1052 recs.addElement( currentStyleRecord );
1053 }
1054
1055 return currentStyleRecord;
1056 }
1057
1058 /**
1059 * SWFText interface
1060 */
1061 public void font( int fontId, int textHeight ) throws IOException
1062 {
1063 getCurrentStyle()[0] = new int[] { fontId, textHeight };
1064 }
1065
1066 /**
1067 * SWFText interface
1068 */
1069 public void color( Color color ) throws IOException
1070 {
1071 getCurrentStyle()[3] = color;
1072 }
1073
1074 /**
1075 * SWFText interface
1076 */
1077 public void setX( int x ) throws IOException
1078 {
1079 getCurrentStyle()[1] = new int[] { x };
1080 }
1081
1082 /**
1083 * SWFText interface
1084 */
1085 public void setY( int y ) throws IOException
1086 {
1087 getCurrentStyle()[2] = new int[] { y };
1088 }
1089
1090 /**
1091 * SWFText interface
1092 */
1093 public void text( int[] glyphIndices, int[] glyphAdvances ) throws IOException
1094 {
1095 currentStyleRecord = null;
1096 recs.addElement( new Object[] { glyphIndices, glyphAdvances } );
1097
1098 for( int i = 0; i < glyphIndices.length; i++ )
1099 {
1100 if( glyphIndices[i] > maxGlyphIndex ) maxGlyphIndex = glyphIndices[i];
1101 if( glyphAdvances[i] > maxAdvance ) maxAdvance = glyphAdvances[i];
1102 }
1103 }
1104
1105 /**
1106 * SWFText interface
1107 */
1108 public void done() throws IOException
1109 {
1110 int glyphBits = OutStream.determineUnsignedBitSize( maxGlyphIndex );
1111 int advanceBits = OutStream.determineSignedBitSize ( maxAdvance );
1112
1113 out.writeUI8( glyphBits );
1114 out.writeUI8( advanceBits );
1115
1116 for( Enumeration enum = recs.elements(); enum.hasMoreElements(); )
1117 {
1118 Object[] rec = (Object[])enum.nextElement();
1119
1120 if( rec.length == 4 ) //style record
1121 {
1122 boolean hasFont = rec[0] != null;
1123 boolean hasX = rec[1] != null;
1124 boolean hasY = rec[2] != null;
1125 boolean hasColor = rec[3] != null;
1126
1127 int flags = 0x80;
1128 if( hasFont ) flags |= TEXT_HAS_FONT;
1129 if( hasX ) flags |= TEXT_HAS_XOFFSET;
1130 if( hasY ) flags |= TEXT_HAS_YOFFSET;
1131 if( hasColor ) flags |= TEXT_HAS_COLOR;
1132
1133 out.writeUI8( flags );
1134
1135 if( hasFont )
1136 {
1137 out.writeUI16( ((int[])rec[0])[0] ); //fontId
1138 }
1139
1140 if( hasColor )
1141 {
1142 Color color = (Color)rec[3];
1143
1144 if( hasAlpha ) color.writeWithAlpha( out );
1145 else color.writeRGB( out );
1146 }
1147
1148 if( hasX )
1149 {
1150 int xOffset = ((int[])rec[1])[0];
1151 out.writeSI16( (short)xOffset );
1152 }
1153
1154 if( hasY ) //x & y are in reverse order from flag bits
1155 {
1156 int yOffset = ((int[])rec[2])[0];
1157 out.writeSI16( (short)yOffset );
1158 }
1159
1160 if( hasFont )
1161 {
1162 out.writeUI16( ((int[])rec[0])[1] ); //textHeight
1163 }
1164 }
1165 else //glyph record
1166 {
1167 int[] glyphs = (int[])rec[0];
1168 int[] advances = (int[])rec[1];
1169
1170 out.writeUI8( glyphs.length );
1171
1172 for( int i = 0; i < glyphs.length; i++ )
1173 {
1174 out.writeUBits( glyphBits, glyphs[i] );
1175 out.writeSBits( advanceBits, advances[i] );
1176 }
1177 }
1178 }
1179
1180 out.writeUI8( 0 ); //record terminator
1181
1182 completeTag();
1183 }
1184 }
1185
1186
1187 protected class SpriteTags implements SWFTags
1188 {
1189 protected int frameCount = 0;
1190
1191 public void tag( int tagType2, boolean longTag2,
1192 byte[] contents ) throws IOException
1193 {
1194 int length = (contents != null ) ? contents.length : 0;
1195 longTag2 = ( length > 62 ) || longTag2;
1196
1197 int hdr = ( tagType2 << 6 ) + ( longTag2 ? 0x3f : length );
1198
1199 out.writeUI16( hdr );
1200
1201 if( longTag2 ) out.writeUI32( length );
1202
1203 if( contents != null ) out.write( contents );
1204
1205 if( tagType2 == SWFConstants.TAG_SHOWFRAME ) frameCount++;
1206
1207 if( tagType2 == SWFConstants.TAG_END )
1208 {
1209 out.flush();
1210 contents = bytes.toByteArray();
1211
1212 out = null;
1213 bytes = null;
1214
1215 byte[] fc = OutStream.uintTo2Bytes( frameCount );
1216 contents[2] = fc[0];
1217 contents[3] = fc[1];
1218
1219 mTags.tag( tagType, longTag, contents );
1220 }
1221 }
1222
1223 public void header( int version, long length,
1224 int twipsWidth, int twipsHeight,
1225 int frameRate, int frameCount ) throws IOException
1226 {
1227 }
1228 }
1229
1230 protected void startShape( int tagType, int id, Rect outline ) throws IOException
1231 {
1232 startTag( tagType, id, true );
1233 outline.write( out );
1234 }
1235
1236 /**
1237 * Implementation of the SWFShape interface
1238 */
1239 protected static class SWFShapeImpl implements SWFShape
1240 {
1241 protected boolean hasAlpha;
1242 protected boolean hasStyle;
1243 protected boolean outstandingChanges = true;
1244 protected boolean initialStyles = false;
1245
1246 protected int fill0Index = -1;
1247 protected int fill1Index = -1;
1248 protected int lineIndex = -1;
1249
1250 protected int[] moveXY;
1251 protected Vector lineStyles = new Vector();
1252 protected Vector fillStyles = new Vector();
1253
1254 protected int fillBits;
1255 protected int lineBits;
1256
1257 protected int glyphCount = 0;
1258 protected Vector glyphByteArrays;
1259
1260 protected OutStream out;
1261 protected TagWriter writer;
1262 protected ByteArrayOutputStream bout;
1263
1264 /**
1265 * For shapes (other than glyphs)
1266 */
1267 public SWFShapeImpl( TagWriter writer, boolean hasAlpha, boolean hasStyle )
1268 {
1269 this.hasAlpha = hasAlpha;
1270 this.hasStyle = hasStyle;
1271 this.writer = writer;
1272 out = writer.getOutStream();
1273 }
1274
1275 /**
1276 * For glyphs
1277 */
1278 public SWFShapeImpl( TagWriter writer, int glyphCount )
1279 {
1280 this( writer, false, false );
1281 this.glyphCount = glyphCount;
1282 bout = new ByteArrayOutputStream();
1283 out = new OutStream( bout );
1284 glyphByteArrays = new Vector();
1285
1286 fill1Index = 1;
1287 lineIndex = 0;
1288 }
1289
1290
1291 public void done() throws IOException
1292 {
1293 if( ! initialStyles )
1294 {
1295 writeInitialStyles();
1296 initialStyles = true;
1297 }
1298
1299 out.writeUBits( 6, 0 ); //end record
1300 out.flushBits();
1301
1302 if( bout != null && glyphCount > 0 ) //capturing bytes internally
1303 {
1304 byte[] glyphBytes = bout.toByteArray();
1305 glyphByteArrays.addElement( glyphBytes );
1306 }
1307
1308 if( glyphCount > 1 )
1309 {
1310 bout = new ByteArrayOutputStream();
1311 out = new OutStream( bout );
1312 glyphCount--;
1313
1314 fill1Index = 1;
1315 lineIndex = 0;
1316 outstandingChanges = true;
1317 initialStyles = false;
1318 }
1319 else
1320 {
1321 if( bout != null ) finishFont();
1322 writer.completeTag();
1323 }
1324 }
1325
1326 protected void finishFont() throws IOException
1327 {
1328 out = writer.getOutStream();
1329
1330 int glyphCount = glyphByteArrays.size();
1331
1332 //--Write first shape offset
1333 int offset = glyphCount * 2;
1334 out.writeUI16( offset );
1335
1336 //--Write subsequent shape offsets
1337 for( int i = 0; i < glyphCount - 1; i++ )
1338 {
1339 offset += ((byte[])glyphByteArrays.elementAt(i)).length;
1340 out.writeUI16( offset );
1341 }
1342
1343 //--Write shapes
1344 for( int i = 0; i < glyphCount; i++ )
1345 {
1346 out.write( (byte[])glyphByteArrays.elementAt(i) );
1347 }
1348 }
1349
1350 public void setFillStyle0( int styleIndex ) throws IOException
1351 {
1352 fill0Index = styleIndex;
1353 outstandingChanges = true;
1354 }
1355
1356 public void setFillStyle1( int styleIndex ) throws IOException
1357 {
1358 fill1Index = styleIndex;
1359 outstandingChanges = true;
1360 }
1361
1362 public void setLineStyle( int styleIndex ) throws IOException
1363 {
1364 lineIndex = styleIndex;
1365 outstandingChanges = true;
1366 }
1367
1368 public void defineFillStyle( Color color ) throws IOException
1369 {
1370 fillStyles.addElement( new FillStyle( color ) );
1371 outstandingChanges = true;
1372 }
1373
1374 public void defineFillStyle( Matrix matrix, int[] ratios,
1375 Color[] colors, boolean radial )
1376 throws IOException
1377 {
1378 fillStyles.addElement( new FillStyle( matrix, ratios, colors, radial ) );
1379 outstandingChanges = true;
1380 }
1381
1382 public void defineFillStyle( int bitmapId, Matrix matrix, boolean clipped )
1383 throws IOException
1384 {
1385 fillStyles.addElement( new FillStyle( bitmapId, matrix, clipped ) );
1386 outstandingChanges = true;
1387 }
1388
1389 public void defineLineStyle( int width, Color color ) throws IOException
1390 {
1391 lineStyles.addElement( new LineStyle( width, color ) );
1392 outstandingChanges = true;
1393 }
1394
1395 public void line( int deltaX, int deltaY ) throws IOException
1396 {
1397 if( outstandingChanges ) flushChangeRecords();
1398
1399 int numBits = OutStream.determineSignedBitSize( deltaX );
1400 int dyBits = OutStream.determineSignedBitSize( deltaY );
1401
1402 if( dyBits > numBits ) numBits = dyBits;
1403 if( numBits < 2 ) numBits = 2;
1404
1405 out.writeUBits( 2, 3 ); //11b = line record
1406
1407 out.writeUBits( 4, numBits - 2 );
1408
1409 if( deltaX !=