The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package IO::SWF::Type::SHAPE;

use strict;
use warnings;

use base 'IO::SWF::Type';

use List::Util;
use IO::SWF::Bit;
use IO::SWF::Type::FILLSTYLEARRAY;
use IO::SWF::Type::LINESTYLEARRAY;

sub parse {
    my ($reader, $opts_href) = @_;
    my %opts = ref($opts_href) ? %{$opts_href} : ();
    my $tagCode = $opts{'tagCode'};
    my @shapeRecords = ();

    $reader->byteAlign();
    my $numFillBits = $reader->getUIBits(4);
    my $numLineBits = $reader->getUIBits(4);

    my $currentDrawingPositionX = 0;
    my $currentDrawingPositionY = 0;
    my $currentFillStyle0 = 0;
    my $currentFillStyle1 = 0;
    my $currentLineStyle = 0;
    my $done = 0;

    # ShapeRecords
    while (!$done) {
        my %shapeRecord = ();
        my $typeFlag = $reader->getUIBit();
        $shapeRecord{'TypeFlag'} = $typeFlag;
        if ($typeFlag == 0) {
            my $endOfShape = $reader->getUIBits(5);
            if ($endOfShape == 0) {
                # EndShapeRecord
                $shapeRecord{'EndOfShape'} = $endOfShape;
                $done = 1;
            } else {
                # StyleChangeRecord
                $reader->incrementOffset(0, -5);
                my $stateNewStyles = $reader->getUIBit();
                my $stateLineStyle = $reader->getUIBit();
                my $stateFillStyle1 = $reader->getUIBit();
                my $stateFillStyle0 = $reader->getUIBit();

                my $stateMoveTo = $reader->getUIBit();
                if ($stateMoveTo) {
                    my $moveBits = $reader->getUIBits(5);
#                        $shapeRecord{'(MoveBits)'} = $moveBits;
                    my $moveDeltaX = $reader->getSIBits($moveBits);
                    my $moveDeltaY = $reader->getSIBits($moveBits);
#                        $currentDrawingPositionX += $moveDeltaX;
#                        $currentDrawingPositionY += $moveDeltaY;
                    $currentDrawingPositionX = $moveDeltaX;
                    $currentDrawingPositionY = $moveDeltaY;
                    $shapeRecord{'MoveX'} = $currentDrawingPositionX;
                    $shapeRecord{'MoveY'} = $currentDrawingPositionY;
                }
                $shapeRecord{'MoveX'} = $currentDrawingPositionX;
                $shapeRecord{'MoveY'} = $currentDrawingPositionY;

                if ($stateFillStyle0) {
                    $currentFillStyle0 = $reader->getUIBits($numFillBits);
                }
                if ($stateFillStyle1) {
                    $currentFillStyle1 = $reader->getUIBits($numFillBits);
                }
                if ($stateLineStyle) {
                    $currentLineStyle = $reader->getUIBits($numLineBits);
                }
                $shapeRecord{'FillStyle0'} = $currentFillStyle0;
                $shapeRecord{'FillStyle1'} = $currentFillStyle1;
                $shapeRecord{'LineStyle'}  = $currentLineStyle;
                if ($stateNewStyles) {
                    my %opts = ('tagCode' => $tagCode);
                    $shapeRecord{'FillStyles'} = IO::SWF::Type::FILLSTYLEARRAY::parse($reader, \%opts);
                    $shapeRecord{'LineStyles'} = IO::SWF::Type::LINESTYLEARRAY::parse($reader, \%opts);
                    $reader->byteAlign();
                    $numFillBits = $reader->getUIBits(4);
                    $numLineBits = $reader->getUIBits(4);
                }
            }
        } else { # Edge records
            $shapeRecord{'StraightFlag'} = $reader->getUIBit();
            if ($shapeRecord{'StraightFlag'}) {
                # StraightEdgeRecord
                my $numBits = $reader->getUIBits(4);
#                    $shapeRecord{'(NumBits)'} = $numBits;
                my $generalLineFlag = $reader->getUIBit();
                my $vertLineFlag = 0;
                if ($generalLineFlag == 0) {
                    $vertLineFlag = $reader->getUIBit();
                }
                if ($generalLineFlag || ($vertLineFlag == 0)) {
                    my $deltaX = $reader->getSIBits($numBits + 2);
                    $currentDrawingPositionX += $deltaX;
                }
                if ($generalLineFlag || $vertLineFlag) {
                    my $deltaY = $reader->getSIBits($numBits + 2);
                    $currentDrawingPositionY += $deltaY;
                }
                $shapeRecord{'X'} = $currentDrawingPositionX;
                $shapeRecord{'Y'} = $currentDrawingPositionY;
            } else {
                # CurvedEdgeRecord
                my $numBits = $reader->getUIBits(4);
#                    $shapeRecord{'(NumBits)'} = $numBits;

                my $controlDeltaX = $reader->getSIBits($numBits + 2);
                my $controlDeltaY = $reader->getSIBits($numBits + 2);
                my $anchorDeltaX = $reader->getSIBits($numBits + 2);
                my $anchorDeltaY = $reader->getSIBits($numBits + 2);

                $currentDrawingPositionX += $controlDeltaX;
                $currentDrawingPositionY += $controlDeltaY;
                $shapeRecord{'ControlX'} = $currentDrawingPositionX;
                $shapeRecord{'ControlY'} = $currentDrawingPositionY;

                $currentDrawingPositionX += $anchorDeltaX;
                $currentDrawingPositionY += $anchorDeltaY;
                $shapeRecord{'AnchorX'} = $currentDrawingPositionX;
                $shapeRecord{'AnchorY'} = $currentDrawingPositionY;
            }
        }
        push @shapeRecords, \%shapeRecord;
    }
    return \@shapeRecords;
}

