--- /dev/null
+package Font::TTF::Segarr;
+
+=head1 NAME
+
+Font::TTF::Segarr - Segmented array
+
+=head1 DESCRIPTION
+
+Holds data either directly or indirectly as a series of arrays. This class
+looks after the set of arrays and masks the individual sub-arrays, thus saving
+a class, we hope.
+
+=head1 INSTANCE VARIABLES
+
+All instance variables do not start with a space.
+
+The segmented array is simply an array of segments
+
+Each segment is a more complex affair:
+
+=over 4
+
+=item START
+
+In terms of the array, the address for the 0th element in this segment.
+
+=item LEN
+
+Number of elements in this segment
+
+=item VAL
+
+The array which contains the elements
+
+=back
+
+=head1 METHODS
+
+=cut
+
+use strict;
+use vars qw(@types $VERSION);
+$VERSION = 0.0001;
+
+@types = ('', 'C', 'n', '', 'N');
+
+=head2 Font::TTF::Segarr->new($size)
+
+Creates a new segmented array with a given data size
+
+=cut
+
+sub new
+{
+ my ($class) = @_;
+ my ($self) = [];
+
+ bless $self, (ref($class) || $class);
+}
+
+
+=head2 $s->fastadd_segment($start, $is_sparse, @dat)
+
+Creates a new segment and adds it to the array assuming no overlap between
+the new segment and any others in the array. $is_sparse indicates whether the
+passed in array contains C<undef>s or not. If false no checking is done (which
+is faster, but riskier). If equal to 2 then 0 is considered undef as well.
+
+Returns the number of segments inserted.
+
+=cut
+
+sub fastadd_segment
+{
+ my ($self) = shift;
+ my ($start) = shift;
+ my ($sparse) = shift;
+ my ($p, $i, $seg, @seg);
+
+
+ if ($sparse)
+ {
+ for ($i = 0; $i <= $#_; $i++)
+ {
+ if (!defined $seg && (($sparse != 2 && defined $_[$i]) || $_[$i] != 0))
+ { $seg->{'START'} = $start + $i; $seg->{'VAL'} = []; }
+
+ if (defined $seg && (($sparse == 2 && $_[$i] == 0) || !defined $_[$i]))
+ {
+ $seg->{'LEN'} = $start + $i - $seg->{'START'};
+ push(@seg, $seg);
+ $seg = undef;
+ } elsif (defined $seg)
+ { push (@{$seg->{'VAL'}}, $_[$i]); }
+ }
+ if (defined $seg)
+ {
+ push(@seg, $seg);
+ $seg->{'LEN'} = $start + $i - $seg->{'START'};
+ }
+ } else
+ {
+ $seg->{'START'} = $start;
+ $seg->{'LEN'} = $#_ + 1;
+ $seg->{'VAL'} = [@_];
+ @seg = ($seg);
+ }
+
+ for ($i = 0; $i <= $#$self; $i++)
+ {
+ if ($self->[$i]{'START'} > $start)
+ {
+ splice(@$self, $i, 0, @seg);
+ return wantarray ? @seg : scalar(@seg);
+ }
+ }
+ push(@$self, @seg);
+ return wantarray ? @seg : scalar(@seg);
+}
+
+
+=head2 $s->add_segment($start, $overwrite, @dat)
+
+Creates a new segment and adds it to the array allowing for possible overlaps
+between the new segment and the existing ones. In the case of overlaps, elements
+from the new segment are deleted unless $overwrite is set in which case the
+elements already there are over-written.
+
+This method also checks the data coming in to see if it is sparse (i.e. contains
+undef values). Gaps cause new segments to be created or not to over-write existing
+values.
+
+=cut
+
+sub add_segment
+{
+ my ($self) = shift;
+ my ($start) = shift;
+ my ($over) = shift;
+ my ($seg, $i, $s, $offset, $j, $newi);
+
+ return $self->fastadd_segment($start, $over, @_) if ($#$self < 0);
+ $offset = 0;
+ for ($i = 0; $i <= $#$self && $offset <= $#_; $i++)
+ {
+ $s = $self->[$i];
+ if ($s->{'START'} <= $start + $offset) # only < for $offset == 0
+ {
+ if ($s->{'START'} + $s->{'LEN'} > $start + $#_)
+ {
+ for ($j = $offset; $j <= $#_; $j++)
+ {
+ if ($over)
+ { $s->{'VAL'}[$start - $s->{'START'} + $j] = $_[$j] if defined $_[$j]; }
+ else
+ { $s->{'VAL'}[$start - $s->{'START'} + $j] ||= $_[$j] if defined $_[$j]; }
+ }
+ $offset = $#_ + 1;
+ last;
+ } elsif ($s->{'START'} + $s->{'LEN'} > $start + $offset) # is $offset needed here?
+ {
+ for ($j = $offset; $j < $s->{'START'} + $s->{'LEN'} - $start; $j++)
+ {
+ if ($over)
+ { $s->{'VAL'}[$start - $s->{'START'} + $j] = $_[$j] if defined $_[$j]; }
+ else
+ { $s->{'VAL'}[$start - $s->{'START'} + $j] ||= $_[$j] if defined $_[$j]; }
+ }
+ $offset = $s->{'START'} + $s->{'LEN'} - $start;
+ }
+ } else # new seg please
+ {
+ if ($s->{'START'} > $start + $#_ + 1)
+ {
+ $i += $self->fastadd_segment($start + $offset, 1, @_[$offset .. $#_]) - 1;
+ $offset = $#_ + 1;
+ }
+ else
+ {
+ $i += $self->fastadd_segment($start + $offset, 1, @_[$offset .. $s->{'START'} - $start]) - 1;
+ $offset = $s->{'START'} - $start + 1;
+ }
+ }
+ }
+ if ($offset <= $#_)
+ {
+ $seg->{'START'} = $start + $offset;
+ $seg->{'LEN'} = $#_ - $offset + 1;
+ $seg->{'VAL'} = [@_[$offset .. $#_]];
+ push (@$self, $seg);
+ }
+ $self->tidy;
+}
+
+
+=head2 $s->tidy
+
+Merges any immediately adjacent segments
+
+=cut
+
+sub tidy
+{
+ my ($self) = @_;
+ my ($i, $sl, $s);
+
+ for ($i = 1; $i <= $#$self; $i++)
+ {
+ $sl = $self->[$i - 1];
+ $s = $self->[$i];
+ if ($s->{'START'} == $sl->{'START'} + $sl->{'LEN'})
+ {
+ $sl->{'LEN'} += $s->{'LEN'};
+ push (@{$sl->{'VAL'}}, @{$s->{'VAL'}});
+ splice(@$self, $i, 1);
+ $i--;
+ }
+ }
+ $self;
+}
+
+
+=head2 $s->at($addr, [$len])
+
+Looks up the data held at the given address by locating the appropriate segment
+etc. If $len > 1 then returns an array of values, spaces being filled with undef.
+
+=cut
+
+sub at
+{
+ my ($self, $addr, $len) = @_;
+ my ($i, $dat, $s, @res, $offset);
+
+ $len = 1 unless defined $len;
+ $offset = 0;
+ for ($i = 0; $i <= $#$self; $i++)
+ {
+ $s = $self->[$i];
+ next if ($s->{'START'} + $s->{'LEN'} < $addr + $offset); # only fires on $offset == 0
+ if ($s->{'START'} > $addr + $offset)
+ {
+ push (@res, (undef) x ($s->{'START'} > $addr + $len ?
+ $len - $offset : $s->{'START'} - $addr - $offset));
+ $offset = $s->{'START'} - $addr;
+ }
+ last if ($s->{'START'} >= $addr + $len);
+
+ if ($s->{'START'} + $s->{'LEN'} >= $addr + $len)
+ {
+ push (@res, @{$s->{'VAL'}}[$addr + $offset - $s->{'START'} ..
+ $addr + $len - $s->{'START'} - 1]);
+ $offset = $len;
+ last;
+ } else
+ {
+ push (@res, @{$s->{'VAL'}}[$addr + $offset - $s->{'START'} .. $s->{'LEN'} - 1]);
+ $offset = $s->{'START'} + $s->{'LEN'} - $addr;
+ }
+ }
+ push (@res, (undef) x ($len - $offset)) if ($offset < $len);
+ return wantarray ? @res : $res[0];
+}
+
+
+=head2 $s->remove($addr, [$len])
+
+Removes the item or items from addr returning them as an array or the first
+value in a scalar context. This is very like C<at>, including padding with
+undef, but it deletes stuff as it goes.
+
+=cut
+
+sub remove
+{
+ my ($self, $addr, $len) = @_;
+ my ($i, $dat, $s, @res, $offset);
+
+ $len = 1 unless defined $len;
+ $offset = 0;
+ for ($i = 0; $i <= $#$self; $i++)
+ {
+ $s = $self->[$i];
+ next if ($s->{'START'} + $s->{'LEN'} < $addr + $offset);
+ if ($s->{'START'} > $addr + $offset)
+ {
+ push (@res, (undef) x ($s->{'START'} > $addr + $len ?
+ $len - $offset : $s->{'START'} - $addr - $offset));
+ $offset = $s->{'START'} - $addr;
+ }
+ last if ($s->{'START'} >= $addr + $len);
+
+ unless ($s->{'START'} == $addr + $offset)
+ {
+ my ($seg) = {};
+
+ $seg->{'START'} = $s->{'START'};
+ $seg->{'LEN'} = $addr + $offset - $s->{'START'};
+ $seg->{'VAL'} = [splice(@{$s->{'VAL'}}, 0, $addr + $offset - $s->{'START'})];
+ $s->{'LEN'} -= $addr + $offset - $s->{'START'};
+ $s->{'START'} = $addr + $offset;
+
+ splice(@$self, $i, 0, $seg);
+ $i++;
+ }
+
+ if ($s->{'START'} + $s->{'LEN'} >= $addr + $len)
+ {
+ push (@res, splice(@{$s->{'VAL'}}, 0, $len - $offset));
+ $s->{'LEN'} -= $len - $offset;
+ $s->{'START'} += $len - $offset;
+ $offset = $len;
+ last;
+ } else
+ {
+ push (@res, @{$s->{'VAL'}});
+ $offset = $s->{'START'} + $s->{'LEN'} - $addr;
+ splice(@$self, $i, 0);
+ $i--;
+ }
+ }
+ push (@res, (undef) x ($len - $offset)) if ($offset < $len);
+ return wantarray ? @res : $res[0];
+}
+
+
+=head2 $s->copy
+
+Deep copies this array
+
+=cut
+
+sub copy
+{
+ my ($self) = @_;
+ my ($res, $p);
+
+ $res = [];
+ foreach $p (@$self)
+ { push (@$res, $self->copy_seg($p)); }
+ $res;
+}
+
+
+=head2 $s->copy_seg($seg)
+
+Creates a deep copy of a segment
+
+=cut
+
+sub copy_seg
+{
+ my ($self, $seg) = @_;
+ my ($p, $res);
+
+ $res = {};
+ $res->{'VAL'} = [@{$seg->{'VAL'}}];
+ foreach $p (keys %$seg)
+ { $res->{$p} = $seg->{$p} unless defined $res->{$p}; }
+ $res;
+}
+
+
+1;
+
+=head1 BUGS
+
+No known bugs.
+
+=head1 AUTHOR
+
+Martin Hosken Martin_Hosken@sil.org. See L<Font::TTF::Font> for copyright and
+licensing.
+
+=cut
+