#!/usr/bin/perl

# Copyright 2000-2007 Robert Krawitz <rlk@alum.mit.edu>
#
#   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 of the License, or (at your option)
#   any later version.
#
#   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.
#
#   You should have received a copy of the GNU General Public License
#   along with this program; if not, write to the Free Software
#   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

use Getopt::Std;
use strict;

getopts('VvO:');

use vars qw($atend
	    $stuff
	    $opt_v
	    $opt_V
	    $opt_O
	    $curpos
	    $esc
	    $page_mgmt_unit
	    $horizontal_position
	    $horizontal_unit
	    $vertical_unit
	    $vertical_position
	    $raster_x
	    $raster_y
	    $print_offsets
	    %seqtable
	    @offsets);

$atend = 0;

%seqtable = ( "@", 0,
	      "(R", "REMOTE",
	      "(", "VARIABLE",
	      "U", 1,
	      "\\", 2,
	      "\$", 2,
	      "r", 1,
	      "\031", 1,
	      ".", "SPECIAL",
	      "i", "SPECIAL1",
	      "\000", 2,
	      "\001", 22
	  );

$esc = "\033";

$curpos = 0;

$page_mgmt_unit = 360;
$horizontal_unit = 180;
$vertical_unit = 360;

$vertical_position = 0;
$horizontal_position = 0;
$print_offsets = 0;

sub get_stuff($) {
    my ($where) = @_;
    my ($end) = length $stuff;
    if ($curpos == 0 && $end == 0) {
	$stuff = <>;		# Need to do this once to "activate" ARGV
	$end = length $stuff;
    }
    my ($old_end) = length $stuff;
    my ($tmp);
    my ($bytes_to_read) = 4096;
    if ($where - $end > $bytes_to_read) {
	$bytes_to_read = $where - $end;
    }
    while ($end < $where) {
	my $foo = read ARGV, $tmp, $bytes_to_read;
#	print STDERR length $tmp, " ", length $stuff, " $curpos $where $bytes_to_read $foo $!", "\n";
	$stuff .= $tmp;
#	print STDERR length $tmp, " ", length $stuff, " $curpos $where $bytes_to_read $foo $!", "\n\n";
	$end = length $stuff;
	if ($old_end == $end) {
	    $atend = 1;
	    return 0;
	} else {
	    $bytes_to_read -= $end - $old_end;
	    $old_end = $end;
	}
    }
    return 1;
}

sub increment_curpos($) {
    my ($curpos_increment) = @_;
    substr($stuff, 0, $curpos_increment) = "";
    $curpos += $curpos_increment;
}

sub do_remote_command() {
    print "\n";
    printf "%08x  ", $curpos;
    print "1b  (  R ";
    increment_curpos(3);
    get_stuff(2);
    my $lchar = substr($stuff, 0, 1);
    my $nlchar = unpack("C", $lchar);
    my $hchar = substr($stuff, 1, 1);
    my $nhchar = unpack("C", $hchar);
    printf "%02x %02x ", $nlchar, $nhchar;
    my $skipchars = ($nhchar * 256) + $nlchar;
    increment_curpos(2);
    my $i;
    my $char;
    my $nchar;
    get_stuff($skipchars);
    for ($i = 0; $i < $skipchars; $i++) {
	$char = substr($stuff, $i, 1);
	$nchar = unpack("C", $char);
	if ($nchar >= 32 && $nchar < 127) {
	    print $char;
	} else {
	    printf "%02x ", $nchar;
	}
    }
    increment_curpos($skipchars);
    while (get_stuff(2) &&
	   substr($stuff, 0, 2) =~ /[A-Z0-9][A-Z0-9]/) {
	print "\n";
	printf "%08x    ", $curpos;
	my ($cmd) = substr($stuff, 0, 2);
	print $cmd;
	increment_curpos(2);
	get_stuff(2);
	$lchar = substr($stuff, 0, 1);
	$nlchar = unpack("C", $lchar);
	$hchar = substr($stuff, 1, 1);
	$nhchar = unpack("C", $hchar);
	if ($cmd eq "DF") {
	    $skipchars = 0;
	} else {
	    $skipchars = ($nhchar * 256) + $nlchar;
	}
	printf "%02x %02x ", $nlchar, $nhchar;
	increment_curpos(2);
	get_stuff($skipchars);
	for ($i = 0; $i < $skipchars; $i++) {
	    printf "%02x ", unpack("C", substr($stuff, $i, 1));
	}
	increment_curpos($skipchars);
    }
}

