1#!/usr/bin/perl
2
3# Copyright 2000-2008 Robert Krawitz <rlk@alum.mit.edu>
4#
5#   This program is free software; you can redistribute it and/or modify it
6#   under the terms of the GNU General Public License as published by the Free
7#   Software Foundation; either version 2 of the License, or (at your option)
8#   any later version.
9#
10#   This program is distributed in the hope that it will be useful, but
11#   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12#   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
13#   for more details.
14#
15#   You should have received a copy of the GNU General Public License
16#   along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
18use Getopt::Std;
19use strict;
20
21getopts('VvO:');
22
23use vars qw($atend
24	    $escpr
25	    $stuff
26	    $opt_v
27	    $opt_V
28	    $opt_O
29	    $curoffset
30	    $curpos
31	    $esc
32	    $initial_vertical_position
33	    $null
34	    $page_mgmt_unit
35	    $horizontal_position
36	    $horizontal_unit
37	    $vertical_unit
38	    $vertical_position
39	    $raster_x
40	    $raster_y
41	    $print_offsets
42	    %seqtable
43	    @seqkeys
44	    %chartable
45	    %xchartable
46	    %nchartable
47	    %rchartable
48	    %keylengths
49	    $total_length
50	    @offsets);
51
52$atend = 0;
53$escpr = 0;
54%chartable = ();
55%xchartable = ();
56%nchartable = ();
57%rchartable = ();
58%keylengths = ();
59
60%seqtable = ( "@", 0,
61	      "(R", "REMOTE",
62	      "(", "VARIABLE",
63	      "U", 1,
64	      "\\", 2,
65	      "\$", 2,
66	      "r", 1,
67	      "\031", 1,
68	      ".", "SPECIAL",
69	      "i", "SPECIAL1",
70	      "\000", 2,
71	      "\001", 22
72	  );
73
74map {
75    my ($xchar) = pack("C", $_);
76    if ($_ >= 32 && $_ < 127) {
77	$chartable{$xchar} = " $xchar";
78	$xchartable{$xchar} = " *$xchar";
79	$rchartable{$xchar} = $xchar;
80    } else {
81	$chartable{$xchar} = sprintf("%02x", $_);
82	$xchartable{$xchar} = sprintf("*%02x", $_);
83	$rchartable{$xchar} = sprintf("%02x ", $_);
84    }
85    $nchartable{$xchar} = $_;
86} (0..255);
87
88@seqkeys = (sort { length $b <=> length $a } keys %seqtable);
89
90map { $keylengths{$_} = length $_ } @seqkeys;
91
92$esc = "\033";
93$null = "\000";
94
95$curpos = 0;
96$curoffset = 0;
97
98$page_mgmt_unit = 360;
99$horizontal_unit = 180;
100$vertical_unit = 360;
101
102$initial_vertical_position = 0;
103$vertical_position = 0;
104$horizontal_position = 0;
105$print_offsets = 0;
106$total_length = 0;
107
108sub get_long($) {
109    my ($string) = @_;
110    my ($tmp) = unpack("V", $string);
111    if ($tmp >= (1 << 31)) {
112	return -(0xffffffff ^ $tmp) - 1;
113    } else {
114	return $tmp;
115    }
116}
117
118sub get_long_at($) {
119    my ($offset) = @_;
120    return get_long(substr($stuff, $curoffset + $offset, 4))
121}
122
123sub get_short($) {
124    my ($string) = @_;
125    my ($tmp) = unpack("v", $string);
126    if ($tmp >= (1 << 15)) {
127	return -(0xffff ^ $tmp) - 1;
128    } else {
129	return $tmp;
130    }
131}
132
133sub get_raw_at($;$) {
134    my ($offset, $count) = @_;
135    $count = 1 if ! defined $count;
136    return substr($stuff, $curoffset + $offset, $count);
137}
138
139sub get_short_at($) {
140    my ($offset) = @_;
141    return get_short(get_raw_at($offset, 2))
142}
143
144sub get_byte($) {
145    my ($string) = @_;
146    return $nchartable{substr($string, 0, 1)};
147}
148
149sub get_byte_at($) {
150    my ($offset) = @_;
151    return get_byte(get_raw_at($offset, 1))
152}
153
154sub get_blong_at($) {
155    my ($offset) = @_;
156    return get_blong(substr($stuff, $curoffset + $offset, 4));
157}
158
159sub get_bshort_at($) {
160    my ($offset) = @_;
161    return get_bshort(substr($stuff, $curoffset + $offset, 2));
162}
163
164sub get_blong($) {
165    my ($string) = @_;
166    my ($tmp) = unpack("N", $string);
167    if ($tmp >= (1 << 31)) {
168	return -(0xffffffff ^ $tmp) - 1;
169    } else {
170	return $tmp;
171    }
172}
173
174sub get_bshort($) {
175    my ($string) = @_;
176    my ($tmp) = unpack("n", $string);
177    if ($tmp >= (1 << 15)) {
178	return -(0xffff ^ $tmp) - 1;
179    } else {
180	return $tmp;
181    }
182}
183
184sub get_string_at($$) {
185    my ($offset, $count) = @_;
186    return join('', map { $rchartable{get_raw_at($offset + $_)}} (0..$count-1));
187}
188
189sub get_char_at($) {
190    my ($offset) = @_;
191    return $chartable{get_raw_at($offset, 1)};
192}
193
194sub get_vector_at($$;$) {
195    my ($offset, $count, $nospc) = @_;
196    my ($str) = get_raw_at($offset, $count);
197    if ($nospc) {
198	return sprintf("%*v02x ", $str);
199    } else {
200	return sprintf("%*v02x ", " ", $str);
201    }
202}
203
204sub fill_buffer($) {
205    my ($where) = @_;
206    return 1 if $total_length - $curoffset >= $where;
207    my ($end) = $total_length - $curoffset;
208    if ($curpos == 0 && $end == 0) {
209	$stuff = <>;		# Need to do this once to "activate" ARGV
210	$total_length = length $stuff;
211	$end = $total_length - $curoffset;
212    }
213    my ($old_end) = $end;
214    my ($tmp);
215    my ($bytes_to_read) = 16384;
216    if ($where - $end > $bytes_to_read) {
217	$bytes_to_read = $where - $end;
218    }
219    if ($curoffset >= 16384) {
220	substr($stuff, 0, $curoffset) = "";
221	$total_length -= $curoffset;
222	$curoffset = 0;
223    }
224    while ($end < $where) {
225	my $foo = read ARGV, $tmp, $bytes_to_read;
226	$stuff .= $tmp;
227	$end += $foo;
228	$total_length += $foo;
229	if ($old_end == $end) {
230	    $atend = 1;
231	    return 0;
232	} else {
233	    $bytes_to_read -= $end - $old_end;
234	    $old_end = $end;
235	}
236    }
237    return 1;
238}
239
240sub increment_curpos($) {
241    my ($curpos_increment) = @_;
242    $curpos += $curpos_increment;
243    $curoffset += $curpos_increment;
244}
245
246sub print_escpr_data($$$$$) {
247    my ($outbytes, $compression, $bpp, $offset, $expected) = @_;
248    if ($compression == 0) {
249	printf("%*v02x ", " ", substr($stuff, $offset, $outbytes * 3));
250	if ($expected != $outbytes) {
251	    printf "**** Wrong byte count: got %d, expected %d (%x)!!!", $outbytes, $expected, $expected;
252	}
253    } else {
254	my ($totbytes) = 0;
255	my ($orig_outbytes) = $outbytes;
256	while ($outbytes > 0) {
257	    my ($place) = $offset + $orig_outbytes - $outbytes;
258	    my $counter = ord(substr($stuff, $place, 1));
259	    $place++;
260	    $outbytes--;
261	    if ($counter <= 127) {
262		$counter++;
263		$counter *= $bpp;
264		$outbytes -= $counter;
265		printf("%*v02x ", " ", substr($stuff, $place, $counter));
266	    } else {
267		$counter = 257 - $counter;
268		$counter *= $bpp;
269		$outbytes -= $bpp;
270		my ($fchar) = sprintf "%*v02x ", " ", substr($stuff, $place, $bpp);
271		my ($outdata);
272		map { $outdata .= $fchar } (0..$counter - 1);
273		print $outdata;
274	    }
275	    $totbytes += $counter;
276	}
277	$totbytes /= $bpp;
278	if ($totbytes != $expected) {
279	    print "**** Wrong byte count: got $totbytes, expected $expected!!!";
280	}
281    }
282}
283
284sub do_escpr() {
285    my ($print_width, $print_length, $bpp);
286    while (fill_buffer(4)) {
287	printf("\n%08x    ", $curpos);
288	my ($cmd) = substr($stuff, $curoffset, 1);
289	my ($ncmd) = $nchartable{$cmd};
290	printf(" %02x ", $ncmd);
291	increment_curpos(1);
292	if ($cmd ne "$esc") {
293	    next;
294	}
295	my ($class) = substr($stuff, $curoffset, 1);
296	printf(" %s ", $class);
297	increment_curpos(1);
298	if ($class eq "@") {
299	    return;
300	}
301	my ($skipchars) = get_long(substr($stuff, $curoffset + 0, 4));
302	printf " %08x ", $skipchars;
303	increment_curpos(4);
304	fill_buffer(4);
305	my ($remaining) = $skipchars;
306	$cmd = substr($stuff, $curoffset + 0, 4);
307	printf(" %4s ", $cmd);
308	increment_curpos(4);
309	fill_buffer($skipchars);
310	if ($cmd eq "setq") {	# Set Quality
311	    my (@qualities) = qw(draft normal high);
312	    my (@colormono) = qw(color mono);
313	    my (@colordepth) = qw(24 8);
314	    printf("%*v02x ", " ", substr($stuff, $curoffset + 0, $skipchars));
315	    if ($opt_v) {
316		printf(" (Set Quality: Media: %02x Quality: %s Color: %s Brightness: %02x Contrast: %02x Saturation: %02x Color Depth: %x (%s) Palette Size: %04x)",
317		       get_byte_at(0),
318		       $qualities[get_byte_at(1)],
319		       $colormono[get_byte_at(2)],
320		       get_byte_at(3),
321		       get_byte_at(4),
322		       get_byte_at(5),
323		       get_byte_at(6),
324		       $colordepth[get_byte_at(6)],
325		       get_short_at(7));
326	    }
327	    $bpp = get_byte_at(6) ? 1 : 3;
328	    increment_curpos($skipchars);
329	} elsif ($cmd eq "setj") { # Set Job
330	    my (@resolution) = qw(360 720);
331	    my (@direction) = qw(bi uni);
332	    printf("%*v02x ", " ", substr($stuff, $curoffset + 0, $skipchars));
333	    if ($opt_v) {
334		printf(" (Set Job: Paper (%d %d) (%.1f %.1f) Top %d Left %d Printable (%d %d) (%.2f %.2f) In Resolution %s Direction %s)",
335		       get_blong_at(0),
336		       get_blong_at(4),
337		       get_blong_at(0) / $resolution[get_byte_at(20)],
338		       get_blong_at(4) / $resolution[get_byte_at(20)],
339		       get_bshort_at(8),
340		       get_bshort_at(10),
341		       get_blong_at(12),
342		       get_blong_at(16),
343		       get_blong_at(12) / $resolution[get_byte_at(20)],
344		       get_blong_at(16) / $resolution[get_byte_at(20)],
345		       $resolution[get_byte_at(20)],
346		       $direction[get_byte_at(21)]);
347	    }
348	    $print_width = get_blong_at(12);
349	    $print_length = get_blong_at(16);
350	    increment_curpos($skipchars);
351	} elsif ($cmd eq "sttp") { # Start Page
352	    printf("%*v02x ", " ", substr($stuff, $curoffset + 0, $skipchars));
353	} elsif ($cmd eq "dsnd") { # Data Send
354	    printf("%*v02x ", " ", substr($stuff, $curoffset + 0, 7));
355	    if ($opt_v) {
356		printf(" (Send Data: Offset (%d %d) Compression %d Size %d) ",
357		       get_bshort_at(0),
358		       get_bshort_at(2),
359		       get_byte_at(4),
360		       get_bshort_at(5));
361	    }
362	    if ($opt_V) {
363		print_escpr_data(get_bshort_at(5), get_byte_at(4), $bpp, $curoffset + 7, $print_width);
364		increment_curpos($skipchars);
365	    } else {
366		increment_curpos($skipchars);
367	    }
368	} elsif ($cmd eq "bsnd") { # Data Send -- ???
369	    printf("%*v02x ", " ", substr($stuff, $curoffset + 0, 34));
370	    my ($data_size) = get_blong_at(18) + get_blong_at(22) + get_blong_at(26);
371	    if ($opt_v) {
372		printf(" (Send Data: Offset (%d %d) Compression %d Size %d %d %d = %x, delta = %x) ",
373		       get_bshort_at(3),
374		       get_bshort_at(5),
375		       get_byte_at(7),
376		       get_blong_at(18),
377		       get_blong_at(22),
378		       get_blong_at(26),
379		       $data_size,
380		       $skipchars - ($data_size + 42))
381	    }
382	    if ($opt_V) {
383		print_escpr_data($data_size, get_byte_at(7), $bpp, $curoffset + 34, $print_width);
384	    }
385	    increment_curpos($skipchars);
386	} elsif ($cmd eq "endp") { # End Page
387	    printf("%*v02x ", " ", substr($stuff, $curoffset + 0, $skipchars));
388	    increment_curpos($skipchars);
389	} elsif ($cmd eq "endj") { # End Job
390	    printf("%*v02x ", " ", substr($stuff, $curoffset + 0, $skipchars));
391	    increment_curpos($skipchars);
392	} else {
393	    printf("%*v02x ", " ", substr($stuff, $curoffset + 0, $skipchars));
394	    increment_curpos($skipchars);
395	}
396    }
397}
398
399sub print_remote_SN($) {
400    my ($skipchars) = @_;
401    if ($skipchars == 1) {
402	print(" (Setup)");
403    } elsif ($skipchars == 3) {
404	my ($subcmd) = get_byte_at(1);
405	my ($arg) = get_byte_at(2);
406	if ($subcmd == 0) {
407	    print(" (Feed Sequence $arg)");
408	} elsif ($subcmd == 4) {
409	    print(" (Feed Adjustment $arg)");
410	} elsif ($subcmd == 5) {
411	    print(" (Vacuum Intensity $arg)");
412	} else {
413	    print(" (Unknown command $subcmd, arg $arg)");
414	}
415    } else {
416	print(" (Unknown command)");
417    }
418}
419
420sub print_remote_DR($) {
421    my ($skipchars) = @_;
422    if ($skipchars == 4) {
423	my ($subcmd) = get_byte_at(1);
424	my ($arg) = get_short_at(2);
425	if ($subcmd == 0) {
426	    printf(" (Scan dry time %.1f)", $arg / 10.0);
427	} elsif ($subcmd == 1) {
428	    printf(" (Page dry time %.1f)", $arg / 10.0);
429	} elsif ($subcmd == 0x40) {
430	    printf(" (Scan minimum dry time %.1f)", $arg / 10.0);
431	} else {
432	    print(" (Unknown command $subcmd, arg $arg)");
433	}
434    } else {
435	print(" (Unknown command)");
436    }
437}
438
439sub print_remote_CO($) {
440    my ($skipchars) = @_;
441    if ($skipchars == 8) {
442	my ($cmd) = get_byte_at(2);
443	my ($arg) = get_long_at(4);
444	if ($cmd == 0) {
445	    print(" (cut)");
446	} elsif ($cmd == 1) {
447	    print(" (cut all at $arg)");
448	} elsif ($cmd == 2) {
449	    print(" (cut last at $arg)");
450	} else {
451	    print(" (Unknown command $cmd)");
452	}
453    } else {
454	print(" (unknown command)");
455    }
456}
457
458sub print_remote_MI($) {
459    my ($skipchars) = @_;
460    if ($skipchars == 4) {
461	my ($media) = get_byte_at(2);
462	my ($size) = get_byte_at(3);
463	print(" (Media type $media, size code $size)");
464    } else {
465	print(" (unknown command)");
466    }
467}
468
469sub print_remote_DP($) {
470    my ($skipchars) = @_;
471    if ($skipchars == 2) {
472	my ($cmd) = get_byte_at(1);
473	if ($cmd == 0) {
474	    print(" (no duplex)");
475	} elsif ($cmd == 1) {
476	    print(" (duplex no tumble)");
477	} elsif ($cmd == 2) {
478	    print(" (duplex tumble)");
479	} else {
480	    print(" (unknown duplex command $cmd)");
481	}
482    } else {
483	print(" (unknown command)");
484    }
485}
486
487sub print_remote_PH($) {
488    my ($skipchars) = @_;
489    if ($skipchars == 2) {
490	my ($thickness) = get_byte_at(1);
491	print(" (Paper thickness $thickness)");
492    } else {
493	print(" (unknown command)");
494    }
495}
496
497sub print_remote_US($) {
498    my ($skipchars) = @_;
499    if ($skipchars == 3) {
500	my ($cmd) = get_byte_at(1);
501	my ($arg) = get_byte_at(2);
502	if ($cmd == 1) {
503	    print(" (Platen gap $arg)");
504	} else {
505	    print(" (Unknown command $cmd, arg $arg)");
506	}
507    } else {
508	print(" (unknown command)");
509    }
510}
511
512sub print_remote_FP($) {
513    my ($skipchars) = @_;
514    if ($skipchars == 3) {
515	my ($offset) = get_short_at(1);
516	print(" (Full bleed, offset $offset)");
517    } else {
518	print(" (unknown command)");
519    }
520}
521
522sub print_remote_IK($) {
523    my ($skipchars) = @_;
524    if ($skipchars == 2) {
525	my ($ink_type) = get_byte_at(1);
526	my ($ink) = $ink_type >= 0x80 ? "matte" : "photo";
527	print(" (Ink type $ink_type, probably $ink)");
528    } else {
529	print(" (unknown command)");
530    }
531}
532
533sub print_remote_JS($) {
534    my ($skipchars) = @_;
535    print(" (Job start)");
536}
537
538sub print_remote_JE($) {
539    my ($skipchars) = @_;
540    print(" (Job end)");
541}
542
543sub print_remote_PP($) {
544    my ($skipchars) = @_;
545    print(" (Set input slot, printer-specific)");
546}
547
548sub do_remote() {
549    while (fill_buffer(2) && get_raw_at(0, 2) =~ /[A-Z0-9][A-Z0-9]/) {
550	printf "\n%08x    ", $curpos;
551	my ($cmd) = get_raw_at(0, 2);
552	print $cmd;
553	increment_curpos(2);
554	fill_buffer(2);
555	my $nlchar = get_byte_at(0);
556	my $nhchar = get_byte_at(1);
557	my $skipchars;
558	if ($cmd eq "DF") {
559	    $skipchars = 0;
560	} else {
561	    $skipchars = ($nhchar * 256) + $nlchar;
562	}
563	printf(" %02x %02x ", $nlchar, $nhchar);
564	increment_curpos(2);
565	fill_buffer($skipchars);
566	print get_vector_at(0, $skipchars);
567	if ($opt_v) {
568	    if (eval "defined \&print_remote_$cmd") {
569		map { print("   "); } ($skipchars...5) if ($skipchars < 6);
570		eval "print_remote_$cmd($skipchars)";
571	    }
572	}
573	increment_curpos($skipchars);
574    }
575}
576
577sub do_remote_command() {
578    printf "\n%08x  1b  (  R ", $curpos;
579    increment_curpos(3);
580    fill_buffer(2);
581    my ($nlchar) = get_byte_at(0);
582    my ($nhchar) = get_byte_at(1);
583    my $skipchars = ($nhchar * 256) + $nlchar;
584    increment_curpos(2);
585    fill_buffer($skipchars);
586    my $rstring = get_string_at(0, $skipchars);
587    print $rstring;
588    increment_curpos($skipchars);
589    if ($rstring eq "00 ESCPR") {
590	do_escpr();
591    } else {
592	do_remote();
593    }
594}
595
596sub print_prefix_bytes($$) {
597    my ($bytes_to_print) = @_;
598    printf "\n%08x  1b ", $curpos;
599    fill_buffer($bytes_to_print);
600    print get_char_at(1), " ", get_vector_at(2, $bytes_to_print - 2);
601    increment_curpos($bytes_to_print);
602}
603
604sub print_output_data($$$$$$) {
605    my ($comptype, $bitsperpixel, $dots, $rows, $dot_scale, $color) = @_;
606    my $counter;
607    my $fchar;
608    my $last_row = 0;
609    my $first_row = -1;
610    my $i;
611    my $vstuff;
612    $dots *= 8;
613    $dots /= $dot_scale;
614    my $real_dots = $dots / $bitsperpixel;
615    if ($opt_v) {
616	my ($xcolor) = sprintf("%02x", $color);
617	print " ($xcolor color $real_dots dots, $rows rows, $bitsperpixel bits";
618    }
619    my $savedots = $dots;
620    if ($comptype == 0) {
621	foreach $i (0..$rows-1) {
622	    fill_buffer($dots / 8);
623	    print get_vector_at(0, $dots / 8) if ($opt_V);
624	    increment_curpos($dots / 8);
625	}
626    } elsif ($comptype == 1) {
627	foreach $i (0..$rows-1) {
628	    my ($found_something) = 0;
629	    $dots = $savedots / 8;
630	    my ($tstuff) = "\n    $i    ";
631	    while ($dots > 0) {
632		if ($total_length - $curoffset < 1) {
633		    fill_buffer(1);
634		}
635		$counter = ord(substr($stuff, $curoffset + 0, 1));
636		$curpos++;
637		$curoffset++;
638		if ($counter <= 127) {
639		    $counter++;
640		    if ($total_length - $curoffset < $counter) {
641			fill_buffer($counter);
642		    }
643		    if ($opt_V || ($opt_v && ! $found_something)) {
644			my $tmp = get_vector_at(0, $counter);
645			if (!($tmp =~ /^[0 ]+$/)) {
646			    $found_something = 1;
647			    $last_row = $i;
648			    if ($first_row == -1) {
649				$first_row = $i;
650			    }
651			}
652			if ($opt_V) {
653			    $tstuff .= $tmp;
654			}
655		    }
656		    $curpos += $counter;
657		    $curoffset += $counter;
658		} else {
659		    $counter = 257 - $counter;
660		    if ($total_length - $curoffset < 1) {
661			fill_buffer(1);
662		    }
663		    if ($opt_v) {
664			if (! $found_something) {
665			    my $tbyte = get_raw_at(0, 1);
666			    if ($tbyte != 0) {
667				$found_something = 1;
668				$last_row = $i;
669				if ($first_row == -1) {
670				    $first_row = $i;
671				}
672			    }
673			}
674		    } elsif ($opt_V) {
675			$fchar = sprintf "%v02x ", get_raw_at(0, 1);
676			if ($fchar ne "00 ") {
677			    $found_something = 1;
678			    $last_row = $i;
679			    if ($first_row == -1) {
680				$first_row = $i;
681			    }
682			}
683			map { $tstuff .= $fchar } (0..$counter - 1);
684		    }
685		    $curpos++;
686		    $curoffset++;
687		}
688		$dots -= $counter;
689	    }
690	    if ($opt_V && $found_something) {
691		$vstuff .= $tstuff;
692	    }
693	}
694    } else {
695	print "\nUnknown compression type $comptype!\n";
696    }
697    if ($opt_v) {
698	my ($offset) = $offsets[$color];
699	my ($first_position) = ($vertical_position / $vertical_unit)
700	    + ($first_row + $offset) * $raster_y;
701	my ($last_position) = ($vertical_position / $vertical_unit)
702	    + ($last_row + $offset) * $raster_y;
703	my ($final_position) = ($vertical_position / $vertical_unit)
704	    + ($rows + $offset) * $raster_y;
705	my ($final_horizontal) = $horizontal_position +
706	    ($real_dots * $page_mgmt_unit * $raster_x);
707	if ($print_offsets) {
708	    printf (" %d,%d+%d %.4f  %d,%d+%d %.4f  %.4f) ",
709		    $horizontal_position, $first_row, $offset, $first_position,
710		    $final_horizontal, $last_row, $offset, $last_position,
711		    $final_position);
712	} else {
713	    printf (" %d,%d %.4f  %d,%d %.4f  %.4f) ",
714		    $horizontal_position, $first_row, $first_position,
715		    $final_horizontal, $last_row, $last_position,
716		    $final_position);
717	}
718    }
719    if ($opt_V) {
720	print " $vstuff";
721    }
722}
723
724sub purge_line() {
725    fill_buffer(1);
726    while (get_raw_at(0) eq "\r") {
727	fill_buffer(1);
728	increment_curpos(1);
729    }
730}
731
732sub do_special_command() {
733    fill_buffer(8);
734    my $comptype = get_byte_at(2);
735    my $color = 0;
736    my $dots = get_short_at(6);
737    my $rows = get_byte_at(5);
738    print_prefix_bytes(8, 2);
739    print_output_data($comptype, 1, $dots, $rows, 8, $color);
740    purge_line();
741}
742
743sub do_special1_command() {
744    fill_buffer(9);
745    my $color = get_byte_at(2);
746    my $comptype = get_byte_at(3);
747    my $bitsperpixel = get_byte_at(4);
748    my $dots = get_short_at(5);
749    my $rows = get_short_at(7);
750    print_prefix_bytes(9, 1);
751    print_output_data($comptype, $bitsperpixel, $dots, $rows, 1, $color);
752    purge_line();
753}
754
755if ($opt_O) {
756    my (@stuff) = split(/,/, $opt_O);
757    map {
758	my ($key, $val) = split(/=/, $_);
759	if ($val) {
760	    $print_offsets = 1;
761	}
762	@offsets[$key] = $val;
763    } @stuff;
764}
765
766sub indent_cmd($) {
767    my ($skipchars) = @_;
768    map { print("   "); } ($skipchars...3) if ($skipchars < 4);
769}
770
771sub do_xcmd_c($) {
772    my ($skipchars) = @_;
773    my ($top, $bottom);
774    if ($skipchars == 8) {
775	$top = get_long_at(5);
776	$bottom = get_long_at(9) if ($opt_v);
777    } else {
778	$top = get_short_at(5);
779	$bottom = get_short_at(7) if ($opt_v);
780    }
781    if ($opt_v) {
782	printf (" (page format %d %d %.2f %.2f)", $top, $bottom,
783		$top / $page_mgmt_unit, $bottom / $page_mgmt_unit);
784    }
785    $initial_vertical_position = $top * $vertical_unit / $page_mgmt_unit;
786    $vertical_position = $initial_vertical_position;
787}
788
789sub do_xcmd_S($) {
790    my ($skipchars) = @_;
791    if ($opt_v) {
792	my ($width, $height);
793	if ($skipchars == 8) {
794	    $width = get_long_at(5);
795	    $height = get_long_at(9);
796	} else {
797	    $width = get_short_at(5);
798	    $height = get_short_at(7);
799	}
800	indent_cmd($skipchars);
801	printf (" (paper size %d %d %.2f %.2f)", $width, $height,
802		$width / $page_mgmt_unit, $height / $page_mgmt_unit);
803    }
804}
805
806sub do_xcmd_C($) {
807    my ($skipchars) = @_;
808    if ($opt_v) {
809	my ($length);
810	if ($skipchars == 4) {
811	    $length = get_long_at(5);
812	} else {
813	    $length = get_short_at(5);
814	}
815	indent_cmd($skipchars);
816	printf (" (page length %d %.2f)", $length, $length / $page_mgmt_unit);
817    }
818}
819
820sub do_xcmd_D($) {
821    my ($skipchars) = @_;
822    my $base = get_short_at(5);
823    my $y = get_byte_at(7);
824    my $x = get_byte_at(8);
825    $raster_x = $x / $base;
826    $raster_y = $y / $base;
827    if ($opt_v) {
828	indent_cmd($skipchars);
829	printf (" (raster base %d, %d x %d)", $base, $base / $x, $base / $y);
830    }
831}
832
833sub do_xcmd_U($) {
834    my ($skipchars) = @_;
835    my $page_mgmt = get_byte_at(5);
836    if ($skipchars == 5) {
837	my $vertical = get_byte_at(6);
838	my $horiz = get_byte_at(7);
839	my $scale = get_short_at(8);
840	$page_mgmt_unit = $scale / $page_mgmt;
841	$horizontal_unit = $scale / $horiz;
842	$vertical_unit = $scale / $vertical;
843	if ($opt_v) {
844	    indent_cmd($skipchars);
845	    printf (" (units base %d  mgmt %d  vert %d  horiz %d)",
846		    $scale, $page_mgmt_unit, $vertical_unit, $horizontal_unit);
847	}
848    } else {
849	if ($opt_v) {
850	    indent_cmd($skipchars);
851	    printf " (units base = %d/3600)", $page_mgmt;
852	}
853	$page_mgmt_unit = 3600 / $page_mgmt;
854	$horizontal_unit = 3600 / $page_mgmt;
855	$vertical_unit = 3600 / $page_mgmt;
856    }
857}
858
859sub do_xcmd_v($) {
860    my ($skipchars) = @_;
861    my ($length);
862    if ($skipchars == 4) {
863	$length = get_long_at(5);
864    } else {
865	$length = get_short_at(5);
866    }
867    $vertical_position += $length;
868    if ($opt_v) {
869	indent_cmd($skipchars);
870	printf (" (skip vertical %d at %d %.4f)", $length, $vertical_position,
871		$vertical_position / $vertical_unit);
872    }
873}
874
875sub do_xcmd_dlr($) {
876    my ($skipchars) = @_;
877    if ($skipchars == 4) {
878	$horizontal_position = get_long_at(5);
879    } else {
880	$horizontal_position = get_short_at(5);
881    }
882    if ($opt_v) {
883	indent_cmd($skipchars);
884	printf (" (horizontal position %d %.4f)",
885		$horizontal_position, $horizontal_position / $horizontal_unit);
886    }
887}
888
889sub do_xcmd_d($) {
890    my ($skipchars) = @_;
891    if ($opt_v) {
892	my ($bytes) = get_short_at(3);
893	indent_cmd($skipchars);
894	printf " (nop $bytes bytes)";
895    }
896}
897
898sub do_xcmd_m($) {
899    my ($skipchars) = @_;
900    if ($opt_v) {
901	indent_cmd($skipchars);
902	printf(" (print method %x)", get_byte_at(5));
903    }
904}
905
906sub do_xcmd_e($) {
907    my ($skipchars) = @_;
908    if ($opt_v) {
909	indent_cmd($skipchars);
910	printf(" (dot size %x)", get_byte_at(6));
911    }
912}
913
914sub do_xcmd_G($) {
915    my ($skipchars) = @_;
916    if ($opt_v) {
917	indent_cmd($skipchars);
918	print(" (set graphics mode)");
919    }
920}
921
922sub do_xcmd_K($) {
923    my ($skipchars) = @_;
924    if ($opt_v) {
925	indent_cmd($skipchars);
926	my ($ctype) = get_byte_at(6);
927	if ($ctype == 1) {
928	    print(" (BW mode)");
929	} elsif ($ctype == 2) {
930	    print(" (Color mode)");
931	} elsif ($ctype == 3) {
932	    print(" (Fast 360 color mode)");
933	} else {
934	    print(" (Unknown BW/color mode)");
935	}
936    }
937}
938
939sub do_xcmd_i($) {
940    my ($skipchars) = @_;
941    if ($opt_v) {
942	indent_cmd($skipchars);
943	my ($ctype) = get_byte_at(5);
944	if ($ctype == 0) {
945	    print(" (Soft weave)");
946	} else {
947	    printf(" (Printer weave method %d)", $ctype);
948	}
949    }
950}
951
952while (! $atend) {
953    my $found;
954    my $skipchars;
955    my $startoff;
956    my $bytes;
957    my ($maxklen) = $keylengths{$seqkeys[0]};
958    fill_buffer(1);
959    my $cchar = get_raw_at(0);
960    if ($cchar eq "$esc") {
961	$found = 0;
962	fill_buffer(2 + $maxklen);
963	foreach my $key (@seqkeys) {
964	    my ($klen) = $keylengths{$key};
965	    if (get_raw_at(1, $klen) eq $key) {
966		$skipchars = $seqtable{$key};
967		if ($skipchars eq "SPECIAL") {
968		    do_special_command();
969		    $found = 2;
970		} elsif ($skipchars eq "SPECIAL1") {
971		    do_special1_command();
972		    $found = 2;
973		} elsif ($skipchars eq "REMOTE") {
974		    do_remote_command();
975		    $found = 2;
976		} else {
977		    printf "\n%08x  1b ", $curpos;
978		    $startoff = 0;
979		    my $print_stuff = 0;
980		    my $print_variable = 0;
981		    if ($skipchars eq "VARIABLE") {
982			fill_buffer(3);
983			$print_variable = 1;
984			my $nlchar = get_byte_at($klen + 2);
985			my $nhchar = get_byte_at($klen + 3);
986			$skipchars = ($nhchar * 256) + $nlchar;
987			$startoff = 3;
988			$print_stuff = 1;
989		    }
990		    my ($blen) = $skipchars + $klen + $startoff;
991		    fill_buffer($blen + 1);
992		    print get_char_at(1), " ";
993		    if ($blen > 1) {
994			my $char = get_raw_at(2);
995			print get_char_at(2), " ";
996			if ($blen > 2) {
997			    if ($print_variable && $char eq "d") {
998				print get_vector_at(3, 2);
999			    } else {
1000				print get_vector_at(3, $blen - 2);
1001			    }
1002			}
1003		    }
1004		    if ($print_stuff) {
1005			my $xchar = get_raw_at(2);
1006			if ($xchar eq '$') {
1007			    $xchar = 'dlr';
1008			}
1009			if (eval defined "defined do_xcmd_$xchar") {
1010			    eval "do_xcmd_$xchar($skipchars)";
1011			}
1012		    }
1013		    $found = 1;
1014		}
1015		$bytes = $klen + 1 + $skipchars + $startoff;
1016		last;
1017	    }
1018	}
1019	if (! $found) {
1020	    printf "\n%08x  1b ", $curpos;
1021	    increment_curpos(1);
1022	} elsif ($found == 1) {
1023	    increment_curpos($bytes);
1024	}
1025    } elsif ($cchar eq "\0" || $cchar eq "\f") {
1026	printf "\n%08x  $chartable{$cchar} ", $curpos;
1027	$vertical_position = $initial_vertical_position;
1028	increment_curpos(1);
1029    } else {
1030	print "$xchartable{$cchar} " if ($cchar ne "\021");
1031	increment_curpos(1);
1032    }
1033}
1034
1035print "\n" if $curpos > 1;
1036