#  Copyright (c) 1997-2006
#  Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
#  http://www.math.tu-berlin.de/polymake,  mailto:polymake@math.tu-berlin.de
#
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License as published by the
#  Free Software Foundation; either version 2, or (at your option) any
#  later version: http://www.gnu.org/licenses/gpl.txt.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#-----------------------------------------------------------------------------
#  $Project: polymake $$Id: Postscript.pm 7495 2006-12-11 11:03:05Z gawrilow $

no integer;

package Postscript;

# the average ratio of the glyph weight and height
declare $avg_char_width=0.7;

################################################################################
# useful small utilities

sub draw_poly_line {
   my $first=shift;
   if (is_array($first)) {
      " newpath @$first moveto " . join(" ", map { "@$_ lineto" } @_) . " "
   } else {
      " newpath $first moveto " . join(" ", map { "$_ lineto" } @_) . " "
   }
}

################################################################################
#
#  an abstract base class for various drawables (Graph, Polygon, Gale, etc.)
#
package Postscript::Element;

use Struct (
   [ '$marginLeft'   => 'undef' ],
   [ '$marginRight'  => 'undef' ],
   [ '$marginBottom' => 'undef' ],
   [ '$marginTop'    => 'undef' ],
   [ '$minX' => 'undef' ],
   [ '$maxX' => 'undef' ],
   [ '$minY' => 'undef' ],
   [ '$maxY' => 'undef' ],
   [ '$locked' => '1' ],	# the scale for X and Y dimensions must be the same
);

################################################################################

package Postscript::Page;

use Struct (
   [ new => '$' ],
   [ '@ISA' => 'Postscript::Element' ],
   [ '$locked' => '0' ],
   '$scaleX',
   '$scaleY',
   [ '$canvas_width' => '$Wpaper-2*$Wmargin' ],
   [ '$canvas_height' => '$Hpaper-2*$Hmargin' ],
   [ '$title' => '#1' ],
   '%procsets',
   '$setup',
   '%dict',
   '$code',
   '@elements',
);

# real world coordinates => PostScript sheet coordinates
sub transform {
   no integer;
   my $self=shift;
   my @xy=( $self->scaleX * ($_[0] - $self->minX) + $self->marginLeft,
	    $self->scaleY * ($_[1] - $self->minY) + $self->marginBottom );
   wantarray ? @xy : "@xy"
}

sub finish {
   no integer;
   my ($self)=@_;
   return unless @{$self->elements};

   foreach my $e (@{$self->elements}) {
      assign_max($self->marginLeft,   $e->marginLeft);
      assign_max($self->marginRight,  $e->marginRight);
      assign_max($self->marginTop,    $e->marginTop);
      assign_max($self->marginBottom, $e->marginBottom);
      assign_min($self->minX,  $e->minX);
      assign_max($self->maxX,  $e->maxX);
      assign_min($self->minY,  $e->minY);
      assign_max($self->maxY,  $e->maxY);
      $self->locked ||= $e->locked;
   }

   $self->scaleX= $self->maxX == $self->minX
                  ? 0
		  : ($self->canvas_width - $self->marginLeft - $self->marginRight) /
                    ($self->maxX - $self->minX);
   $self->scaleY= $self->maxY == $self->minY
                  ? 0
		  : ($self->canvas_height - $self->marginTop - $self->marginBottom) /
                    ($self->maxY - $self->minY);
   if ($self->locked) {
      if ($self->scaleX  and  !$self->scaleY || $self->scaleX <= $self->scaleY) {
	 $self->scaleY=$self->scaleX;
	 $self->canvas_height= $self->scaleY*($self->maxY-$self->minY) +
                               $self->marginBottom + $self->marginTop;
      } else {
	 $self->scaleX=$self->scaleY;
	 $self->canvas_width= $self->scaleX*($self->maxX-$self->minX) +
                              $self->marginLeft + $self->marginRight;
      }
   } else {
      if (!$self->scaleX) {
	 $self->canvas_width= $self->marginLeft + $self->marginRight;
      }
      if (!$self->scaleY) {
	 $self->canvas_height= $self->marginBottom + $self->marginTop;
      }
   }

   $self->marginLeft += $Wmargin;
   $self->marginBottom += $Hmargin;

   foreach my $e (@{$self->elements}) {
      $e->draw($self);
   }
   @{$self->elements}=();
}

sub print_it {
   my ($self, $file, $ord)=@_;
   my @bb=( $Wmargin, $Hmargin,
	    $Wmargin+$self->canvas_width, $Hmargin+$self->canvas_height );
   my $title=$self->title;
   print $file <<".";
%%Page: $title $ord
%%PageBoundingBox: @bb
%%BeginPageSetup
/$fontname $fontsize selectfont
$line_width setlinewidth
0 setgray
.
   print $file $self->setup;
   my $dictsize=keys %{$self->dict};
   if ($dictsize) {
      print $file "$dictsize dict begin\n",
                  map { "/$_ " . $self->dict->{$_} . " def\n" } keys %{$self->dict};
   }
   my $code=$self->code;
   print $file <<".";
%%EndPageSetup
$code
showpage
.
   print $file "end\n" if $dictsize;
}

