package SWF::Builder::Character; use strict; use Carp; use SWF::Element; use SWF::Builder::ExElement; use SWF::Builder; our $VERSION = '0.02'; sub _init_character { my $self = shift; $self->{ID} = SWF::Element::ID->new; $self->{_depends} = {}; $self->{_parent} = undef; $self->{_root} = undef; $self->{_export_name} = undef; } sub name { my ($self, $name) = @_; if (defined $name) { my $rname = $name; croak "Can't rename the character which has been already exported as '".$self->{_export_name}."'" if defined $self->{_export_name}; utf2bin($name); $self->{_root}->_depends($self, 1); $self->{_export_name} = $name; return $rname; } else { unless (defined $self->{_export_name}) { if ($self->{_root}{_auto_namer}) { $name = join('', $self =~ /Character::([^:]+)::.+\(0x(.+)\)$/); $self->{_root}{_names}{$name} = $self; $self->{_root}->_depends($self, 1); $self->{_export_name} = $name; } else { croak "Can't get the name of the character"; } } else { my $rname = $self->{_export_name}; bin2utf($rname); return $rname; } } } *export_asset = \&name; sub pack { my ($self, $stream) = @_; return if $self->{ID}->defined; for my $dep (values %{$self->{_depends}}) { $dep->pack($stream) unless $dep->{ID}->defined; } if ($self->{_root}) { $self->{ID}->configure($self->get_ID); } else { croak "Character ID need to be initialized to pack" unless $self->{ID}->defined; } $self->_pack($stream); if (defined $self->{_export_name}) { SWF::Element::Tag::ExportAssets->new ( Assets => [[ ID => $self->{ID}, Name => $self->{_export_name}]] )->pack($stream); } } sub get_ID { shift->{_root}{_ID_seed}++; } sub _depends { my ($self, $char) = @_; $self->{_depends}{$char} = $char; } sub _destroy { %{+shift} = (); } #### package SWF::Builder::Character::Displayable; @SWF::Builder::Character::Displayable::ISA = qw/SWF::Builder::Character/; use Carp; sub _search_sibling { my ($parent, $ref) = @_; my $p; while(exists $ref->{_parent}) { $p = $ref->{_parent}; return $ref if $p eq $parent; $ref = $p; } return undef; } sub place { my ($self, %param) = @_; my $parent = $param{MovieClip} || $param{MC} || $param{clip_with} || $self->{_parent} or croak "Can't get the movieclip to place"; if ($parent eq '_root') { $parent = $self->{_root}; } elsif (ref($parent) eq 'SWF::Builder') { $parent = $parent->{_root}; } croak "The item can be placed only on the movie which defines it" if $parent->{_root} != $self->{_root}; my $frame = $param{Frame} || 1; $parent->_depends($self, $frame); my $disp_i = bless { _parent => $parent, _root => $self->{_root}, _first_frame => $frame, _last_frame_offset => 2**64, _current_frame => $frame, _obj => $self, _tags => [], }, 'SWF::Builder::DisplayInstance'; push @{$self->{_root}{_to_destroy}}, $disp_i; my $depth; if (exists $param{below}) { my $refitem = _search_sibling($parent, $param{below}) or croak "Can't place the item below what on the different movieclip"; $depth = SWF::Builder::Depth->new($disp_i, $refitem->{_depth}{_lower}); } elsif (exists $param{above}) { my $refitem = _search_sibling($parent, $param{below}) or croak "Can't place the item above what on the different movieclip"; $depth = SWF::Builder::Depth->new($disp_i, $refitem->{_depth}); } else { $depth = SWF::Builder::Depth->new($disp_i, $parent->{_depth_list}{_lower}); } $disp_i->{_depth} = $depth; $disp_i->frame($frame); $disp_i->{_current_frame} = $frame; $disp_i; } #### package SWF::Builder::Character::UsableAsMask; @SWF::Builder::Character::UsableAsMask::ISA = qw/SWF::Builder::Character::Displayable/; sub place_as_mask { my $self = shift; my $disp_i = $self->place(@_); bless $disp_i, 'SWF::Builder::MaskInstance'; bless $disp_i->{_depth}, 'SWF::Builder::MaskDepth'; $disp_i->{_clipdepth} = SWF::Element::Depth->new(0); $disp_i->{_frame_list} = $disp_i->{_parent}{_frame_list}; $disp_i->{_depth_list} = SWF::Builder::Depth->new($disp_i); $disp_i->{_depends} = {}; $disp_i->frame($disp_i->{_first_frame}); $disp_i->{_current_frame} = $disp_i->{_first_frame}; $disp_i; } #### package SWF::Builder::Character::Imported; @SWF::Builder::Character::Imported::ISA = qw/SWF::Builder::Character/; use Carp; sub new { my ($class, $url, $name, $type) = @_; $type ||= 'MovieClip'; $class = "SWF::Builder::Character::${type}::Imported"; my $self = bless { _url => $url, _name => $name, }, $class; eval "require SWF::Builder::Character::$type"; croak "Can't import character type '$type'" unless UNIVERSAL::can($class, '_init_character'); $self->_init_character; $self; } sub _pack { my ($self, $stream) = @_; SWF::Element::Tag::ImportAssets->new ( URL => $self->{_url}, Assets => [[ID => $self->{ID}, Name => $self->{_name}]] )->pack($stream); } #### package SWF::Builder::DisplayInstance; use Carp; use SWF::Builder::ExElement; sub frame { my ($self, $frame) = @_; my $frametag; my $frame_offset = $frame - $self->{_first_frame}; unless (defined($self->{_tags}[$frame_offset])) { croak "The frame $frame is out of range" if $frame_offset < 0 or $frame_offset >= $self->{_last_frame_offset}; $frametag = bless { _parent => $self, _frame_offset => $frame_offset, _tag => SWF::Element::Tag::PlaceObject2->new ( Depth => $self->{_depth}{_depth} ), }, 'SWF::Builder::DisplayInstance::Frame'; $self->{_tags}[$frame_offset] = $frametag; push @{$self->{_parent}{_frame_list}[$frame-1]}, $frametag; if ($frame_offset == 0) { $frametag->{_tag}->CharacterID($self->{_obj}{ID}); } else { $frametag->{_tag}->PlaceFlagMove(1); } } else { $frametag = $self->{_tags}[$frame_offset]; } $self->{_current_frame} = $frame+1; $frametag; } sub name { my ($self, $name) = @_; my $tag = $self->{_tags}[0]{_tag}; if (defined $name) { my $rname = $name; croak "Can't rename the display instance, which is already named as '".$self->Name."'" if $tag->Name->defined; utf2bin($name); $tag->Name($name); return $rname; } else { unless ($tag->Name->defined) { if ($self->{_root}{_auto_namer}) { ($name) = ($self =~ /\(0x(.+)\)$/); $name = "DI$name"; $self->{_root}{_names}{$name} = $self; $tag->Name($name); } else { croak "Can't get the name of the display instance"; } } else { my $rname = $tag->Name; bin2utf($rname); return $rname; } } } sub AUTOLOAD { my $self = shift; my ($name, $class); my $sub = $SWF::Builder::DisplayInstance::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 SWF::Builder::DisplayInstance::Frame->can($sub); $self->frame($self->{_current_frame})->$sub(@_); } sub _destroy { %{+shift} = (); } #### package SWF::Builder::DisplayInstance::Frame; use Carp; sub scale { my $self = shift; $self->matrix->scale(@_); $self; } sub moveto { my $self = shift; $self->matrix->moveto($_[0]*20, $_[1]*20); $self; } sub r_moveto { my ($self, $to_rx, $to_ry) = @_; my $m = $self->matrix; $m->moveto($m->TranslateX + $to_rx*20, $m->TranslateY + $to_ry*20); $self; } sub rotate { my ($self, $r) = @_; $self->matrix->rotate($r); $self; } sub reset { my $self = shift; my $m = $self->matrix; $m->ScaleX(1); $m->ScaleY(1); $m->RotateSkew0(0); $m->RotateSkew1(0); $self; } sub remove { my $self = shift; my $parent = $self->{_parent}; croak "This DisplayInstance has already set to remove " if ($parent->{_last_frame_offset} < 2**64); $self->{_tag} = SWF::Element::Tag::RemoveObject2->new( Depth => $parent->{_depth}{_depth} ); $parent->{_last_frame_offset} = $self->{_frame_offset}; $self; } sub frame_action { my $self = shift; $self->{_parent}{_parent}->frame_action($self->{_parent}{_first_frame}+$self->{_frame_offset}); } sub frame_label { my $self = shift; $self->{_parent}{_parent}->frame_label($self->{_parent}{_first_frame}+$self->{_frame_offset}, @_); } sub ratio { my ($self, $ratio) = @_; $self->{_tag}->Ratio($ratio); $self; } sub matrix { my $self = shift; my $tag = $self->{_tag}; unless ($tag->Matrix->defined) { my $ptags = $self->{_parent}{_tags}; my $frame_offset = $self->{_frame_offset}; $frame_offset-- until ($frame_offset == 0 or defined $ptags->[$frame_offset] and $ptags->[$frame_offset]{_tag}->Matrix->defined); $tag->Matrix($ptags->[$frame_offset]{_tag}->Matrix->clone); } $tag->Matrix; } sub pack { my ($self, $stream) = @_; $self->{_tag}->pack($stream); } #### package SWF::Builder::MaskInstance; @SWF::Builder::MaskInstance::ISA = qw/ SWF::Builder::DisplayInstance /; sub _depends { my $self = shift; $self->{_parent}->_depends(@_); } sub frame { my $self = shift; my $frametag = $self->SUPER::frame(@_); $frametag->{_tag}->ClipDepth($self->{_clipdepth}); $frametag; } ##### package SWF::Builder::MaskDepth; @SWF::Builder::MaskDepth::ISA = qw/ SWF::Builder::Depth /; sub set_depth { my ($self, $n) = @_; $self->{_depth}->configure($n++); my $depth_list = $self->{_parent}{_depth_list}; my $depth = $depth_list->{_upper}; while ($depth != $depth_list) { $n = $depth->set_depth($n); $depth = $depth->{_upper}; } $self->{_parent}{_clipdepth}->configure($n-1); $n; } 1;