The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package CAM::PDF::GS;

use 5.006;
use strict;
use warnings;
use base qw(CAM::PDF::GS::NoText);

our $VERSION = '1.59';

=for stopwords subclasses

=head1 NAME

CAM::PDF::GS - PDF graphic state

=head1 LICENSE

See CAM::PDF.

=head1 SYNOPSIS

    use CAM::PDF;
    my $pdf = CAM::PDF->new($filename);
    my $contentTree = $pdf->getPageContentTree(4);
    my $gs = $contentTree->computeGS();

=head1 DESCRIPTION

This class is used to represent the graphic state at a point in the
rendering flow of a PDF page.  Much of the functionality is actually
based in the parent class, CAM::PDF::GS::NoText.

Subclasses that want to do something useful with text should override
the renderText() method.

=head1 CONVERSION FUNCTIONS

=over

=item $self->getCoords($node)

Computes device coordinates for the specified node.  This implementation
handles text-printing nodes, and hands all other types to the
superclass.

=cut

my %text_cmds = map {$_ => 1} qw(TJ Tj quote doublequote);

sub getCoords
{
   my $self = shift;
   my $node = shift;

   if ($text_cmds{$node->{name}})
   {
      ## no critic (Bangs::ProhibitNumberedNames)
      my ($x1,$y1) = $self->userToDevice(@{$self->{last}});
      my ($x2,$y2) = $self->userToDevice(@{$self->{current}});
      return ($x1,$y1,$x2,$y2);
   }
   else
   {
      return $self->SUPER::getCoords($node);
   }
}

=item $self->textToUser($x, $y)

Convert text coordinates (C<Tm>) to user coordinates.  Returns the
converted X and Y.

=cut

sub textToUser
{
   my $self = shift;
   my $x = shift;
   my $y = shift;

   return $self->dot($self->{Tm}, $x, $y);

=for oldcode
   ## PDF Ref page 313
   #my $tf = [$self->{Tfs}*$self->{Tz}, 0,
   #          0,                        $self->{Tfs},
   #          0,                        $self->{Ts}];
   #return $self->dot($self->{Tm}, $self->dot($tf, $x, $y));

=cut

}

=item $self->textToDevice($x, $y)

Convert text coordinates (C<Tm>) to device coordinates.  Returns
the converted X and Y.

=cut

sub textToDevice
{
   my $self = shift;
   my $x = shift;
   my $y = shift;

   return $self->userToDevice($self->textToUser($x, $y));
}

=item $self->textLineToUser($x, $y)

Convert text coordinates (C<Tlm>) to user coordinates.  Returns
the converted X and Y.

=cut

sub textLineToUser
{
   my $self = shift;
   my $x = shift;
   my $y = shift;

   return $self->dot($self->{Tlm}, $x, $y);
}

=item $self->textLineToDevice($x, $y)

Convert text coordinates (C<Tlm>) to device coordinates.
Returns the converted X and Y.

=cut

sub textLineToDevice
{
   my $self = shift;
   my $x = shift;
   my $y = shift;

   return $self->userToDevice($self->textLineToUser($x, $y));
}

=item $self->renderText($string, $width)

A general method for rendering strings, from C<Tj> or C<TJ>.  This is a
no-op, but subclasses may override.

=cut

sub renderText
{
   my $self = shift;
   my $string = shift;
   my $width = shift;

   # noop, override in subclasses
   return;
}

=item $self->Tadvance($width)

Move the text cursor.

=cut

sub Tadvance
{
   my $self = shift;
   my $width = shift;

   my $tx = 0;
   my $ty = 0;
   if ($self->{wm} == 0)
   {
      $tx = ($width * $self->{Tfs} + $self->{Tc} + $self->{Tw}) * $self->{Tz};
   }
   else
   {
      $ty = $width * $self->{Tfs} + $self->{Tc} + $self->{Tw};
   }
   $self->{moved}->[0] += $tx;
   $self->{moved}->[1] += $ty;

   $self->applyMatrix([1,0,0,1,$tx,$ty], $self->{Tm});
   return;
}