###########################################################################
package Postscript::File;

use Struct (
   [ new => '$' ],
   [ '$workfile' => '#1' ],
   '$title',
   '@pages',
   '%procsets',		# 'name' => 'text'
   '@procset_names',	# 'name'
);

sub encode_text {
   if ($_[0] =~ /\s/ || /^\d*$/ || /^\(/) {
      $_[0]="($_[0])";
   }
}

sub finish_page {
   my ($self)=@_;
   return unless @{$self->pages};
   my $page=$self->pages->[-1];
   $page->finish;

   if (length(my $t=$page->title)) {
      encode_text($t);
      $page->title=$t;
      $self->title ||= $t;
   } else {
      $page->title=scalar(@{$self->pages});
   }

   while (my ($name, $code)=each %{$page->procsets}) {
      $self->procsets->{$name} ||= do {
	 push @{$self->procset_names}, $name;
	 $code
      }
   }
   %{$page->procsets}=();
}

sub new_page {
   my $self=shift;
   finish_page($self);
   push @{$self->pages}, new Postscript::Page(@_);
}

sub print_it {
   my ($self)=@_;
   finish_page($self);

   open my $file, ">".$self->workfile
      or croak( "can't create working file ", $self->workfile, ": $!\n" );

   my $n_pages=@{$self->pages};
   my $title=$self->title;
   print $file <<".";
%!PS-Adobe-3.0
%%Creator: polymake
%%Title: $title
%%Pages: $n_pages
%%BoundingBox: (atend)
.
   if (my $declare_procsets=join("%%+", map { " procset $_ 0\n" } @{$self->procset_names})) {
      print $file "%%DocumentSuppliedResources: $declare_procsets";
   }
   print $file <<".";
%%EndComments
%%BeginProlog
.
   foreach my $pn (@{$self->procset_names}) {
      my $ps=$self->procsets->{$pn};
      print $file <<".";
%%BeginResource: procset $pn 0
$ps
%%EndResource
.
   }
   print $file <<".";
%%EndProlog
.

   my $ord=1;
   my ($bbw, $bbh)=(0, 0);
   foreach my $p (@{$self->pages}) {
      assign_max($bbw, $p->canvas_width);
      assign_max($bbh, $p->canvas_height);
      $p->print_it($file, $ord++);
   }

   $bbw += $Wmargin;
   $bbh += $Hmargin;
   print $file <<".";
%%Trailer
%%BoundingBox: $Wmargin $Hmargin $bbw $bbh
%%EOF
.
   close $file;
}

###########################################################################
#
#  Drawing of some graphical primitives defined in modules::common
#

package Postscript::PointSet;

use Struct (
   [ new => '$' ],
   [ '@ISA' => 'Postscript::Element' ],
   [ '$source' => '#1' ],
   '@coords',
   '@radius',
);

sub init {
   no integer;
   my $self=shift;
   my $P=$self->source;
   @{$self->coords} = map { [ ( /\S+/g )[0,1] ] } @{$P->Vertices};	# chop the z coordinate if any
   ($self->minX, $self->minY)=($self->maxX, $self->maxY)=@{$self->coords->[0]};
   my $last_point=$#{$self->coords};

   my ($labelwidth, $max_radius);
   if (my $get_style=$P->VertexStyle) {
      if (is_code($get_style)) {
	 @{$self->radius}=map {
	    my $s=$get_style->($_);
	    if ($s =~ $Visual::thickness_re) {
	       assign_max($max_radius,$point_radius*$1/2);
	       $point_radius*$1/2;
	    } else {
	       $s !~ $Visual::hidden_re && $point_radius;
	    }
	 } 0..$last_point;
      } else {
	 if ($get_style =~ $Visual::thickness_re) {
	    $max_radius=$point_radius*$1/2;
	 } elsif ($get_style !~ $Visual::hidden_re) {
	    $max_radius=$point_radius;
	 }
      }
   } else {
      $max_radius=$point_radius;
   }
   if (defined($max_radius) && !@{$self->radius}) {
      @{$self->radius}=($max_radius) x ($last_point+1);
   }

   foreach my $coord (@{$self->coords}) {
      assign_min_max($self->minX, $self->maxX, $coord->[0]);
      assign_min_max($self->minY, $self->maxY, $coord->[1]);
   }
   if ($P->VertexLabels) {
      map { assign_max($labelwidth, length($P->VertexLabels->($_))) } 0..$#{$self->coords};
   }
   return ($labelwidth, $max_radius);
}

sub new {
   my $self=&_new;
   my ($labelwidth, $max_radius)=$self->init;
   $self->marginLeft=$self->marginRight=
      max($max_radius, ($avg_char_width * $fontsize*$labelwidth)/2) + $text_spacing/2;
   $self->marginBottom= $max_radius + $text_spacing/2;
   $self->marginTop=$self->marginBottom + $fontsize + $text_spacing;
   $self;
}

sub draw_points {
   my ($self, $page)=@_;
   my $P=$self->source;
   my $get_color=$P->VertexColor;
   my @color;
   if (!is_code($get_color)) {
      @color=Visual::parse_color($get_color);
      undef $get_color;
   }
   if (!@color) {
      @color=Visual::parse_color($Visual::Color::vertices);
   }
   my $default_color=join(" ", RGB2float(@color[2..4]));

   for (my ($p, $last_point)=(0, $#{$self->coords}); $p<=$last_point; ++$p) {
      my $r=$self->radius->[$p] or next;
      $page->code .= ($get_color && (@color=Visual::parse_color($get_color->($p))) ? RGB2float(@color[2..4]) : $default_color) .
                     " $r (" . ($P->VertexLabels ? $P->VertexLabels->($p) : " ") . ") " . join(" ", @{$self->coords->[$p]}) . " Point\n";
   }
}

sub draw_lines { }

sub draw {
   my ($self, $page)=@_;
   foreach my $p (@{$self->coords}) {
      @$p=$page->transform(@$p);
   }
   $self->draw_lines($page);
   $self->draw_points($page) if @{$self->radius};
}

###########################################################################
package Postscript::Polygon;

use Struct [ '@ISA' => 'Postscript::PointSet' ];

sub draw_facet {
   my ($facet_color, $facet_style, $edge_color, $edge_style, $points)=@_;
   my $fill=1;
   if (my @color=Visual::parse_color($facet_color)) {
      $facet_color=RGB2float(@color[2..4]);
      $fill=!$color[0];
   }
   if ($facet_style =~ $Visual::hidden_re) {
      $fill=0;
   }
   if (my @color=Visual::parse_color($edge_color)) {
      $edge_color=RGB2float(@color[2..4]);
   } elsif (!$fill) {
      $edge_color=$facet_color;
   }
   my $lw;
   if ($edge_style =~ $Visual::hidden_re) {
      $lw=0;
   } elsif ($edge_style =~ $Visual::thickness_re) {
      $lw=$1*$line_width;
   } elsif ($line_width != 1) {
      $lw=$line_width;
   }

   my $gsave=($lw || !defined($lw) && $edge_color) && "gsave";
   my $code=$gsave . draw_poly_line(@$points) . "closepath\n";
   if ($fill) {
      $code .= <<".";
gsave $facet_color setrgbcolor eofill grestore
.
   }
   if (!defined($lw) || $lw) {
      if ($edge_color) {
         $code .= "$edge_color setrgbcolor ";
      }
      if ($lw) {
         $code .= "$lw setlinewidth ";
      }
      $code .= "stroke\n";
   }
   $code .= $gsave && "grestore\n";
}

sub draw_lines {
   my ($self, $page)=@_;
   my $P=$self->source;
   my $facet=$P->Facet;
   $page->code .= draw_facet( (map { $P->$_ } qw( FacetColor FacetStyle EdgeColor EdgeStyle )),
			      $facet ? subset($self->coords, $facet =~ /\d+/g)
			             : $self->coords);
}

###########################################################################
package Postscript::Polygons;

use Struct [ '@ISA' => 'Postscript::Polygon' ];

sub draw_lines {
   my ($self, $page)=@_;
   my $P=$self->source;
   my $i=0;
   foreach my $polygon (@{$P->Polygons}) {
      $page->code .= Postscript::Polygon::draw_facet( (map { my $decor=$P->$_; is_code($decor) ? $decor->($i) : $decor }
						           qw( FacetColor FacetStyle EdgeColor EdgeStyle )),
						      subset($self->coords, $polygon->Facet =~ /\d+/g));
      ++$i;
   }
}

###########################################################################

package Postscript::Page;

my $pointset_procs=<<'.';
% R G B raduis (label) x y ->
/Point {
   gsave translate
   dup stringwidth pop 2 div neg  2 index text_spacing add  moveto show
   newpath 0 0 3 -1 roll 0 360 arc
   gsave setrgbcolor fill grestore stroke
   grestore
} def
.

sub addPointSet {
   my ($self, $elem)=@_;
   $self->title ||= $elem->source->name;
   push @{$self->elements}, $elem;
   $self->procsets->{'Common::pointset'}=$pointset_procs;
   $self->dict->{text_spacing}=$text_spacing;
}

1

# Local Variables:
# c-basic-offset:3
# End:
