Introduce src dir.
[librarian.git] / src / librarian / font-optimizer / ext / Font-TTF / lib / Font / TTF / Segarr.pm
diff --git a/src/librarian/font-optimizer/ext/Font-TTF/lib/Font/TTF/Segarr.pm b/src/librarian/font-optimizer/ext/Font-TTF/lib/Font/TTF/Segarr.pm
new file mode 100644 (file)
index 0000000..980188d
--- /dev/null
@@ -0,0 +1,376 @@
+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
+