=back

=head1 DATA FUNCTIONS

=over

=item $self->BT()

=cut

sub BT
{
   my $self = shift;

   @{$self->{Tm}} = (1, 0, 0, 1, 0, 0);
   @{$self->{Tlm}} = (1, 0, 0, 1, 0, 0);
   return;
}

=item $self->Tf($fontname, $fontsize)

=cut

sub Tf
{
   my $self = shift;
   my $fontname = shift;
   my $fontsize = shift;

   $self->{Tf} = $fontname;
   $self->{Tfs} = $fontsize;
   $self->{refs}->{fm} = $self->{refs}->{doc}->getFontMetrics($self->{refs}->{properties}, $fontname);

   # TODO: support vertical text mode (wm = 1)
   $self->{wm} = 0;
   return;
}

=item $self->Tstar()

=cut

sub Tstar
{
   my $self = shift;

   $self->Td(0, -$self->{TL});
   return;
}

=item $self->Tz($scale)

=cut

sub Tz
{
   my $self = shift;
   my $scale = shift;

   $self->{Tz} = $scale/100.0;
   return;
}

=item $self->Td($x, $y)

=cut

sub Td
{
   my $self = shift;
   my $x = shift;
   my $y = shift;

   $self->applyMatrix([1,0,0,1,$x,$y], $self->{Tlm});
   @{$self->{Tm}} = @{$self->{Tlm}};
   return;
}

=item $self->TD($x, $y)

=cut

sub TD
{
   my $self = shift;
   my $x = shift;
   my $y = shift;

   $self->TL(-$y);
   $self->Td($x,$y);
   return;
}

=item $self->Tj($string)

=cut

sub Tj
{
   my $self = shift;
   my $string = shift;

   @{$self->{last}} = $self->textToUser(0,0);
   $self->_Tj($string);
   @{$self->{current}} = $self->textToUser(0,0);
   return;
}

sub _Tj
{
   my $self = shift;
   my $string = shift;

   if (!$self->{refs}->{fm})
   {
      die "No font metrics for font $self->{Tf}";
   }

   my @parts;
   if ($self->{mode} eq 'c' || $self->{wm} == 1)
   {
      @parts = split m//xms, $string;
   }
   else
   {
      @parts = ($string);
   }
   foreach my $substr (@parts)
   {
      my $dw = $self->{refs}->{doc}->getStringWidth($self->{refs}->{fm}, $substr);
      $self->renderText($substr, $dw);
      $self->Tadvance($dw);
   }
   return;
}

=item $self->TJ($arrayref)

=cut

sub TJ
{
   my $self = shift;
   my $array = shift;

   @{$self->{last}} = $self->textToUser(0,0);
   foreach my $node (@{$array})
   {
      if ($node->{type} eq 'number')
      {
         my $dw = -$node->{value} / 1000.0;
         $self->Tadvance($dw);
      }
      else
      {
         $self->_Tj($node->{value});
      }
   }
   @{$self->{current}} = $self->textToUser(0,0);
   return;
}

=item $self->quote($string)

=cut

sub quote
{
   my $self = shift;
   my $string = shift;

   @{$self->{last}} = $self->textToUser(0,0);
   $self->Tstar();
   $self->_Tj($string);
   @{$self->{current}} = $self->textToUser(0,0);
   return;
}

=item $self->doublequote($tw, $tc, $string)

=cut

sub doublequote
{
   my $self = shift;
   $self->{Tw} = shift;
   $self->{Tc} = shift;
   my $string = shift;

   $self->quote($string);
   return;
}

=item $self->Tm($m1, $m2, $m3, $m4, $m5, $m6)

=cut

sub Tm
{
   my ($self, @tm) = @_;

   @{$self->{Tm}} = @{$self->{Tlm}} = @tm;
   return;
}

1;
__END__

=back

=head1 AUTHOR

See L<CAM::PDF>

=cut