sub build {
    my ($writer, $shapeRecords_aref, $opts_href) = @_;
    my @shapeRecords = ref($shapeRecords_aref) ? @{$shapeRecords_aref} : ();
    my %opts = ref($opts_href) ? %{$opts_href} : ();

    my $tagCode = $opts{'tagCode'};
    my $fillStyleCount = $opts{'fillStyleCount'};
    my $lineStyleCount = $opts{'lineStyleCount'};
    my ($numFillBits, $numLineBits);
    if ($fillStyleCount == 0) {
        $numFillBits = 0;
    } else {
        # $fillStyleCount == fillStyle MaxValue because 'undefined' use 0
        $numFillBits = $writer->need_bits_unsigned($fillStyleCount);
    }
    if ($lineStyleCount == 0) {
        $numLineBits = 0;
    } else {
        # $lineStyleCount == lineStyle MaxValue because 'undefined' use 0
        $numLineBits = $writer->need_bits_unsigned($lineStyleCount);
    }

    $writer->byteAlign();
    $writer->putUIBits($numFillBits, 4);
    $writer->putUIBits($numLineBits, 4);
    my $currentDrawingPositionX = 0;
    my $currentDrawingPositionY = 0;
    my $currentFillStyle0 = 0;
    my $currentFillStyle1 = 0;
    my $currentLineStyle = 0;
my $counter = 0;
    foreach my $shapeRecord (@shapeRecords) {
        $counter++;
        my $typeFlag = $shapeRecord->{'TypeFlag'};
        $writer->putUIBit($typeFlag);
        if($typeFlag == 0) {
            if (defined ($shapeRecord->{'EndOfShape'}) && ($shapeRecord->{'EndOfShape'}) == 0) {
                # EndShapeRecord
                $writer->putUIBits(0, 5);
            } else {
                # StyleChangeRecord
                my $stateNewStyles  = defined $shapeRecord->{'FillStyles'} ? 1 : 0;
                my $stateLineStyle  = ($shapeRecord->{'LineStyle'}  != $currentLineStyle)  ? 1 : 0;
                my $stateFillStyle1 = ($shapeRecord->{'FillStyle1'} != $currentFillStyle1) ? 1 : 0;
                my $stateFillStyle0 = ($shapeRecord->{'FillStyle0'} != $currentFillStyle0) ? 1 : 0;

                $writer->putUIBit($stateNewStyles);
                $writer->putUIBit($stateLineStyle);
                $writer->putUIBit($stateFillStyle1);
                $writer->putUIBit($stateFillStyle0);

                my $stateMoveTo;
                if (($shapeRecord->{'MoveX'} != $currentDrawingPositionX) || ($shapeRecord->{'MoveY'} != $currentDrawingPositionY)) {
                    $stateMoveTo = 1;
                } else {
                    $stateMoveTo = 0;
                }
                $writer->putUIBit($stateMoveTo);
                if ($stateMoveTo) {
                    my $moveX = $shapeRecord->{'MoveX'};
                    my $moveY = $shapeRecord->{'MoveY'};
                    $currentDrawingPositionX = $moveX;
                    $currentDrawingPositionY = $moveY;
                    my $moveBits;
                    if ($moveX | $moveY) { 
                        my $XmoveBits = $writer->need_bits_signed($moveX);
                        my $YmoveBits = $writer->need_bits_signed($moveY);
                        $moveBits = List::Util::max($XmoveBits, $YmoveBits);
                    } else {
                        $moveBits = 0;
                    }
                    $writer->putUIBits($moveBits, 5);
                    $writer->putSIBits($moveX, $moveBits);
                    $writer->putSIBits($moveY, $moveBits);
                }
                if ($stateFillStyle0) {
                    $currentFillStyle0 = $shapeRecord->{'FillStyle0'};
                    $writer->putUIBits($currentFillStyle0, $numFillBits);
                }
                if ($stateFillStyle1) {
                    $currentFillStyle1 = $shapeRecord->{'FillStyle1'};
                    $writer->putUIBits($currentFillStyle1, $numFillBits);
                }
                if ($stateLineStyle) {
                    $currentLineStyle = $shapeRecord->{'LineStyle'};
                    $writer->putUIBits($currentLineStyle, $numLineBits);
                }
                if ($stateNewStyles) {
                    my %opts = ('tagCode' => $tagCode);
                    IO::SWF::Type::FILLSTYLEARRAY::build($writer, $shapeRecord->{'FillStyles'}, \%opts);
                    IO::SWF::Type::LINESTYLEARRAY::build($writer, $shapeRecord->{'LineStyles'}, \%opts);
                    $fillStyleCount = scalar(@{$shapeRecord->{'FillStyles'}});
                    if ($fillStyleCount == 0) {
                        $numFillBits = 0;
                    } else {
                        # $fillStyleCount == fillStyle MaxValue because 'undefined' use 0
                        $numFillBits = $writer->need_bits_unsigned($fillStyleCount);
                    }
                    if ($lineStyleCount == 0) {
                        $numLineBits = 0;
                    } else {
                        # $lineStyleCount == lineStyle MaxValue because 'undefined' use 0
                        $numLineBits = $writer->need_bits_unsigned($lineStyleCount);
                    }
                    $writer->byteAlign();
                    $writer->putUIBits($numFillBits, 4);
                    $writer->putUIBits($numLineBits, 4);
                }
            }
        } else {
            my $straightFlag = $shapeRecord->{'StraightFlag'};
            $writer->putUIBit($straightFlag);
            if ($straightFlag) {
                my $deltaX = $shapeRecord->{'X'} - $currentDrawingPositionX;
                my $deltaY = $shapeRecord->{'Y'} - $currentDrawingPositionY;
                my $numBits;
                if ($deltaX | $deltaY) {
                   my $XNumBits = $writer->need_bits_signed($deltaX);
                   my $YNumBits = $writer->need_bits_signed($deltaY);
                   $numBits = List::Util::max($XNumBits, $YNumBits);
                } else {
                    $numBits = 0;
                }
                if ($numBits < 2) {
                    $numBits = 2;
                }
                $writer->putUIBits($numBits - 2, 4);
                if ($deltaX && $deltaY) {
                    $writer->putUIBit(1); # GeneralLineFlag
                    $writer->putSIBits($deltaX, $numBits);
                    $writer->putSIBits($deltaY, $numBits);
                } else {
                    $writer->putUIBit(0); # GeneralLineFlag
                    if ($deltaX) {
                       $writer->putUIBit(0); # VertLineFlag
                       $writer->putSIBits($deltaX, $numBits);
                    } else {
                       $writer->putUIBit(1); # VertLineFlag
                       $writer->putSIBits($deltaY, $numBits);
                    }
                }
                $currentDrawingPositionX = $shapeRecord->{'X'};
                $currentDrawingPositionY = $shapeRecord->{'Y'};
            } else {
                my $controlDeltaX = $shapeRecord->{'ControlX'} - $currentDrawingPositionX;
                my $controlDeltaY = $shapeRecord->{'ControlY'} - $currentDrawingPositionY;
                $currentDrawingPositionX = $shapeRecord->{'ControlX'};
                $currentDrawingPositionY = $shapeRecord->{'ControlY'};
                my $anchorDeltaX = $shapeRecord->{'AnchorX'} - $currentDrawingPositionX;
                my $anchorDeltaY = $shapeRecord->{'AnchorY'} - $currentDrawingPositionY;
                $currentDrawingPositionX = $shapeRecord->{'AnchorX'};
                $currentDrawingPositionY = $shapeRecord->{'AnchorY'};

                my $numBitsControlDeltaX = $writer->need_bits_signed($controlDeltaX);
                my $numBitsControlDeltaY = $writer->need_bits_signed($controlDeltaY);
                my $numBitsAnchorDeltaX = $writer->need_bits_signed($anchorDeltaX);
                my $numBitsAnchorDeltaY = $writer->need_bits_signed($anchorDeltaY);
                my $numBits = List::Util::max($numBitsControlDeltaX, $numBitsControlDeltaY, $numBitsAnchorDeltaX, $numBitsAnchorDeltaY);
                if ($numBits < 2) {
                   $numBits = 2;
                }
                $writer->putUIBits($numBits - 2, 4);
                $writer->putSIBits($controlDeltaX, $numBits);
                $writer->putSIBits($controlDeltaY, $numBits);
                $writer->putSIBits($anchorDeltaX, $numBits);
                $writer->putSIBits($anchorDeltaY, $numBits);
            }
        }
    }
    return 1;
}

