home *** CD-ROM | disk | FTP | other *** search
- package RISCOS::DrawFile::Container;
-
- require RISCOS::DrawFile::Common;
- require RISCOS::DrawFile::FontTable;
- require RISCOS::DrawFile::Text;
- require RISCOS::DrawFile::TextArea;
- require RISCOS::DrawFile::OpaqueObject;
- require RISCOS::DrawFile::Group;
- require RISCOS::DrawFile::TagObject;
- require RISCOS::DrawFile::Options;
- require RISCOS::DrawFile::Path;
- require RISCOS::DrawFile::Sprite;
- require RISCOS::DrawFile::JPEG;
- use Carp;
- use strict;
- use vars qw ($VERSION @ISA %objs @EXPORT_OK);
-
- @ISA = qw(RISCOS::DrawFile::Common Exporter);
- @EXPORT_OK = 'split_drawobjs';
- $VERSION = 0.07;
- # 0.07 adds Translate, MoreStuff
- # 0.06
- # Replace returns () not undef. Bozo
- # localise $_ in map (by hook or by crook)
- # 0.05
- # split_drawobjs as a function.
- # 0.04
- # Name changes along with copy constructor.
- # 0.03
- # Added Replace. Documentation up to date
- # 0.02
- # Remember to undef the bounding box if we change the 'stuff'
- # Now copes elegantly if container contains no objects with valid bboxes.
-
- sub Objfunc {
- \%objs;
- }
- %objs = (
- 0 => sub { RISCOS::DrawFile::FontTable->new (@_) }, # FontTable
- 2 => sub { RISCOS::DrawFile::Path->new (@_) }, # Path
- 6 => sub { new RISCOS::DrawFile::Group @_ }, # Group
- 7 => sub { new RISCOS::DrawFile::TagObject @_ }, # Tag
- 9 => sub { new RISCOS::DrawFile::TextArea @_ }, # Text Area
- 16 => sub { new RISCOS::DrawFile::JPEG @_ }, # JPEG
- );
- $objs{1} = # Text
- $objs{12} = # Transformed Text
- sub { new RISCOS::DrawFile::Text @_ };
-
- $objs{5} = # Sprite
- $objs{13} = # Transformed Sprite
- sub { new RISCOS::DrawFile::Sprite @_ };
- $objs{11} = # Draw options
- $objs{101} = # DrawPlus options
- sub { new RISCOS::DrawFile::Options @_ };
-
- ### use SelfLoader;
- sub RISCOS::DrawFile::Container::new ;
- sub RISCOS::DrawFile::Container::Stuff ;
- sub RISCOS::DrawFile::Container::MoreStuff ;
- sub RISCOS::DrawFile::Container::PrePack ;
- sub RISCOS::DrawFile::Container::Size ;
- sub RISCOS::DrawFile::Container::Pack ;
- sub RISCOS::DrawFile::Container::Write ;
- sub RISCOS::DrawFile::Container::_split_drawobjs ($$$$$$$);
- sub RISCOS::DrawFile::Container::split_drawobjs ($$;$$$$$);
- sub RISCOS::DrawFile::Container::Second_Font_Table ;
- sub RISCOS::DrawFile::Container::Unknown_Obj ;
- sub RISCOS::DrawFile::Container::BBox_Calc ;
- sub RISCOS::DrawFile::Container::Translate ;
- sub RISCOS::DrawFile::Container::DoToAll ;
- sub RISCOS::DrawFile::Container::Do ;
- sub RISCOS::DrawFile::Container::Replace ;
- sub RISCOS::DrawFile::Container::Change ;
- sub RISCOS::DrawFile::Container::ChangeString ;
- 1;
- ### __DATA__
-
- sub new {
- my $proto = shift;
- my $class = ref($proto) || $proto;
- my $self = {};
-
- $self->{'__STUFF'} = (ref($_[0]) eq 'ARRAY') ? $_[0] : [@_];
-
- return bless ($self, $class);
- }
-
- sub Stuff {
- my $self = shift;
- my $stuff = $self->{'__STUFF'};
- my $newstuff = (ref($_[0]) eq 'ARRAY') ? $_[0] : \@_;
-
- if (@$newstuff) {
- my $use = [];
- foreach (@$newstuff) {
- push @$use, $_ if defined $_;
- }
- $self->{'__STUFF'} = $use;
- undef $self->{'__BBOX'};
- }
- $stuff;
- }
-
- sub MoreStuff {
- my $self = shift;
- my $stuff = $self->{'__STUFF'};
- my $newstuff = (ref($_[0]) eq 'ARRAY') ? $_[0] : \@_;
-
- if (@$newstuff) {
-
- foreach (@$newstuff) {
- push @$stuff, $_ if defined $_;
- }
- undef $self->{'__BBOX'};
- }
- $stuff;
- }
-
- # For me when I'm feeling daft - this recursively calls PrePack, not BBox_Calc.
- # So don't try to change (read break) it.
- sub PrePack {
- my $self = shift;
- return undef unless @{$self->{'__STUFF'}};
- my $box;
-
- foreach (@{$self->{'__STUFF'}}) {
- next unless (my $subbox = $_->PrePack (@_));
- confess "$#$subbox $_" unless defined $$subbox[3];
- if (defined $box) {
- $$box[0] = $$subbox[0] if $$box[0] > $$subbox[0]; # min
- $$box[1] = $$subbox[1] if $$box[1] > $$subbox[1];
- $$box[2] = $$subbox[2] if $$box[2] < $$subbox[2]; # max
- $$box[3] = $$subbox[3] if $$box[3] < $$subbox[3];
- } else {
- $box = [@$subbox];
- }
- }
- $self->{'__BBOX'} = $box; # Return the bbox we made, and store it
- }
-
- sub Size {
- my $self = shift;
- my $size = 0;
- foreach (@{$self->{'__STUFF'}}) {
- $size += $_->Size;
- }
- $size;
- }
-
- # fonttable (as param 2)
- sub Pack {
- my $self = shift;
- join '', map { $_->Pack (@_)} (@{$self->{'__STUFF'}});
- }
-
- # handle
- # fonttable
- sub Write {
- my $self = shift;
- my $good = 0;
- foreach (@{$self->{'__STUFF'}}) {
- $good &= $_->Write (@_);
- }
- $good;
- }
-
- # Data scalar data
- # scalar ref type, length, bbox striped
- # array ref array of objects for group
- # array
- # hash ref ?? array, name, type data ?? dunno...
- # type split
- # function ref pass unpslit type
- # returns (type, layer, flags, spare) or (type)
- # array ref (type, layer, flags, spare)
- # ref to fonttable (starts as undef)
- # sub constructors (array or hash - both!)
- # duplicate font table
- # unknown object constructor.
-
- # Constructors return an array of
- # single object / array ref to array of objects
- # type (or undef)
- # fonttable (if found)
-
- sub _split_drawobjs ($$$$$$$) {
- shift; # my $self = shift;
- my $data = shift; # Don't want to clobber it.
- my ($split, $fonttable, $subconst, $fontfunc, $unk) = @_;
- unshift @_, undef;
- carp "Can't split a " . ref ($data) . ' ref, only SCALAR refs'
- unless ref ($data) eq 'LVALUE' || ref ($data) eq 'SCALAR';
-
- my ($position, $stuff, $font) = (0);
- while ($position < length ($$data)) {
- my ($type, $length) = unpack 'I2', substr $$data, $position;
- $type = &$split ($type) if defined $split;
-
- my $func;
- if (ref ($subconst) eq 'HASH') {
- $func = $subconst->{$type};
- } elsif (ref ($subconst) eq 'ARRAY') {
- $func = $subconst->[$type];
- } else {
- carp "Can't lookup objects in a " . ref ($subconst) . ' ref';
- }
- $func = $unk unless (defined $func);
-
- $_[0] = substr $$data, $position, $length;
- my @result = $func->(@_);
- # Pass on @_;
- if (ref $result[2] eq 'RISCOS::DrawFile::FontTable') {
-
- if (defined $$fonttable) {
- $fontfunc->(@_); # Got one already
- } else {
- $$fonttable = $font = $result[2]; # Store FontTable ref
- }
- }
- push @$stuff, (ref ($result[0]) eq 'ARRAY') ? @{$result[0]} : $result[0]
- if defined $result[0];
- $position += $length;
- }
- ($stuff, undef, $font);
- }
- sub split_drawobjs ($$;$$$$$) {
- if (defined ($_[2]) and $_[2] eq '+') {
- $_[2] = \&RISCOS::DrawFile::Common::drawplus_split_type;
- }
- $_[3] = {} unless defined $_[3];
- $_[4] = defined $_[0] ? $_[0]->Objfunc() : Objfunc() unless defined $_[4];
- $_[5] = defined $_[0] ? $_[0]->can ('Second_Font_Table')
- : \&Second_Font_Table
- unless defined $_[5];
- $_[6] = defined $_[0] ? $_[0]->can ('Unknown_Obj')
- : \&Unknown_Obj
- unless defined $_[6];
- goto &_split_drawobjs;
- }
-
- sub Second_Font_Table {
- carp 'Duplicate font table size ' . length ($_[0]) . ' found - will ignore';
- ();
- }
-
- sub Unknown_Obj {
- carp sprintf 'Uknown object type &%08X size %d found - will treat as opaque',
- unpack ('I', $_[0]), length ($_[0]);
- new RISCOS::DrawFile::OpaqueObject @_;
- }
-
- sub BBox_Calc {
- my $self = shift;
- return undef unless @{$self->{'__STUFF'}};
- my $box;
-
- foreach (@{$self->{'__STUFF'}}) {
- next unless (my $subbox = $_->BBox_Calc);
- if (defined $box) {
- $$box[0] = $$subbox[0] if $$box[0] > $$subbox[0]; # min
- $$box[1] = $$subbox[1] if $$box[1] > $$subbox[1];
- $$box[2] = $$subbox[2] if $$box[2] < $$subbox[2]; # max
- $$box[3] = $$subbox[3] if $$box[3] < $$subbox[3];
- } else {
- $box = [@$subbox];
- }
- }
- $self->{'__BBOX'} = $box; # Return the bbox we made, and store it
- }
-
- sub Translate {
- my $self = shift;
-
- foreach (@{$self->{'__STUFF'}}) {
- $_->Translate (@_);
- }
- undef $self->{'__BBOX'};
- ()
- }
-
- sub DoToAll {
- my $self = shift;
- my $what = shift;
- map {
- my (@args, @result) = @_; # @args gobbles all of @_
- my ($object, $method) = $_;
- if (defined ($method = $object->can ('DoToAll'))) {
- @result = &$method ($object, $what, @args);
- }
- if ('CODE' eq ref $what) {
- push @result, &$what ($object, @args);
- } elsif (defined ($method = $object->can ($what))) {
- push @result, &$method ($object, @args)
- }
- $_ = $object; # In effect localise it
- @result; # It's map, remember.
- } (@{$self->{'__STUFF'}})
- }
-
- sub Do {
- my $self = shift;
- my $what = shift;
- map {
- my (@args, @result) = @_; # @args gobbles all of @_
- my ($object, $method) = $_;
- if (defined ($method = $object->can ('Do'))) {
- @result = &$method ($object, $what, @args);
- } else {
- if ('CODE' eq ref $what) {
- @result = &$what ($object, @args);
- } elsif (defined ($method = $object->can ($what))) {
- @result = &$method ($object, @args)
- }
- }
- $_ = $object;
- @result; # It's map, remember.
- } (@{$self->{'__STUFF'}})
- }
-
- sub Replace {
- my $self = shift;
- my $what = shift;
- @{$self->{'__STUFF'}} = map {
- my (@args, @result) = @_; # @args gobbles all of @_
- my ($object, $method) = $_;
- if (defined ($method = $object->can ('Replace'))) {
- @result = &$method ($object, $what, @args);
- } else {
- if ('CODE' eq ref $what) {
- @result = &$what ($object, @args);
- } elsif (defined ($method = $object->can ($what))) {
- @result = &$method ($object, @args)
- }
- }
- $_ = $object;
- @result; # It's map, remember.
- } (@{$self->{'__STUFF'}});
- # If we still contain anything, return ourself.
- @{$self->{'__STUFF'}} ? $self : ();
- }
-
- sub Change {
- my $self = shift;
- my $what = shift;
- my $old = shift;
- map {
- my (@args, @result) = @_; # @args gobbles all of @_
- my ($object, $method) = $_;
- if (defined ($method = $object->can ('Change'))) {
- @result = &$method ($object, $what, @args);
- }
- if (defined ($method = $object->can ($what))) {
- push @result, &$method ($object, @args)
- if $old = &$method ($object);
- }
- $_ = $object;
- @result; # It's map, remember.
- } (@{$self->{'__STUFF'}})
- }
-
- sub ChangeString {
- my $self = shift;
- my $what = shift;
- my $old = shift;
- map {
- my (@args, @result) = @_; # @args gobbles all of @_
- my ($object, $method) = $_;
- if (defined ($method = $object->can ('ChangeString'))) {
- @result = &$method ($object, $what, @args);
- }
- if (defined ($method = $object->can ($what))) {
- push @result, &$method ($object, @args)
- if $old eq &$method ($object);
- }
- $_ = $object;
- @result; # It's map, remember.
- } (@{$self->{'__STUFF'}})
- }
-
- 1;
- __END__
-
- =head1 NAME
-
- RISCOS::DrawFile::Container
-
- =head1 SYNOPSIS
-
- Abstract base class for classes that hold other DrawFile objects.
-
- =head1 DESCRIPTION
-
- C<RISCOS::DrawFile::Container> provides an abstract base class for classes that
- hold other DrawFile objects (groups, tagged objects and DrawFiles themselves.
- C<RISCOS::DrawFile::Container> itself is not a C<RISCOS::DrawFile::Object>, as
- not all classes which derive from it are objects found in DrawFiles.
-
- =head2 Methods
-
- =over 4
-
- =item new <contents>
-
- creates a new object. If I<contents> is an array reference it is dereferenced.
- The array of objects (if any) is used as the container's contents.
-
- =item Stuff [<new_contents>]
-
- returns a reference to the array of contents. If I<new_contents> are given, then
- these replace the existing contents (and the old contents are returned). If
- I<new_contents> is an array reference it is automatically dereferenced first.
-
- =item MoreStuff [<additional_contents>]
-
- adds I<new_contents> existing contents, returning a reference to the array of
- contents. If I<additional_contents> is an array reference it is automatically
- dereferenced first.
-
- =item Do <what>, arguments...
-
- recursively does something to all contained objects.
-
- For each contained item:
-
- =over 4
-
- =item *
-
- If it has a C<Do> method, calls it with the arguments passed to this method.
-
- =item *
-
- If <what> is a code reference, calls it as
-
- &what (object, arguments...)
-
- else looks for a method I<what> in the object, and if found calls that method
- with the arguments given
-
- =back
-
- C<Do> returns the list of all results returned from all called subroutines.
-
- This method is extremely powerful. For example, to set all line widths to thin
- in the object C<$draw>
-
- $draw->Do('Width', 0);
-
- To change all occurrences of the font 'Homerton.Medium' to 'AvantG.Book' in all
- text objects you could do:
-
- $draw->Do(sub {$_[0]->Font ('AvantG.Book')
- if $_[0]->can('Font')
- and $_[0]->Font() eq 'Homerton.Medium'});
-
- (note that if you pass code you need to check that the method exists with C<can>
- before you try to call it) but you'd be much better off with
-
- $draw->ChangeString('Font','Homerton.Medium','AvantG.Book');
-
- (see below)
-
- =item DoToAll <what>, arguments...
-
- is like C<Do> except that it also calls the named method or code reference on
- contained containers, unlike C<Do> which only calls it on objects which do not
- possess their own C<Do> to recurse to. Unless you want to alter contained Groups
- or Tag objects in some way, you probably don't want to call C<DoToAll> as it
- will return an possibly unhelpful list of results - for example if the result
- array is all objects inside a bounding box you may get objects within groups
- multiple times; once when the check is performed on the object itself, and again
- within each group that also meets the test condition.
-
- =item Replace <what>, arguments...
-
- is like C<Do> except it I<replaces> the contents of each container with the
- return values of I<what>, so I<what> had better be returning DrawFile objects.
- If a container contains at least one object afterwards it returns a reference to
- itself, whereas an empty container returns C<undef>. Beware that this way a
- container (I<e.g.> an entire DrawFile) can end up deleting itself, so B<do>
- check the return value, before your script crashes when attempting to call a
- method on a now undefined scalar.
-
-
- =item Change <method_name>, <test_value> arguments...
-
- is similar to C<Do>, but can only take a named method. The method is called with
- no arguments in scalar context, and if the result is I<numerically> equal
- (C<==>) to I<test_value> the method is called again with the arguments supplied.
-
- So to change all 4 point lines to 6
-
- $draw->Change('Width', 2560, 3840);
-
- (without having to ungroup or regroup anything...)
-
- =item ChangeString <method_name>, <test_value> arguments...
-
- is identical to C<Change> except that the comparison is for a string (C<eq>).
-
- $draw->ChangeString('Font','Homerton.Medium','AvantG.Book');
-
- =item BBox
-
- returns a reference to an array giving the bounding box, or C<undef> if there is
- no bounding box for this object (I<e.g.> an empty group, a tagged empty path).
- C<BBox> will call C<BBox_Calc> if the bounding box is currently unknown.
-
- As the returned array reference B<is> the internal copy of the bounding box it
- must not be modified.
-
- =item BBox_Calc
-
- recalculates and returns the bounding box, by calling C<BBox_Calc> for each
- contained object and merging the bounding boxes. C<BBox_Calc> will return
- C<undef> if no contained object returned a valid bounding box. (This is far more
- elegant than returning (int_max, int_max, int_min, int_min), as is the wont of
- C<Draw_ProcessPath> when presented with an empty path - yes, we're trapping this
- one, and C<Font_ScanString> when given an empty string).
-
- =item PrePack <hash_reference>
-
- is provided as a hook to perform calculations immediately before saving a
- DrawFile. The hash reference is used to store the names of fonts needed in the
- FontTable by C<RISCOS::DrawFile::Text> objects. C<PrePack> calls
- C<PrePack> for each contained object, and merges the bounding boxes.
-
- =item Size
-
- returns the size of the object when saved in a DrawFile, by summing the results
- of calling C<Size> on the contents.
-
- =item Pack <undef>, fonttable, ...
-
- returns a scalar containing the object packed ready to save into a DrawFile, by
- concatenating the results of calling C<Pack> on the contents.
-
- =item Write <filehandle>, <fonttable>, ...
-
- writes the object to the given filehandle. The default implementation calls
- C<Write> with the remainder of the argument list for each item in the contents,
- returning false if any call to C<Write> did not return true.
-
- =item Second_Font_Table
-
- prints a warning that a second font table has been found, and returns an empty
- list. Mostly of use to the DrawFile class.
-
- =item Objfunc
-
- returns a reference to a hash of code references, keyed by object type. This
- hash determines the correct object constructor to call when the DrawFile data is
- split into objects. Mostly of use to the DrawFile class.
-
- =item Unknown_Obj
-
- prints a warning that an unknown object type has been found, and returns the
- result of calling C<RISCOS::DrawFile::OpaqueObject>. Mostly of use to the
- DrawFile class.
-
- =item _split_drawobjs <data>, <split>, <fonttable>, <sub_constructors>, <duplicate_fonttable>, <unknown_object>
-
- splits the data passed as a B<scalar reference> into a list of DrawFile objects.
- I<split> is a split function as described in C<new> in
- C<RISCOS::DrawFile::Object>. I<fonttable> is initially C<undef>, but for
- recursive calls is replaced with the fonttable object once found.
- I<sub_constructors> is a hash or array reference used to find code to construct
- objects keyed by type. Usually this is supplied by calling C<Objfunc>, but a
- custom hash/array can be used. I<duplicate_fonttable> is called as a constructor
- when a second fonttable is found. Usually this is a reference to
- C<&Second_Font_Table>. I<unknown_object> is called as a constructor for any
- object type not found in I<sub_constructors>. Usually this is a reference to
- I<&Unknown_Obj>.
-
- C<_split_drawobjs> returns a list ([objects], undef, fonttable) as for a
- DrawFile object constructor.
-
- This method is used by groups and DrawFile objects to split their contents into
- objects. It probably isn't needed by anyone else.
-
- =item Do <what>, arguments...
-
- recursively does something to all contained objects.
-
- For each contained item:
-
- =over 4
-
- =item *
-
- If it has a C<Do> method, calls it with the arguments passed to this method.
-
- =item *
-
- If <what> is a code reference, calls it as
-
- &what (I<object>, I<arguments...>
-
- else looks for a method I<what> in the object, and if found calls that method
- with the arguments given
-
- =back
-
- C<Do> returns the list of all results returned from all called subroutines.
-
- This method is extremely powerful. For example, to set all line widths to thin
- in the object C<$draw>
-
- $draw->Do('Width', 0);
-
- To change all occurrences of the font 'Homerton.Medium' to 'AvantG.Book' in all
- text objects:
-
- $draw->Do(sub {$_[0]->Font ('AvantG.Book')
- if $_[0]->can('Font')
- and $_[0]->Font() eq 'Homerton.Medium'});
-
- (note that if you pass code you need to check that the method exists with C<can>
- before you try to call it)
-
- =back
-
- =head1 BUGS
-
- Currently doesn't allow derived classes to limit the number of objects that they
- can hold. (C<TagObject>s only hold one object)
-
- =head1 AUTHOR
-
- Nicholas Clark <F<nick@unfortu.net>>
-