sub print_prefix_bytes($) {
    my ($bytes_to_print) = @_;
    print "\n";
    printf "%08x  ", $curpos;
    print "1b ";
    my $i;
    my $char;
    my $nchar;
    get_stuff($bytes_to_print);
    for ($i = 1; $i < $bytes_to_print; $i++) {
	$char = substr($stuff, $i, 1);
	$nchar = unpack("C", $char);
	if ($i < 2 && $nchar >= 32 && $nchar < 127) {
	    print " $char ";
	} else {
	    printf "%02x ", unpack("C", $char);
	}
    }
}

sub print_output_data($$$$$$) {
    my ($comptype, $bitsperpixel, $dots, $rows, $dot_scale, $color) = @_;
    my $counter;
    my $fchar;
    my $last_row = 0;
    my $first_row = -1;
    my $i;
    my $vstuff;
    $dots *= 8;
    $dots /= $dot_scale;
    my $real_dots = $dots / $bitsperpixel;
    if ($opt_v) {
	print " ($real_dots dots, $rows rows, $bitsperpixel bits";
    }
    my $savedots = $dots;
    if ($comptype == 0) {
	if ($opt_V) {
	    get_stuff($dots);
	    printf "%*v02x ", " ", substr($stuff, 0, $dots);
	}
	increment_curpos($dots);
    } elsif ($comptype == 1) {
	foreach $i (0..$rows-1) {
	    my ($found_something) = 0;
	    $dots = $savedots;
	    my ($tstuff) = "\n    $i    ";;
	    while ($dots > 0) {
		get_stuff(1);
		$counter = ord(substr($stuff, 0, 1));
		increment_curpos(1);
		if ($counter <= 127) {
		    $counter++;
		    get_stuff($counter);
		    if ($opt_v || $opt_V) {
			my $tmp = sprintf "%*v02x ", " ", substr($stuff, 0, $counter);
			if (!($tmp =~ /^[0 ]+$/)) {
			    $found_something = 1;
			    $last_row = $i;
			    if ($first_row == -1) {
				$first_row = $i;
			    }
			}
			if ($opt_V) {
			    $tstuff .= $tmp;
			}
		    }
		    increment_curpos($counter);
		} else {
		    $counter = 257 - $counter;
		    get_stuff(1);
		    if ($opt_v || $opt_V) {
			$fchar = sprintf "%v02x ", substr($stuff, 0, 1);
			if ($fchar ne "00 ") {
			    $found_something = 1;
			    $last_row = $i;
			    if ($first_row == -1) {
				$first_row = $i;
			    }
			}
		    }
		    if ($opt_V) {
			map { $tstuff .= $fchar } (0..$counter - 1);
		    }
		    increment_curpos(1);
		}
		$dots -= $counter * 8;
	    }
	    if ($opt_V && $found_something) {
		$vstuff .= $tstuff;
	    }
	}
	if ($opt_v) {
	    my ($offset) = $offsets[$color];
	    my ($first_position) = ($vertical_position / $vertical_unit)
		+ ($first_row + $offset) * $raster_y;
	    my ($last_position) = ($vertical_position / $vertical_unit)
		+ ($last_row + $offset) * $raster_y;
	    my ($final_position) = ($vertical_position / $vertical_unit)
		+ ($rows + $offset) * $raster_y;
	    my ($final_horizontal) = $horizontal_position +
		($real_dots * $page_mgmt_unit * $raster_x);
	    if ($print_offsets) {
		printf (" %d,%d+%d %.4f  %d,%d+%d %.4f  %.4f) ",
			$horizontal_position, $first_row, $offset, $first_position,
			$final_horizontal, $last_row, $offset, $last_position,
			$final_position);
	    } else {
		printf (" %d,%d %.4f  %d,%d %.4f  %.4f) ",
			$horizontal_position, $first_row, $first_position,
			$final_horizontal, $last_row, $last_position,
			$final_position);
	    }
	}
	if ($opt_V) {
	    print " $vstuff";
	}
    } else {
	print "\nUnknown compression type $comptype!\n";
    }
}

sub do_special_command() {
    get_stuff(8);
    my $comptype = unpack("C", substr($stuff, 2, 1));
    my $color = 0;
    my $dots = unpack("v", substr($stuff, 6, 2));
    my $rows = unpack("C", substr($stuff, 5, 1));
    print_prefix_bytes(8);
    increment_curpos(8);
    print_output_data($comptype, 1, $dots, $rows, 8, $color);
    get_stuff(1);
    while (substr($stuff, 0, 1) eq "\r") {
	increment_curpos(1);
    }
}