sub string {
    my ($shapeRecords_aref, $opts_href) = @_;
    my @shapeRecords = ref($shapeRecords_aref) ? @{$shapeRecords_aref} : ();
    my %opts = ref($opts_href) ? %{$opts_href} : ();

    my $tagCode = $opts{'tagCode'};
    foreach my $row (@shapeRecords) {
        my %shapeRecord = %{$row};
        my $typeFlag = $shapeRecord{'TypeFlag'};
        if ($typeFlag == 0) {
           if (defined ($shapeRecord{'EndOfShape'})) {
               last;
           } else {
               my $moveX = $shapeRecord{'MoveX'} / 20;
               my $moveY = $shapeRecord{'MoveY'} / 20;
               print "\tChangeStyle: MoveTo: ($moveX, $moveY)";
               my @style_list = ('FillStyle0', 'FillStyle1', 'LineStyle');
               print "  FillStyle: ".$shapeRecord{'FillStyle0'}."|".$shapeRecord{'FillStyle1'};
               print "  LineStyle: ".$shapeRecord{'LineStyle'}."\n";
               if (defined ($shapeRecord{'FillStyles'})) {
                   print "    FillStyles:\n";
                   print IO::SWF::Type::FILLSTYLEARRAY::string($shapeRecord{'FillStyles'}, \%opts);
               }
               if (defined ($shapeRecord{'LineStyles'})) {
                    print "    LineStyles:\n";
                    print IO::SWF::Type::LINESTYLEARRAY::string($shapeRecord{'LineStyles'}, \%opts);
               }
           }
        } else {
            my $straightFlag = $shapeRecord{'StraightFlag'};
            if ($straightFlag) {
                my $x = $shapeRecord{'X'} / 20;
                my $y = $shapeRecord{'Y'} / 20;
                print "\tStraightEdge: MoveTo: ($x, $y)\n";
            } else {
                my $controlX = $shapeRecord{'ControlX'} / 20;
                my $controlY = $shapeRecord{'ControlY'} / 20;
                my $anchorX = $shapeRecord{'AnchorX'} / 20;
                my $anchorY = $shapeRecord{'AnchorY'} / 20;
                print "\tCurvedEdge: MoveTo: Control($controlX, $controlY) Anchor($anchorX, $anchorY)\n";
            }
        }
    }
}

1;