=pod
=head1 NAME
examples/tiger.pl - draw eps file
=head1 FEATURES
Demonstrates capabilities of C<Prima::Drawable::Path> - curves, lines, and
matrix transformations, similar to those in the PostScript language. The PS
interpreter is very dumb and minimal, to parse F<tiger.eps> file only.
=cut
use strict;
use warnings;
use FindBin qw($Bin);
use Prima qw(Application);
my ( $device, %globals, %sym, %def, $line, @stack, @code_stack, @array_stack, @graphics_stack);
my $debug = 0;
use constant CLASS => 0;
use constant VALUE => 1;
$SIG{__DIE__} = sub {
print "stack:\n";
print "* @$_\n" for @stack;
die @_;
};
sub ntok($$) { [ @_ ] }
sub spush($) { push @stack, $_[0] }
sub spushn($) { spush ntok num => shift }
sub spop { pop @stack or die "stack undeflow\n" }
sub spopx($)
{
my ($pop, $class) = (spop, shift);
die "$class expected\n" unless $pop->[CLASS] eq $class;
return $pop->[VALUE];
}
sub spopn { spopx 'num' }
sub spopy { spopx 'sym' }
sub spopc { spopx 'code' }
sub spopnx($) { reverse ( map { spopn } 1 .. $_[0] ) }
sub gc { $graphics_stack[-1] }
sub newpath {
my $m = gc->{path}->last_matrix;
gc->{path} = $device->new_path;
gc->{path}->matrix( @$m ) if $m;
gc->{path_actual} = 0;
}
sub path { gc->{path} }
sub execute
{
my $code = shift;
for my $t ( @$code ) {
if ( $debug ) {
my $ns = @stack;
print STDERR "$ns:$t->[CLASS] $t->[VALUE]\n";
if ( $debug > 1 ) {
for ( @stack ) {
print STDERR " * $_->[CLASS] $_->[VALUE]\n";
}
}
}
if ( $t->[CLASS] =~ /^(id|num|sym|code|array|dict)/) {
push @stack, $t;
} elsif ( $t->[CLASS] eq 'fun') {
my $m = $t->[VALUE];
main->can($m)->();
} elsif ( $t->[CLASS] eq 'call' ) {
my $d = $def{ $t->[VALUE] };
die "$t->[VALUE] is undefined\n" unless $d;
execute($d->[VALUE]);
} else {
die "Unknown opcode $t->[CLASS]\n";
}
}
}
sub __log { use Data::Dumper; print STDERR Dumper(spop) }
sub _add { spushn spopn + spopn }
sub _begin { }
sub _bind { }
sub _cleartomark { }
sub _closepath { path->close }
sub _copy {
my $n = spopn;
return if $n < 1;
push @stack, ((splice( @stack, -$n )) x 2);
}
sub _countdictstack { spushn 0 }
sub _currentflat { spushn 24 }
sub _currentpoint {
spushn gc->{point}->[0];
spushn gc->{point}->[1];
}
sub _curveto {
gc->{path_actual} = 1;
my @p = spopnx 6;
path->spline( [ @{ gc->{point} }, @p ], degree => 3 );
gc->{point} = [ @p[4,5] ];
}
sub _def {
my $v = spop;
$v = ntok code => [$v] unless $v->[CLASS] eq 'code';
$def{spopy()} = $v;
}
sub _dict { spopn; spush ntok dict => {} }
sub _dup { my $p = spop; spush $p; spush $p }
sub _end { }
sub _eq {
my ( $y, $x ) = ( spop, spop );
spushn $x->[CLASS] eq $x->[CLASS] && $x->[VALUE] eq $y->[VALUE];
}
sub _exch { push @stack, spop, spop }
sub _fill { path-> fill if gc->{path_actual}; newpath }
sub _get { spush ntok fun => "_" . spopy }
sub _grestore {
pop @graphics_stack;
$device->lineWidth(gc->{lw});
$device->color(gc->{color});
}
sub _gsave {
my %last = %{ gc() };
$last{path} = $last{path}->dup;
$last{point} = [ @{$last{point}} ];
push @graphics_stack, \%last;
}
sub _if {
my ( $code, $bool ) = ( spopc, spopn );
execute($code) if $bool;
}
sub _ifelse {
my ( $code2, $code1, $bool ) = ( spopc, spopc, spopn );
execute($bool ? $code1 : $code2);
}
sub _index { spush ($stack[ spopn ] or die "No such value at index") }
sub _itransform { }
sub _lineto {
unless ( gc->{path_actual}) {
path->line( @{ gc->{point} });
gc->{path_actual} = 1;
}
path->line( @{gc->{point}} = spopnx 2 );
}
sub _load {
my $arg = spopy;
return spush $def{$arg} if exists $def{$arg};
return spush ntok fun => "_$arg" if main->can("_$arg");
die "No such symbol: $arg\n";
}
sub _lt { spushn( spopn() >= spopn()) }
sub _mark { }
sub _moveto {
newpath if gc->{path_actual};
gc->{point} = [spopnx 2];
}
sub _neg { spushn - spopn }
sub _newpath { newpath }
sub _pop { spop }
sub _repeat { my ($c, $n) = (spopc, spopn); execute($c) for 1..$n }
sub _restore { }
sub _roll {
my ( $n, $j ) = spopnx 2;
return unless $j;
my @r = splice( @stack, -$n );
if ( $j < 0 ) {
push @r, shift @r for 1 .. -$j;
} else {
unshift @r, pop @r for 1 .. $j;
}
push @stack, @r;
}
sub _round { spushn int spopn }
sub _save { }
sub _scale { path->scale(spopnx 2) }
sub _setdash { spop, spop }
sub _setflat { spopn }
sub _setgray {$device->color(gc->{color} = int( 255 * spopn ) * ( 65536 + 256 + 1 )) }
sub _setmiterlimit { spopn }
sub _setlinecap { spopn }
sub _setlinejoin { spopn }
sub _setlinewidth { $device->lineWidth( gc->{lw} = spopn ) }
sub _setcmybcolor {
my @k = map { 1 - spopn } 0 .. 3;
my @c = map { int 255 * $k[0] * $_ } @k[1..3];
$device->color(gc->{color} = ($c[2] << 16) | ($c[1] << 8) | $c[0]);
}
sub _setrgbcolor {
my @c = map { int( 255 * spopn ) } 0 .. 2;
$device->color(gc->{color} = ($c[2] << 16) | ($c[1] << 8) | $c[0]);
}
sub _showpage { }
sub _stroke { path->stroke if gc->{path_actual}; newpath }
sub _sub { spushn( spopn - spopn )}
sub _transform { }
sub _translate { path->translate(spopnx 2) }
sub _where { spushn(main->can('_' . spopy) // 0) }
sub tok
{{
if ( m/\G([_a-z][\.\w]*)/gcsi ) {
return ntok call => $1 if exists $sym{$1};
return ntok fun => "_$1" if main->can("_$1");
die "Unknown identifier: $1 at line $line\n";
}
m/\G([+-]?(?:\.\d+|\d+(?:\.\d+)?))/gcs and return ntok num => $1;
m/\G\/([\.\w]+)/gcs and return $sym{$1} = ntok sym => $1;
m/\G\{/gcs and return ntok code_begin => undef;
m/\G\}/gcs and return ntok code_end => undef;
m/\G\[/gcs and return ntok arr_begin => undef;
m/\G\]/gcs and return ntok arr_end => undef;
m/\G\s+/gcs and redo;
m/\G$/gcs and return;
m/\G(.*)/gcs;
die "Unknown token: $1 at line $line\n";
}}
# parse
my $f = "$Bin/tiger.eps";
open F, "<", $f or die "cannot open $f:$!\n";
$line = 0;
push @code_stack, [];
while (<F>) {
$line++;
chomp;
if ( m/^%(.)(.*)/ ) {
my ( $s, $t ) = ( $1, $2 );
if ( $s eq '%' ) {
my $v;
($t, $v) = ($1, $2) if $t =~ /(.*)\s*\:\s*(.*)/;
$globals{$t} = $v;
}
next;
}
s/^\s+//;
s/\s*$//;
next unless length;
while ( my $t = tok ) {
if ( $t->[CLASS] =~ /^(id|num|call|fun|sym)/) {
push @{ $code_stack[-1] }, $t;
} elsif ( $t->[CLASS] eq 'code_begin' ) {
push @code_stack, [];
} elsif ( $t->[CLASS] eq 'code_end' ) {
@code_stack > 1 or die "Unexpected } at line $line\n";
my $nt = ntok code => pop(@code_stack);
push @{ $code_stack[-1] }, $nt;
} elsif ( $t->[CLASS] eq 'arr_begin' ) {
push @code_stack, [];
push @array_stack, 1;
} elsif ( $t->[CLASS] eq 'arr_end' ) {
pop @array_stack or die "Unexpected ] at line $line\n";
push @{ $code_stack[-1] }, ntok array => pop(@code_stack);
} else {
die "Unknown token $t->[CLASS] at line $line\n";
}
}
}
close F;
die "Unclosed array\n" if @array_stack;
die "Unclosed code\n" if 1 < @code_stack;
die "No code\n" unless @code_stack;
# execute
my @bb = split ' ', ($globals{BoundingBox} // '0 0 1000 1000');
#$device = Prima::DeviceBitmap->new(
# type => dbt::Pixmap,
# width => $bb[2] - $bb[0],
# height => $bb[3] - $bb[1],
# backColor => cl::White,
# color => cl::Black,
#);
#$device->clear;
#$device->translate( -$bb[0], -$bb[1]);
#execute( $code_stack[0] );
sub init
{
@graphics_stack = ({
color => 0,
point => [0,0],
path => $device->new_path,
path_actual => 0,
lw => 0,
});
@stack = ();
}
my $w = Prima::MainWindow->new(
text => 'Tiger',
backColor => cl::White,
color => cl::Black,
onPaint => sub {
my ( $self, $canvas ) = @_;
$canvas->clear;
my @sz = $canvas-> size;
my @ps = ( $bb[2] - $bb[0], $bb[3] - $bb[1] );
my @ratios = map { $sz[$_] / $ps[$_] } 0, 1;
my ( $dx, $dy ) = (0,0);
my $r;
if ( $ratios[0] < $ratios[1] ) {
$r = $ratios[0];
$dy = ( $sz[1] - $ps[1] * $r ) / 2;
} else {
$r = $ratios[1];
$dx = ( $sz[0] - $ps[0] * $r ) / 2;
}
$device = $canvas;
$device->translate( $dx - $r * $bb[0], $dy - $r * $bb[1]);
init;
path->scale($r);
execute( $code_stack[0] );
}
);
run Prima;