sub do_special1_command() {
    get_stuff(9);
    my $color = unpack("C", substr($stuff, 2, 1));
    my $comptype = unpack("C", substr($stuff, 3, 1));
    my $bitsperpixel = unpack("C", substr($stuff, 4, 1));
    my $dots = unpack("v", substr($stuff, 5, 2));
    my $rows = unpack("v", substr($stuff, 7, 2));
    print_prefix_bytes(9);
    increment_curpos(9);
    print_output_data($comptype, $bitsperpixel, $dots, $rows, 1, $color);
    get_stuff(1);
    while (substr($stuff, 0, 1) eq "\r") {
	increment_curpos(1);
    }
}

sub get_long($) {
    my ($string) = @_;
    my ($tmp) = unpack("V", $string);
    if ($tmp >= (1 << 31)) {
	return -(0xffffffff ^ $tmp) - 1;
    } else {
	return $tmp;
    }
}

sub get_short($) {
    my ($string) = @_;
    my ($tmp) = unpack("v", $string);
    if ($tmp >= (1 << 15)) {
	return -(0xffff ^ $tmp) - 1;
    } else {
	return $tmp;
    }
}

sub get_byte($) {
    my ($string) = @_;
    return unpack("C", $string);
}

if ($opt_O) {
    my (@stuff) = split /,/, $opt_O;
    map {
	my ($key, $val) = split /=/;
	if ($val) {
	    $print_offsets = 1;
	}
	@offsets[$key] = $val;
    } @stuff;
}

while (! $atend) {
    my $found;
    my $key;
    my $skipchars;
    my $startoff;
    my $kchar;
    my $nkchar;
    my $lchar;
    my $nlchar;
    my $hchar;
    my $nhchar;
    my $i;
    my $char;
    my $nchar;
    my $bytes;
    get_stuff(2);
    if (substr($stuff, 0, 1) eq "$esc") {
	$found = 0;
	foreach $key (sort { length $b <=> length $a } keys %seqtable) {
	    get_stuff(1 + length $key);
	    if (substr($stuff, 1, length $key) eq $key) {
		$skipchars = $seqtable{$key};
		if ($skipchars eq "SPECIAL") {
		    do_special_command();
		    $found = 2;
		} elsif ($skipchars eq "SPECIAL1") {
		    do_special1_command();
		    $found = 2;
		} elsif ($skipchars eq "REMOTE") {
		    do_remote_command();
		    $found = 2;
		} else {
		    print "\n";
		    printf "%08x  ", $curpos;
		    print "1b ";
		    $startoff = 0;
		    my $print_stuff = 0;
		    if ($skipchars eq "VARIABLE") {
			get_stuff((length $key) + 3);
			$kchar = substr($stuff, (length $key) + 1, 1);
			$nkchar = unpack("C", $kchar);
			$lchar = substr($stuff, (length $key) + 2, 1);
			$nlchar = unpack("C", $lchar);
			$hchar = substr($stuff, (length $key) + 3, 1);
			$nhchar = unpack("C", $hchar);
			$skipchars = ($nhchar * 256) + $nlchar;
			$startoff = 3;
			$print_stuff = 1;
		    }
		    get_stuff($skipchars + (length $key) + $startoff + 1);
		    for ($i = 0;
			 $i < $skipchars + (length $key) + $startoff;
			 $i++) {
			$char = substr($stuff, $i + 1, 1);
			$nchar = unpack("C", $char);
			if ($i < 3 && $nchar >= 32 && $nchar < 127) {
			    print " $char ";
			} else {
			    printf "%02x ", unpack("C", $char);
			}
		    }
		    if ($print_stuff) {
			my $xchar = substr($stuff, 2, 1);
			if ($xchar eq "c") {
			    my ($top, $bottom);
			    if ($skipchars == 8) {
				$top = get_long(substr($stuff, 5, 4));
				$bottom = get_long(substr($stuff, 9, 4));
			    } else {
				$top = get_short(substr($stuff, 5, 2));
				$bottom = get_short(substr($stuff, 7, 2));
			    }
			    if ($opt_v) {
				printf (" (page format %d %d %.2f %.2f)",
					$top, $bottom, $top / $page_mgmt_unit,
					$bottom / $page_mgmt_unit);
			    }
			    $vertical_position =
				$top * $vertical_unit / $page_mgmt_unit;
			} elsif ($xchar eq "S") {
			    my ($width, $height);
			    if ($skipchars == 8) {
				$width = get_long(substr($stuff, 5, 4));
				$height = get_long(substr($stuff, 9, 4));
			    } else {
				$width = get_short(substr($stuff, 5, 2));
				$height = get_short(substr($stuff, 7, 2));
			    }
			    if ($opt_v) {
				printf (" (paper size %d %d %.2f %.2f)",
					$width, $height, $width / $page_mgmt_unit,
					$height / $page_mgmt_unit);
			    }
			} elsif ($xchar eq "C") {
			    my ($length);
			    if ($skipchars == 4) {
				$length = get_long(substr($stuff, 5, 4));
			    } else {
				$length = get_short(substr($stuff, 5, 2));
			    }
			    if ($opt_v) {
				printf (" (page length %d %.2f)",
					$length, $length / $page_mgmt_unit);
			    }
			} elsif ($xchar eq "D") {
			    my $base = get_short(substr($stuff, 5, 2));
			    my $y = get_byte(substr($stuff, 7, 1));
			    my $x = get_byte(substr($stuff, 8, 1));
			    $raster_x = $x / $base;
			    $raster_y = $y / $base;
			    if ($opt_v) {
				printf (" (raster base %d, %d x %d)",
					$base, $base / $x, $base / $y);
			    }
			} elsif ($xchar eq "U") {
			    if ($skipchars == 5) {
				my $page_mgmt = get_byte(substr($stuff, 5, 1));
				my $vertical = get_byte(substr($stuff, 6, 1));
				my $horiz = get_byte(substr($stuff, 7, 1));
				my $scale = get_short(substr($stuff, 8, 2));
				$page_mgmt_unit = $scale / $page_mgmt;
				$horizontal_unit = $scale / $horiz;
				$vertical_unit = $scale / $vertical;
				if ($opt_v) {
				    printf (" (units base %d  mgmt %d  vert %d  horiz %d)",
					    $scale, $page_mgmt_unit,
					    $vertical_unit, $horizontal_unit);
				}
			    } else {
				my $page_mgmt = get_byte(substr($stuff, 5, 1));
				if ($opt_v) {
				    printf " (units base = %d/720)", $page_mgmt;
				}
				$page_mgmt_unit = $page_mgmt;
				$horizontal_unit = $page_mgmt;
				$vertical_unit = $page_mgmt;
			    }
			} elsif ($xchar eq "v") {
			    my ($length);
			    if ($skipchars == 4) {
				$length = get_long(substr($stuff, 5, 4));
			    } else {
				$length = get_short(substr($stuff, 5, 2));
			    }
			    $vertical_position += $length;
			    if ($opt_v) {
				printf (" (skip vertical %d at %d %.4f)",
					$length, $vertical_position,
					$vertical_position / $vertical_unit);
			    }
			} elsif ($xchar eq "\$") {
			    my ($length);
			    if ($skipchars == 4) {
				$horizontal_position =
				    get_long(substr($stuff, 5, 4));
			    } else {
				$horizontal_position =
				    get_short(substr($stuff, 5, 2));
			    }
			    if ($opt_v) {
				printf (" (horizontal position %d %.4f)",
					$horizontal_position,
					$horizontal_position / $horizontal_unit);
			    }
			}
		    }
		    $found = 1;
		}
		$bytes = length($key) + 1 + $skipchars + $startoff;
		last;
	    }
	}
	if (! $found) {
	    print "\n";
	    printf "%08x  ", $curpos;
	    print "1b ";
	    increment_curpos(1);
	} elsif ($found == 1) {
	    increment_curpos($bytes);
	}
    } elsif (substr($stuff, 0, 1) eq "\0" ||
	     substr($stuff, 0, 1) eq "\f") {
	get_stuff(1);
	$char = substr($stuff, 0, 1);
	$nchar = unpack("C", $char);
	if ($nchar >= 32 && $nchar < 127) {
	    print " $char ";
	} else {
	    printf "%02x ", unpack("C", $char);
	}
	increment_curpos(1);
    } else {
	get_stuff(1);
	$char = substr($stuff, 0, 1);
	$nchar = unpack("C", $char);
	if ($nchar >= 32 && $nchar < 127) {
	    print " *$char ";
	} else {
	    printf "*%02x ", unpack("C", $char);
	}
	increment_curpos(1);
    }
}

print "\n";

