—package
SWF::Builder;
use
strict;
use
SWF::File;
use
SWF::Element;
use
Carp;
our
$VERSION
=
'0.12'
;
my
$SFTAG
= SWF::Element::Tag::ShowFrame->new;
sub
new {
my
$class
=
shift
;
my
$self
=
bless
{
_root
=>
SWF::Builder::Movie::Root->new(
@_
)
},
$class
;
$self
;
}
sub
AUTOLOAD {
my
$self
=
shift
;
my
$sub
=
$SWF::Builder::AUTOLOAD
;
return
if
$sub
=~/::DESTROY$/;
$sub
=~ s/.+:://;
croak
"Can't locate object method \"$sub\" via package \""
.
ref
(
$self
).'
" (perhaps you forgot to load "
'.ref($self).'
"?)'
unless
$self
->{_root}->can(
$sub
);
$self
->{_root}->
$sub
(
@_
);
}
sub
DESTROY {
my
$r
=
shift
->{_root};
$r
->_destroy
if
defined
$r
;
}
####
package
SWF::Builder::Depth;
use
Carp;
sub
new {
my
(
$class
,
$parent
,
$lower
) =
@_
;
my
$self
=
bless
{
_parent
=>
$parent
,
_depth
=> SWF::Element::Depth->new,
},
$class
;
$lower
||=
$self
;
$self
->{_lower} =
$lower
;
$self
->{_upper} =
$lower
->{_upper}||
$lower
;
$lower
->{_upper} =
$lower
->{_upper}{_lower} =
$self
;
return
$self
;
}
sub
set_depth {
my
(
$self
,
$n
) =
@_
;
$self
->{_depth}->configure(
$n
);
$n
+1;
}
sub
_destroy {
my
$self
=
shift
;
while
(
my
$lower
=
$self
->{_lower}) {
%$self
= ();
$self
=
$lower
;
}
}
####
package
SWF::Builder::_FrameList;
@SWF::Builder::_FrameList::ISA
=
qw/ SWF::Element::Array::TAGARRAY /
;
sub
pack
{
my
(
$self
,
$stream
) =
@_
;
for
my
$frame
(
@$self
) {
for
my
$tag
(
@$frame
) {
$tag
->
pack
(
$stream
);
}
$SFTAG
->
pack
(
$stream
);
}
SWF::Element::Tag::End->new->
pack
(
$stream
);
}
####
package
SWF::Builder::Movie;
use
Carp;
sub
new {
my
$class
=
shift
;
my
$self
=
bless
{
_frame_list
=> SWF::Builder::_FrameList->new,
_streamsoundf
=> 0,
},
$class
;
$self
->{_depth_list} = SWF::Builder::Depth->new(
$self
);
$self
;
}
sub
new_shape {
shift
->_new_character(SWF::Builder::Character::Shape::Def->new);
}
sub
new_static_text {
shift
->_new_character(SWF::Builder::Character::Text::Def->new(
@_
));
}
*new_text
= \
&new_static_text
;
sub
new_dynamic_text {
my
$s
=
shift
;
my
$q
= SWF::Builder::Character::DynamicText->new(
@_
);
$s
->_new_character(
$q
);
}
sub
new_edit_text {
shift
->_new_character(SWF::Builder::Character::EditText::Def->new(
@_
));
}
sub
new_html_text {
shift
->_new_character(SWF::Builder::Character::HTMLText->new(
@_
));
}
sub
new_text_area {
shift
->_new_character(SWF::Builder::Character::TextArea->new(
@_
));
}
sub
new_input_field {
shift
->_new_character(SWF::Builder::Character::InputField->new(
@_
));
}
sub
new_password_field {
shift
->_new_character(SWF::Builder::Character::PasswordField->new(
@_
));
}
sub
new_font {
shift
->_new_character(SWF::Builder::Character::Font::Def->new(
@_
));
}
sub
new_movie_clip {
shift
->_new_character(SWF::Builder::Character::MovieClip::Def->new);
}
*new_mc
= \
&new_movie_clip
;
sub
new_gradient {
SWF::Builder::Gradient->new;
}
sub
new_jpeg {
my
$self
=
shift
;
unshift
@_
,
'JPEGFile'
if
@_
==1;
$self
->_new_character(SWF::Builder::Character::Bitmap::JPEG->new(
@_
));
}
sub
new_bitmap {
my
$self
=
shift
;
$self
->_new_character(SWF::Builder::Character::Bitmap::Lossless->new(
@_
));
}
sub
new_sound {
my
$self
=
shift
;
$self
->_new_character(SWF::Builder::Character::Sound::Def->new(
@_
));
}
sub
import_asset {
my
$self
=
shift
;
my
$i
=
$self
->_new_character(SWF::Builder::Character::Imported->new(
@_
));
$self
->_depends(
$i
, 1);
# set 'depend' flag by force because imported assets may be used only by ActionScript.
$i
;
}
#sub shape_tween {
# require SWF::Builder::Character::MorphShape;
#
# my $self = shift;
#
# $self->_new_character(SWF::Builder::Character::MorphShape->shape_tween(@_));
#}
sub
_new_character {
my
(
$parent
,
$self
) =
@_
;
push
@{
$parent
->{_root}{_character_IDs}},
$self
->{ID};
push
@{
$parent
->{_root}{_to_destroy}},
$self
;
$self
->{_parent} =
$parent
;
$self
->{_root} =
$parent
->{_root};
return
$self
;
}
sub
frame_action {
my
(
$self
,
$frame
) =
@_
;
my
$tag
= SWF::Element::Tag::DoAction->new;
push
@{
$self
->{_frame_list}[
$frame
-1]},
$tag
;
my
$action
= SWF::Builder::ActionScript->new(
Version
=>
$self
->{_root}{_version});
$tag
->Actions(
$action
->{_actions});
return
$action
;
}
sub
frame_label {
my
(
$self
,
$frame
,
$label
,
$anchor
) =
@_
;
utf2bin(
$label
);
push
@{
$self
->{_frame_list}[
$frame
-1]}, SWF::Element::Tag::FrameLabel->new(
Name
=>
$label
,
NamedAnchorFlag
=>
$anchor
);
}
sub
set_depth {
my
$self
=
shift
;
my
$n
= 1;
my
$depth
=
$self
->{_depth_list}{_upper};
while
(
$depth
!=
$self
->{_depth_list}) {
$n
=
$depth
->set_depth(
$n
);
$depth
=
$depth
->{_upper};
}
}
sub
_destroy {
my
$self
=
shift
;
$self
->{_depth_list}->_destroy;
%$self
= ();
}
####
package
SWF::Builder::Movie::Root;
use
Carp;
sub
new {
my
$class
=
shift
;
my
%param
=
@_
;
my
$version
=
$param
{Version} || 6;
my
$self
=
$class
->SUPER::new;
$self
->{_file} = SWF::File->new
(
undef
,
Version
=>
$version
,
FrameRate
=>
$param
{FrameRate},
FrameSize
=> [
map
{
$_
*20} @{
$param
{FrameSize}}],
);
$self
->{_backgroundcolor} =
$param
{BackgroundColor};
$self
->{_root} =
$self
;
$self
->{_character_IDs} = [];
$self
->{_ID_seed} = 1;
$self
->{_target_path} =
'_root'
;
$self
->{_to_destroy} = [];
$self
->{_version} =
$version
;
$self
->{_as_namespace} = {};
$self
->{_init_action} =
undef
;
$self
->{_auto_namer} = 1;
$self
->_init_is_alpha;
$self
;
}
sub
pack
{
my
(
$self
,
$stream
) =
@_
;
for
my
$id
(@{
$self
->{_character_IDs}}) {
$id
->configure(
undef
);
}
$self
->set_depth;
$self
->{_frame_list}->
pack
(
$stream
);
}
our
$EMPTY_SPRITE
= SWF::Element::Tag::DefineSprite->new(
ControlTags
=>[SWF::Element::Tag::ShowFrame->new]);
sub
save {
my
(
$self
,
$file
) =
@_
;
my
$stream
=
$self
->{_file};
$self
->{_is_alpha}->configure(0);
SWF::Element::Tag::SetBackgroundColor->new(
BackgroundColor
=>
$self
->_add_color(
$self
->{_backgroundcolor}))->
pack
(
$stream
);
$self
->{_ID_seed} = 1;
if
(
keys
%{
$self
->{_as_namespace}}) {
my
$action
= SWF::Builder::ActionScript->new(
Version
=>
$self
->{_version});
$action
->compile(_create_namespace_initializer(
''
,
'_global'
,
$self
->{_as_namespace}));
# SWF::Element::Tag::DefineSprite->new(SpriteID => $self->{_ID_seed}, ControlTags=>[SWF::Element::Tag::ShowFrame->new])->pack($stream);
SWF::Element::Tag::DefineSprite->new(
SpriteID
=>
$self
->{_ID_seed},
ControlTags
=>[SWF::Element::Tag::End->new])->
pack
(
$stream
);
SWF::Element::Tag::DoInitAction->new(
SpriteID
=>
$self
->{_ID_seed}++,
Actions
=>
$action
->{_actions})->
pack
(
$stream
);
}
if
(
$self
->{_init_action}) {
# SWF::Element::Tag::DefineSprite->new(SpriteID => $self->{_ID_seed}, ControlTags=>[SWF::Element::Tag::ShowFrame->new])->pack($stream);
SWF::Element::Tag::DefineSprite->new(
SpriteID
=>
$self
->{_ID_seed},
ControlTags
=>[SWF::Element::Tag::End->new])->
pack
(
$stream
);
SWF::Element::Tag::DoInitAction->new(
SpriteID
=>
$self
->{_ID_seed}++,
Actions
=>
$self
->{_init_action}{_actions})->
pack
(
$stream
);
}
$self
->
pack
(
$stream
);
$stream
->
close
(
$file
);
}
sub
_create_namespace_initializer {
my
(
$as
,
$pname
,
$namehash
) =
@_
;
for
my
$name
(
keys
%$namehash
) {
my
$n
=
"$pname.$name"
;
$as
.=
<<ASEND;
//#
if (eval('$n') == undefined) {
set('$n', new Object());
}
ASEND
#//
$as
= _create_namespace_initializer(
$as
,
$n
,
$namehash
->{
$name
});
}
return
$as
;
}
sub
use_namespace {
my
$self
=
shift
;
while
(
my
$name
=
shift
) {
my
$ns
=
$self
->{_as_namespace};
my
@n
=
split
/\./,
$name
;
for
my
$n
(
@n
) {
$ns
->{
$n
} ||= {};
$ns
=
$ns
->{
$n
};
}
}
}
sub
init_action {
my
$self
=
shift
;
$self
->{_init_action} ||= SWF::Builder::ActionScript->new(
Version
=>
$self
->{_version});
}
sub
auto_namer {
shift
->{_auto_namer} = 1;
}
sub
no_namer {
shift
->{_auto_namer} = 0;
}
sub
_depends {
my
(
$self
,
$char
,
$frame
) =
@_
;
push
@{
$self
->{_frame_list}[
$frame
-1]},
$char
;
}
sub
FrameRate {
my
$self
=
shift
;
$self
->{_file}->FrameRate(
@_
);
}
sub
FrameSize {
my
$self
=
shift
;
$self
->{_file}->FrameSize(
map
{
$_
*20}
@_
);
}
sub
BackgroundColor {
my
(
$self
,
$color
) =
@_
;
$self
->{_backgroundcolor} =
$color
if
defined
$color
;
$self
->{_backgroundcolor};
}
sub
compress {
my
$self
=
shift
;
$self
->{_file}->compress(
@_
);
}
sub
_destroy {
my
$self
=
shift
;
undef
$self
->{_root};
for
(@{
$self
->{_to_destroy}}) {
$_
->_destroy;
}
$self
->SUPER::_destroy;
}
# Autoload methods go after =cut, and are processed by the autosplit program.
1;
__END__
# Below is the stub of documentation for your module. You better edit it!
=head1 NAME
SWF::Builder - Create SWF movie.
=head1 SYNOPSIS
use SWF::Builder;
my $movie = SWF::Builder->new
( FrameRate => 15,
FrameSize => [0, 0, 400, 400],
BackgroundColor => 'ffffff'
);
my $shape = $movie->new_shape # red triangle.
->fillstyle('ff0000')
->linestyle(1, '000000')
->moveto(0,-11)
->lineto(10,6)
->lineto(-10,6)
->lineto(0,-11);
my $instance = $shape->place;
for (my $x = 0; $x < 400; $x++) {
$instance->rotate(15)->moveto($x,200);
}
$movie->save('triangle.swf');
=head1 DESCRIPTION
I<SWF::Builder> is a wrapper of I<SWF::File>.
It provides an easy way to create SWF6 movie.
The SWF movie consists a dictionary of character definitions and
a hierarchical group of movie clips. You create a movie by following steps:
=over 4
=item 1.
create a '_root' movie by SWF::Builder->new.
=item 2.
define characters such as shapes, fonts, texts, movieclips, and so on,
by $movie->new_XXX methods.
=item 3.
get a display instance of the character by $char->place.
=item 4.
move, scale, and rotate the instance every frame.
=item 5.
repeat 2-4 if you need.
=item 6.
save the whole movie by $movie->save.
=back
=head2 '_root' movie
The '_root' movie is a top of the movie clip hierarchy.
It has properties of the whole SWF movie. It also has character constructors
and other methods for movie. See the next section for details.
=over 4
=item $movie = SWF::Builder->new( [FrameRate => $rate, FrameSize => [$xmin, $ymin, $xmax, $ymax], BackgroundColor => $color, Version => $version] )
creates a new '_root' movie. It can take three optional named parameters.
FrameRate is a frame count per second. FrameSize is a box size of frames,
which is an array reference of the coordinates of top-left and bottom-right
of the box in pixels.
BackgroundColor is a background color of the movie. It can take a six-figure
hexadecimal string, an array reference of R, G, and B value, an array reference
of named parameters such as [Red => 255], and SWF::Element::RGB object.
Version is a version number of the SWF. It must be 6 and above.
=item $movie->FrameRate( $rate )
=item $movie->FrameSize( $xmin, $ymin, $xmax, $ymax )
=item $movie->BackgroundColor( $color )
sets the property. See SWF::Builder->new.
=item $movie->use_namespace( $namespace )
prepares ActionScript namespace. For example, $movie->use_namespace('SWF.Builder')
creates movieclip which is initialized by
if (_global.SWF == undefined) {
_global.SWF = new Object();
}
if (_global.SWF.Builder == undefined) {
_global.SWF.Builder = new Object();
}
and place the movieclip on the first frame of the root movie.
=item $movie->init_action
returns SWF::Builder::ActionScript object to initialize the root movie.
=item $movie->no_namer
=item $movie->auto_namer
deactivate/reactivate auto_namer. Default is active.
Auto_namer gives a suitable name to characters and display instances automatically
when their name is referred before being named explicitly.
=item $movie->save( $filename )
saves the movie.
=back
=head2 Character constructors
=over 4
=item $mc->new_shape
returns a new shape (type: Shape).
See L<SWF::Builder::Character::Shape> for the detail.
=item $mc->new_font( $fontfile [, $fontname] )
returns a new font (type: Font).
$fontfile is a font file name. It should be a TrueType font file (ttf/ttc).
Optional $fontname is a font name referred by HTMLs in dynamic texts.
It is taken from the TrueType file if not defined.
See L<SWF::Builder::Character::Font> for the detail.
=item $mc->new_static_text( [$font, $text] )
returns a new static text (type: Text), which is fixed by authoring and cannot
be changed at playing time.
See L<SWF::Builder::Character::Text> for the detail of a text.
=item $mc->new_edit_text( [$font, $text] )
=item $mc->new_dynamic_text( [$font, $text] )
=item $mc->new_html_text( [$html] )
=item $mc->new_text_area( $width, $height )
=item $mc->new_input_field( [$length] )
=item $mc->new_password_field( [$length] )
return new dynamic editable text variations (type: EditText).
See L<SWF::Builder::Character::EditText> for the detail.
=item $mc->new_movie_clip
=item $mc->new_mc
returns a new movie clip (type: MovieClip).
See L<SWF::Builder::Character::MovieClip> for the detail.
=item $mc->new_gradient
returns a new gradient object.
See L<SWF::Builder::Gradient> and L<SWF::Builder::Character::Shape> for the detail.
=item $mc->new_jpeg( ... )
returns a new JPEG bitmap (type: Bitmap).
See L<SWF::Builder::Character::Bitmap> for the detail.
=item $mc->new_bitmap( $obj [, $type] )
returns a new lossless bitmap (type: Bitmap).
See L<SWF::Builder::Character::Bitmap> for the detail.
=item $mc->import_asset( $url, $name [, $type] )
returns a character which will be imported from the SWF movie in $url by $name.
This method does not actually import a character but put
an 'ImportAssets' tag on the movie.
$type is a type of the character, such as 'Shape', 'Font', 'Text',
'EditText', 'MovieClip', and 'Bitmap'. Default is 'MovieClip'.
This method does not check whether a character which has $name and $type is actually exported.
=back
=head2 Other methods for movies
Here describe other common methods for root movie and movie clips.
=over 4
=item $mc->frame_action( $frame )
returns SWF::Builder::ActionScript object for a frame action.
=item $mc->frame_label( $frame, $label [, $anchorflag] )
gives $label to $frame to which ActionScripts can refer.
If the $anchorflag is set to 1, it is accessible as an
HTML anchor.
=back
=head2 Characters
Display and export methods of characters are described here.
See SWF::Builder::Character::* for details of type-specific methods.
=over 4
=item $disp_i = $char->place( [ MovieClip => $mc, Frame => $frame, above => $another_i, below => $another_i, clip_with => $mask_i ] )
places the character on $mc and returns the display instance.
It can take four optional named parameters.
'MovieClip'(MC) is a parent movie clip on which the character is placed.
The movie clip must be under the same root movie with the movie clip
in which the character is defined.
If MC is not set, the character is placed on the movie clip in which
it is defined.
'Frame' is a first frame number on which the character is placed. Default is 1.
You can set the relative depth of the new instance by 'above' and 'below'.
'clip_with' is a mask instance with which the character is clipped.
Font character can't place itself.
=item $mask_i = $char->place_as_mask( [ MovieClip => $mc, Frame => $frame, above => $another_i, below => $another_i ] )
places the character on $mc as the mask object (clipping layer)
and returns the mask instance.
It can take optional parameters as same as 'place' method except 'clip_with'.
You can handle the mask instance as same as the display instance.
Only Shape and Text characters can place_as_mask.
=item $char->name( [$name] )
gives a name to the character, exports the character, and returns the name.
ActionScripts can refer the character as $name.
Other movies can import the character by $name.
When you call 'name' method without a parameter, the method simply returns the name
of the character. If the character is not named yet and auto_namer is active,
auto_namer gives a suitable name.
=item $char->export_asset( [$name] )
Same as $char->name.
=back
=head2 Display instances
It is necessary to get the display instance to show the defined character.
Each instance has its own timeline tied to the parent movie clip
and the current frame to move, to rotate, etc.
=over 4
=item $disp_i->name( [$name] )
gives a name to the display instance to which ActionScripts can refer and returns the name.
When you call 'name' method without a parameter, the method simply returns the name
of the character. If the character is not named yet and auto_namer is active,
auto_namer gives a suitable name.
=item $fobj = $disp_i->frame( $frame )
gets the specified frame object of the display instance and sets the current
frame of the display instance to $frame.
Moving, rotating, scaling, and any other matrix transforming of
the display instance are handled in a frame by frame via a frame object.
When a frame object is not specified, the 'current frame object' kept by the
display item is used. The current frame is counted up after it is used.
=item $fobj/$disp_i->moveto( $x, $y )
moves the display item to ($x, $y) at the (current) frame.
=item $fobj/$disp_i->r_moveto( $dx, $dy )
moves the display item relatively ( to (former X + $x, former Y + $y)).
=item $fobj/$disp_i->scale( $xscale [, $yscale] )
magnifies/reduces the display item at the (current) frame.
The scaling effect is accumulative.
=item $fobj/$disp_i->rotate( $angle )
rotates the display item at the (current) frame.
The rotation angle is accumulative.
=item $fobj/$disp_i->reset
resets the rotation and scaling at the (current) frame.
=item $fobj/$disp_i->remove
removes the display instance from the parent movie clip at the (current) frame.
=item $fobj/$disp_i->matrix
gets the transformation matrix of the display instance at the (current) frame.
The result is an SWF::Element::MATRIX object.
=item $fobj/$disp_i->frame_action
=item $fobj/$disp_i->frame_label( $label [, $anchorflag] )
same as those for movie clips, setting the frame number to that of the frame object.
=back
=head1 COPYRIGHT
Copyright 2003 Yasuhiro Sasama (ySas), <ysas@nmt.ne.jp>
This library is free software; you can redistribute it
and/or modify it under the same terms as Perl itself.
=cut