chiark / gitweb /
basically fixed reorg-introduced bugs, lib stuff works
[trains.git] / layout / layout
1 #!/usr/bin/perl -w
2
3 use POSIX;
4
5 # Data structures:
6 #  $ctx->{CmdLog}= undef                  } not in defobj
7 #  $ctx->{CmdLog}[]= [ command args ]     } in defobj
8 #  $ctx->{LocsMade}[]= $id
9 #  $ctx->{Loc}{$id}{X}
10 #  $ctx->{Loc}{$id}{Y}
11 #  $ctx->{Loc}{$id}{A}
12 #  $ctx->{Trans}{X}       # transformation.  is ev representing
13 #  $ctx->{Trans}{Y}       # new origin.  (is applied at _input_
14 #  $ctx->{Trans}{A}       # not at plot-time)
15 #  $ctx->{Trans}{R}       # but multiply all y coords by this!
16 #  $ctx->{Draw}{T}        # 1 or '' for drawing track
17 #  $ctx->{Draw}{L}        # L1 or 1 or '' for labelling or drawing locs
18 #
19 #  $objs{$id}{CmdLog}
20 #  $objs{$id}{Loc}
21
22 #$debug=1;
23 open DEBUG, ($debug ? ">&2" : ">/dev/null") or die $!;
24
25 if ($debug) {
26     select(DEBUG); $|=1;
27     select(STDOUT); $|=1;
28 }
29
30 # ev_... functions
31 #
32 # Operate on Enhanced Vectors which are a location (coordinates) and a
33 # direction at that location.  Representation is a hash with members X
34 # Y and A (angle of the direction in radians, anticlockwise from
35 # East).  May be absolute, or interpreted as relative, according to
36 # context.
37 #
38 # Each function's first argument is a hashref whose X Y A members will
39 # be created or overwritten; this hashref will be returned (so you can
40 # use it `functionally' by passing {}).  The other arguments may be ev
41 # hashrefs, or other info.  The results are in general undefined if
42 # one of the arguments is the same hash as the result.
43
44 sub ev_byang ($$;$) {
45     # ev_byang(R, ANG,[LEN])
46     # result is evec of specified angle and length (default=1.0)
47     my ($res,$ang,$len)=@_;
48     $len=1.0 unless defined $len;
49     $res->{X}= $len * cos($ang);
50     $res->{Y}= $len * sin($ang);
51     $res->{A}= $ang;
52     $res;
53 }
54 sub ev_compose ($$$) {
55     # ev_compose(SUM_R, A,B);
56     # appends B to A, result is end of B'
57     # A may have a member R, which if provided then it should be 1.0 or -1.0,
58     # and B's Y and A will be multiplied by R first (ie, we can reflect);
59     my ($sum,$a,$b) = @_;
60     my ($r);
61     $r= defined $a->{R} ? $a->{R} : 1.0;
62     $sum->{X}= $a->{X} + $b->{X} * cos($a->{A}) - $r * $b->{Y} * sin($a->{A});
63     $sum->{Y}= $a->{Y} + $r * $b->{Y} * cos($a->{A}) + $b->{X} * sin($a->{A});
64     $sum->{A}= $a->{A} + $r * $b->{A};
65     $sum;
66 }
67 sub ev_decompose ($$$) {
68     # ev_decompose(B_R, A,SUM)
69     # computes B_R s.t. ev_compose({}, A, B_R) gives SUM
70     my ($b,$a,$sum)=@_;
71     my ($r,$brx,$bry);
72     $r= defined $a->{R} ? $a->{R} : 1.0;
73     $brx= $sum->{X} - $a->{X};
74     $bry= $r * ($sum->{Y} - $a->{Y});
75     $b->{X}= $brx * cos($a->{A}) + $bry * sin($a->{A});
76     $b->{Y}= $bry * cos($a->{A}) - $brx * sin($a->{A});
77     $b->{A}= $r * ($sum->{A} - $a->{A});
78     $b;
79 }
80 sub ev_lincomb ($$$$) {
81     # ev_linkcomb(RES,A,B,P)
82     # gives P*A + (1-P)*B
83     my ($r,$a,$b,$p) = @_;
84     my ($q) = 1.0-$p;
85     map { $r->{$_} = $q * $a->{$_} + $p * $b->{$_} } qw(X Y A);
86     $r;
87 }    
88
89 sub canf ($$) {
90     my ($converter,$defaulter)=@_;
91     my ($spec,$v);
92     return &$defaulter unless @al;
93     $spec= shift @al;
94     $v= &$converter($spec);
95     dv('canf ','$spec',$spec, '$v',$v);
96     return $v;
97 }
98 sub can ($) { my ($c)=@_; canf($c, sub { die "too few args"; }); }
99 sub cano ($$) { my ($c,$def)=@_; canf($c, sub { return $def }); }
100
101 $pi= atan2(0,-1);
102 sub signum ($) { return ($_[0] > 0) - ($_[0] < 0); }
103
104 %units_len= qw(- mm  mm 1  cm 10  m 1000);
105 %units_ang= qw(- d   r 1); $units_ang{'d'}= 2*$pi / 360;
106
107 sub cva_len ($) { my ($sp)=@_; cva_units($sp,\%units_len); }
108 sub cva_ang ($) { my ($sp)=@_; cva_units($sp,\%units_ang); }
109 sub cva_absang ($) { input_absang(cva_ang($_[0])) }
110 sub cva_units ($$) {
111     my ($sp,$ua)=@_;
112     my ($n,$u,$r);
113     $sp =~ m/^([-0-9eE.]*[0-9.])([A-Za-z]*)$/
114         or die "lexically invalid quantity";
115     ($n,$u)= ($1,$2);
116     $u=$ua->{'-'} unless length $u;
117     defined $ua->{$u} or die "unknown unit $u";
118     $r= $n * $ua->{$u};
119     print DEBUG "cva_units($sp,)=$r ($n $u $ua->{$u})\n";
120     return $r;
121 }
122 sub cva_idstr ($) {
123     my ($sp)=@_;
124     die "invalid id" unless $sp =~ m/^[a-z][_0-9A-Za-z]*$/;
125     return $&;
126 }
127 sub cva_idex ($) {
128     my ($sp,$id)=@_;
129     my ($r,$d,$k);
130     $id=cva_idstr($sp);
131     die "unknown $id" unless defined $ctx->{Loc}{$id};
132     $r= $ctx->{Loc}{$id};
133     $d= "idex $id";
134     foreach $k (sort keys %$r) { $d .= " $k=$r->{$k}"; }
135     printf DEBUG "%s\n", $d;
136     return $r;
137 }
138 sub cva_idnew ($) {
139     my ($sp)=@_;
140     my ($id);
141     $id=cva_idstr($sp);
142     die "duplicate $id" if exists $ctx->{Loc}{$id};
143     exists $ctx->{Loc}{$id}{X};
144     push @{ $ctx->{LocsMade} }, $id;
145     return $ctx->{Loc}{$id};
146 }
147 sub cva_cmd ($) { return cva_idstr($_[0]); }
148 sub cva__enum ($$) {
149     my ($sp,$el)=@_;
150     return $sp if grep { $_ eq $sp } @$el;
151     die "invalid option (permitted: @$el)";
152 }
153 sub cvam_enum { my (@e) = @_; return sub { cva__enum($_[0],\@e); }; }
154
155 sub cmd_abs {
156     my ($i,$nl);
157     $nl= can(\&cva_idnew);
158     $i->{X}= can(\&cva_len);
159     $i->{Y}= can(\&cva_len);
160     $i->{A}= can(\&cva_ang);
161     ev_compose($nl, $ctx->{Trans}, $i);
162 }
163 sub cmd_rel {
164     my ($from,$to,$len,$right,$turn);
165     $from= can(\&cva_idex);
166     $to= can(\&cva_idnew);
167     $len= can(\&cva_len);
168     $right= can(\&cva_len);
169     $turn= cano(\&cva_absang, 0);
170     my ($u)= ev_compose({}, $from, { X => $len, Y => -$right, A => 0 });
171     ev_compose($to, $u, { X => 0, Y => 0, A => $turn });
172 }
173
174 sub dv__evreff ($) {
175     my ($pfx) = @_;
176     $pfx . ($pfx =~ m/\}$|\]$/ ? '' : '->');
177 }
178 sub dv__evr ($) {
179     my ($v) = @_;
180     return 'undef' if !defined $v;
181     return $v if $v !~ m/\W/ && $v =~ m/[A-Z]/ && $v =~ m/^[a-z_]/i;
182     return $v if $v =~ m/^[0-9.]+/;
183     $v =~ s/[\\\']/\\$&/g;
184     return "'$v'";
185 }
186 sub dv1 ($$$);
187 sub dv1_kind ($$$$$$$) {
188     my ($pfx,$expr,$ref,$ref_exp,$ixfmt,$ixesfn,$ixmapfn) = @_;
189     my ($ix,$any);
190     return 0 if $ref ne $ref_exp;
191     $any=0;
192     foreach $ix (&$ixesfn) {
193         $any=1;
194         my ($v)= &$ixmapfn($ix);
195 #print STDERR "dv1_kind($pfx,$expr,$ref,$ref_exp,$ixmapfn) ix=$ix v=$v\n";
196         dv1($pfx,$expr.sprintf($ixfmt,dv__evr($ix)),$v);
197     }
198     if (!$any) {
199         printf DEBUG "%s%s= $ixfmt\n", $pfx, $expr, ' ';
200     }
201     1;
202 }    
203 sub dv1 ($$$) {
204     return 0 unless $debug;
205     my ($pfx,$expr,$v) = @_;
206     $ref= ref $v;
207 #print STDERR "dv1 >$pfx|$ref<\n";
208     if (!$ref) {
209         printf DEBUG "%s%s= %s\n", $pfx,$expr, dv__evr($v);
210         return;
211     } elsif ($ref eq 'SCALAR') {
212         dv1($pfx, ($expr =~ m/^\$/ ? "\$$expr" : '${'.$expr.'}'), $$v);
213         return;
214     }
215     $expr.='->' unless $expr =~ m/\]$|\}$/;
216     return if dv1_kind($pfx,$expr,$ref,'ARRAY','[%s]',
217                        sub { ($[ .. $#$v) },
218                        sub { $v->[$_[0]] });
219     return if dv1_kind($pfx,$expr,$ref,'HASH','{%s}',
220                        sub { sort keys %$v },
221                        sub { $v->{$_[0]} });
222     printf DEBUG "%s%s is %s\n", $pfx, $expr, $ref;
223 }
224     
225 sub dv {
226     my ($pfx,@l) = @_;
227     my ($expr,$v,$ref);
228     while (@l) {
229         ($expr,$v,@l)=@l;
230         dv1($pfx,$expr,$v);
231     }
232 }                   
233
234 $psu_ulen= 4.5;
235 $psu_edgelw= 0.5;
236 $psu_ticklw= 0.1;
237 $psu_ticksperu= 1;
238 $psu_ticklen= 5.0;
239 $psu_allwidth= 37.0/2;
240 $psu_gauge= 9;
241 $psu_sleeperlen= 17;
242 $psu_sleeperlw= 15;
243 $psu_raillw= 1.0;
244
245 $lmu_marklw= 4;
246 $lmu_marktpt= 9;
247 $lmu_txtboxtxty= $lmu_marktpt * 0.30;
248 $lmu_txtboxh= $lmu_marktpt * 1.0;
249 $lmu_txtboxpadx= 3;
250 $lmu_txtboxoff= $lmu_marklw/2;
251 $lmu_txtboxlw= 1;
252
253 sub o ($) { $o .= $_[0]; }
254 sub ol ($) { $ol .= $_[0]; }
255
256 sub o_path_begin () {
257     o("      newpath\n");
258     $o_path_verb= 'moveto';
259 }
260 sub o_path_point ($) {
261     my ($pt)=@_;
262     o("        $pt $o_path_verb\n");
263     $o_path_verb= 'lineto';
264 }
265 sub o_path_stroke ($) {
266     my ($width)=@_;
267     o("        $width setlinewidth stroke\n");
268 }    
269
270 sub o_line ($$$) {
271     my ($a,$b,$width)=@_;
272     o_path_begin();
273     o_path_point($a);
274     o_path_point($b);
275     o_path_stroke($width);
276 }
277
278 sub psu_coords ($$$) {
279     my ($ends,$inunit,$across)=@_;
280     # $ends->[0]{X} etc.; $inunit 0 to 1 (but go to 1.5);
281     # $across in mm, +ve to right.
282     my (%ea_zo);
283     $ea_zo{X}=$ea_zo{Y}=0;
284     foreach $zo (qw(0 1)) {
285         $prop= $zo ? $inunit : (1.0 - $inunit);
286         $ea_zo{X} += $prop * ($ends->[$zo]{X} - $across * sin($ends->[0]{A}));
287         $ea_zo{Y} += $prop * ($ends->[$zo]{Y} + $across * cos($ends->[0]{A}));
288     }
289 #    dv("psu_coords ", '$ends',$ends, '$inunit',$inunit, '$across',$across,
290 #       '\\%ea_zo', \%ea_zo);
291     return $ea_zo{X}." ".$ea_zo{Y};
292 }
293
294 sub parametric_segment ($$$$$) {
295     my ($endstatuses,$p0,$p1,$lenperp,$calcfn) = @_;
296     # makes $p (global) go from $p0 to $p1  ($p1>$p0)
297     # $ends is II, SI, IS, SS (I=actual lineobj end, S=in mid of lineobj)
298     # $lenperp is the length of one unit p, ie the curve
299     # must have a uniform `density' in parameter space
300     # $calcfn is invoked with $p set and should return a loc
301     # (ie, ref to X =>, Y =>, A =>).
302     my ($pa,$pb,@ends,$side,$ppu,$e,$v,$tick);
303     return unless $ctx->{Draw}{T} =~ m/1/;
304     $ppu= $psu_ulen/$lenperp;
305     my ($railctr)=($psu_gauge + $psu_raillw)*0.5;
306     my ($tickend)=($psu_allwidth - $psu_ticklen);
307     my ($tickpitch)=($psu_ulen / $psu_ticksperu);
308     my ($sleeperctr)=($psu_ulen*0.5);
309     my ($sleeperend)=($psu_sleeperlen*0.5);
310 print DEBUG "ps $p0 $p1 $lenperp ($ppu)\n";
311     for ($pa= $p0; $pa<$p1; $pa=$pb) {
312         $pb= $pa + $ppu;
313         $p= $pa; $ends[0]= @ends ? $ends[1] : &$calcfn;
314         $p= $pb; $ends[1]= &$calcfn;
315 #print DEBUG "pa $pa $ends[0]{X} $ends[0]{Y} $ends[0]{A}\n";
316 #print DEBUG "pb $pb $ends[1]{X} $ends[1]{Y} $ends[1]{A}\n";
317         $e= $pb<=$p1 ? 1.0 : ($p1-$pa)/$ppu;
318         o("    gsave\n");
319         o_path_begin();
320         o_path_point(psu_coords(\@ends,0,-$psu_allwidth));
321         o_path_point(psu_coords(\@ends,0,$psu_allwidth));
322         o_path_point(psu_coords(\@ends,$e,$psu_allwidth));
323         o_path_point(psu_coords(\@ends,$e,-$psu_allwidth));
324         o("        closepath clip\n");
325         foreach $side qw(-1 1) {
326             o_line(psu_coords(\@ends,0,$side*$psu_allwidth),
327                    psu_coords(\@ends,1.5,$side*$psu_allwidth),
328                    $psu_edgelw);
329             o_line(psu_coords(\@ends,0,$side*$railctr),
330                    psu_coords(\@ends,1.5,$side*$railctr),
331                    $psu_raillw);
332             for ($tick=0; $tick<1.5; $tick+=$tickpitch/$psu_ulen) {
333                 o_line(psu_coords(\@ends,$tick,$side*$psu_allwidth),
334                        psu_coords(\@ends,$tick,$side*$tickend),
335                        $psu_ticklw);
336             }
337         }
338         o_line(psu_coords(\@ends,$sleeperctr,-$sleeperend),
339                psu_coords(\@ends,$sleeperctr,+$sleeperend),
340                $psu_sleeperlw);
341         o("      grestore\n");
342     }
343 }
344
345 sub cmd_extend {
346     my ($from,$to,$radius,$ctr,$beta,$ang,$how,$sign_r);
347     $from= can(\&cva_idex);
348     $to= can(\&cva_idnew);
349     printf DEBUG "from $from->{X} $from->{Y} $from->{A}\n";
350     $how= can(cvam_enum(qw(len upto ang uptoang parallel)));
351     if ($how eq 'len') { $len= can(\&cva_len); }
352     elsif ($how =~ m/ang$/) { $ang= can(\&cva_ang); }
353     elsif ($how eq 'parallel' || $how eq 'upto') { $upto= can(\&cva_idex); }
354     $radius= cano(\&cva_len, 'Inf'); # +ve is right hand bend
355     if ($radius eq 'Inf') {
356         print DEBUG "extend inf $len\n";
357         if ($how eq 'ang') { die "len of straight spec by angle"; }
358         if ($how eq 'upto') {
359             $len= ($upto->{X} - $from->{X}) * cos($from->{A})
360                 + ($upto->{Y} - $from->{Y}) * sin($from->{A});
361         }
362         printf DEBUG "len $len\n";
363         $to->{X}= $from->{X} + $len * cos($from->{A});
364         $to->{Y}= $from->{Y} + $len * sin($from->{A});
365         $to->{A}= $from->{A};
366         parametric_segment(II, 0.0, 1.0, abs($len), sub {
367             ev_lincomb({}, $from, $to, $p);
368         });
369     } else {
370         print DEBUG "radius >$radius<\n";
371         $radius *= $ctx->{Trans}{R};
372         $sign_r= signum($radius);
373         $sign_ang= 1;
374         $ctr->{X}= $from->{X} + $radius * sin($from->{A});
375         $ctr->{Y}= $from->{Y} - $radius * cos($from->{A});
376         if ($how eq 'upto') {
377             $beta= atan2(-$sign_r * ($upto->{X} - $ctr->{X}),
378                          $sign_r * ($upto->{Y} - $ctr->{Y}));
379             $beta_interval= 1.0;
380         } elsif ($how eq 'parallel') {
381             $beta= $upto->{A};
382             $beta_interval= 1.0;
383         } elsif ($how eq 'uptoang') {
384             $beta= input_absang($ang);
385             $beta_interval= 2.0;
386         } elsif ($how eq 'len') {
387             $sign_ang= signum($len);
388             $beta= $from->{A} - $sign_r * $len / abs($radius);
389             $beta_interval= 2.0;
390         } else {
391             $sign_ang= signum($ang);
392             $beta= $from->{A} - $sign_r * $ang;
393             $beta_interval= 2.0;
394         }
395     printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n";
396         $beta += $sign_ang * $sign_r * 4.0 * $pi;
397         for (;;) {
398             $delta= $beta - $from->{A};
399             last if $sign_ang * $sign_r * $delta <= 0;
400             $beta -= $sign_ang * $sign_r * $beta_interval * $pi;
401         }       
402     printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n";
403         $to->{A}= $beta;
404         $to->{X}= $ctr->{X} - $radius * sin($beta);
405         $to->{Y}= $ctr->{Y} + $radius * cos($beta);
406         parametric_segment(II, 0.0, 1.0, abs($radius*$delta), sub {
407             my ($beta) = $from->{A} + $delta * $p;
408             return { X => $ctr->{X} - $radius * sin($beta),
409                      Y => $ctr->{Y} + $radius * cos($beta),
410                      A => $beta }
411         });
412     }
413     printf DEBUG "to $to->{X} $to->{Y} $to->{A}\n";
414 }
415
416 sub ang2deg ($) {
417     return $_[0] * 180 / $pi;
418 }
419 sub input_absang ($) {
420     return $_[0] * $ctx->{Trans}{R} + $ctx->{Trans}{A};
421 }
422 sub input_abscoords ($$) {
423     my ($in,$out);
424     ($in->{X}, $in->{Y}) = @_;
425     $in->{A}= 0.0;
426     $out= ev_compose({}, $ctx->{Trans}, $in);
427     return ($out->{X}, $out->{Y});
428 }
429
430 sub newctx () {
431     $ctx= {
432         Trans => { X => 0.0, Y => 0.0, A => 0.0, R => 1.0 },
433         InRunObj => "",
434         Draw => { T => 1, L => L1 }
435         };
436 }
437
438 sub cmd_defobj {
439     my ($id);
440     $id= can(\&cva_idstr);
441     die "nested defobj" if $defobj_save;
442     die "repeated defobj" if exists $objs{$id};
443     $defobj_save= $ctx;
444     newctx();
445     $ctx->{CmdLog}= [ ];
446     $ctx->{InDefObj}= $id;
447     $ctx->{Draw}= { T => '', L => '' }
448 }
449
450 sub cmd_enddefobj {
451     my ($bit,$id);
452     $id= $ctx->{InDefObj};
453     die "unmatched enddefobj" unless defined $id;
454     foreach $bit (qw(CmdLog Loc)) {
455         $objs{$id}{$bit}= $ctx->{$bit};
456     }
457     $ctx= $defobj_save;
458     $defobj_save= undef;
459 }
460
461 sub cmd_obj { cmd__obj(1); }
462 sub cmd_objflip { cmd__obj(-1); }
463 sub cmd__obj ($) {
464     my ($flipsignum)=@_;
465     my ($obj_id, $ctx_save, $pfx, $actual, $formal_id, $formal, $formcv);
466     my ($c, $ctx_inobj);
467     $obj_id= can(\&cva_idstr);
468     $actual= can(\&cva_idex);
469     $formal_id= can(\&cva_idstr);
470     $obj= $objs{$obj_id};
471     dv("cmd__obj ",'$obj',$obj);
472     die "unknown obj $obj_id" unless $obj;
473     $formal= $obj->{Loc}{$formal_id};
474     die "unknown formal $formal_id" unless $formal;
475     $ctx_save= $ctx;
476     newctx();
477     $ctx->{Trans}{R}= $flipsignum;
478     $ctx->{Trans}{A}= $actual->{A} - $formal->{A}/$flipsignum;
479     $formcv= ev_compose({}, $ctx->{Trans},$formal);
480     $ctx->{Trans}{X}= $actual->{X} - $formcv->{X};
481     $ctx->{Trans}{Y}= $actual->{Y} - $formcv->{Y};
482     $ctx->{InRunObj}= $ctx_save->{InRunObj}."${obj_id}::";
483     $ctx->{Draw}{L} =~ s/L//;
484 dv("cmd__obj $obj_id ",'$ctx',$ctx);
485     {
486         local (@al);
487         foreach $c (@{ $obj->{CmdLog} }) {
488             @al= @$c;
489             next if $al[0] eq 'enddefobj';
490             cmd__one();
491         }
492     };
493     $pfx= cano(\&cva_idstr,'');
494     $ctx_inobj= $ctx;
495     $ctx= $ctx_save;
496     if (length $pfx) {
497         foreach $id (keys %{ $ctx_inobj->{Loc} }) {
498             $newid= $pfx.$id;
499             next if exists $ctx_save->{Loc}{$newid};
500             $newpt= cva_idnew($newid);
501             %$newpt= %{ $ctx_inobj->{Loc}{$id} };
502         }
503     }
504 }
505
506 sub cmd__do {
507     my ($cmd);
508 dv("cmd__do $ctx @al ",'$ctx',$ctx);
509     $cmd= can(\&cva_cmd);
510     my ($id,$loc,$io,$ad);
511     $io= defined $ctx->{InDefObj} ? "$ctx->{InDefObj}!" : $ctx->{InRunObj};
512     o("%L cmd   $io $cmd @al\n");
513     $ctx->{LocsMade}= [ ];
514     &{ "cmd_$cmd" };
515     die "too many args" if @al;
516     foreach $id (@{ $ctx->{LocsMade} }) {
517         $loc= $ctx->{Loc}{$id};
518         $ad= ang2deg($loc->{A});
519         ol("%L point $io$id $loc->{X} $loc->{Y} $ad\n");
520         if (length $ctx->{Draw}{L}) {
521             ol("    gsave\n".
522                "      $loc->{X} $loc->{Y} translate $ad rotate\n");
523             if ($ctx->{Draw}{L} =~ m/1/) {
524                 ol("      0 $psu_allwidth newpath moveto\n".
525                    "      0 -$psu_allwidth lineto\n".
526                    "      $lmu_marklw setlinewidth stroke\n");
527             }
528             if ($ctx->{Draw}{L} =~ m/L/) {
529                 ol("      /s ($id) def\n".
530                    "      lf setfont\n".
531                    "      /sx5  s stringwidth pop\n".
532                    "      0.5 mul $lmu_txtboxpadx add def\n".
533                    "      -90 rotate  0 $lmu_txtboxoff translate  newpath\n".
534                    "      sx5 neg  0             moveto\n".
535                    "      sx5 neg  $lmu_txtboxh  lineto\n".
536                    "      sx5      $lmu_txtboxh  lineto\n".
537                    "      sx5      0             lineto closepath\n".
538                    "      gsave  1 setgray fill  grestore\n".
539                    "      $lmu_txtboxlw setlinewidth stroke\n".
540                    "      sx5 neg $lmu_txtboxpadx add  $lmu_txtboxtxty\n".
541                    "      moveto s show\n");
542             }
543             ol("      grestore\n");
544         }
545     }
546 }
547
548 sub cmd__one {
549     cmd__do();
550 }
551
552 $ptscale= 72/25.4 / 5.0;
553
554 print
555     "%!\n".
556     "  /lf /Courier-New findfont $lmu_marktpt scalefont def\n".
557     "  $ptscale $ptscale scale\n"
558     or die $!;
559
560 newctx();
561     
562 while (<>) {
563     next if m/^\s*\#/;
564     chomp; s/^\s+//; s/\s+$//;
565     @al= split /\s+/, $_;
566     next unless @al;
567     print DEBUG "=== @al\n";
568     push @{ $ctx->{CmdLog} }, [ @al ] if exists $ctx->{CmdLog};
569     cmd__one();
570 }
571
572 print $o, $ol, "  showpage\n"
573     or die $!;