chiark / gitweb /
do pages
[trains.git] / layout / layout
1 #!/usr/bin/perl -w
2
3 use POSIX;
4 use strict;
5 no strict 'subs';
6
7 our $scale= 7.0;
8 our $page_x= 0;
9 our $page_y= 0;
10 our $quiet=0;
11 our $debug=0;
12 our $output_layer= '*';
13
14 our @eopts;
15
16 our $drawers= 'arsclmno';
17 our %chdraw_emap= qw(A ARSc
18                      R aRsc
19                      S aRSc
20                      C arsC
21                      c Arsc
22                      r arcs
23                      L LM
24                      l l
25                      M Mno
26                      N MNo
27                      O MNO
28                      m mnol);
29
30 while (@ARGV && $ARGV[0] =~ m/^\-/) {
31     last if $ARGV[0] eq '-';
32     $_= shift @ARGV;
33     last if $_ eq '--';
34     s/^\-//;
35     while (length) {
36         if (s/^D(\d+)//) { $debug= $1; }
37         elsif (s/^D//) { $debug++; }
38         elsif (s/^q//) { $quiet=1; }
39         elsif (s/^l(\d+|\*)//) { $output_layer=$1; }
40         elsif (s/^S([0-9.]+)$//) { $scale= $1 * 1.0; }
41         elsif (s/^P(\d+)x(\d+)$//) { $page_x= $1; $page_y= $2; }
42         elsif (s/^(e)
43                ((?:[a-z]|\*|\?|\[[a-z][-a-z]*\])*?)
44                (\~?) (\d*) (\=*|\-+|\++) (\d*|\*)
45                ([a-z]+)$//ix) {
46             my ($ee,$g,$n,$d,$c,$v,$cc) = ($1,$2,$3,$4,$5,$6,$7);
47             my ($eo, $invert, $lfn, $ccc, $sense,$limit);
48             $g =~ s/\?/\./g; $g =~ s/\*/\.\*/g;
49             die '-[eE]GND[=]* not allowed' if $v eq '*' && length $d;
50             $d= $output_layer if !length $d;
51             $d= 5 if $d eq '*';
52             $invert= length $n;
53             $c= '=' if !length $c;
54             if (length $v && $v ne '*') {
55                 die '-[eE]GN[D]CCV not allowed' if length $c > 1;
56                 $c= $c x $v;
57             }
58             if ($c =~ m/^[-+]/) {
59                 die '-[eE]GN+/-* not allowed' if $v eq '*';
60                 $sense= ($&.'1') + 0;
61                 $limit= ($sense * $d) + length($c) - 1;
62                 $lfn= sub {
63                     ($output_layer eq '*' ? $d
64                      : $_[0]) * $sense >= $limit
65                          xor $invert;
66                 };
67             } elsif ($v eq '*') {
68                 $lfn= sub { !$invert; };
69             } else {
70                 $limit= length($c) - 1;
71                 $lfn= sub {
72 #my ($lfn_result)=(
73                     ($output_layer eq '*' ? 1
74                      : abs($_[0] - $d) <= $limit)
75                         xor $invert
76 #)
77                             ;
78 #print STDERR "output layer $output_layer; asking re $_[0] rel $d lim $limit invert $invert result $lfn_result\n";
79 #$lfn_result;
80                 };
81             }
82             $ccc= '';
83             foreach $c (split //, $cc) {
84                 if ($ee eq 'e') {
85                     die "bad -e option $c" unless defined $chdraw_emap{$c};
86                     $ccc .=  $chdraw_emap{$c};
87                 } else {
88                     die "bad -E option $c" unless $c =~ m/[$drawers]/i;
89                     $ccc .= $c;
90                 }
91             }
92             $eo->{GlobRe}= $g;
93             $eo->{LayerCheck}= $lfn;
94             $eo->{DrawMods}= $ccc;
95 #print STDERR "created eo $eo re $eo->{GlobRe} n=$n d=$d v=$v c=$c limit=$limit cc=$cc\n";
96             push @eopts, $eo;
97         } elsif (m/^S/) {
98             die "-S option must come right at the start and have numeric arg";
99         } else {
100             die "unknown option -$_";
101         }
102     }
103 }
104
105 our $ptscale= 72/25.4 / $scale;
106
107 our $psu_ulen= 4.5;
108 our $psu_edgelw= 0.5;
109 our $psu_ticklw= 0.1;
110 our $psu_ticksperu= 1;
111 our $psu_ticklen= 5.0;
112 our $psu_gauge= 9;
113 our $psu_sleeperlen= 17;
114 our $psu_sleeperlw= 15;
115 our $psu_raillw= 1.0;
116 our $psu_thinlw= 1.0;
117
118 our $lmu_marklw= 4;
119 our $lmu_marktpt= 11;
120 our $lmu_txtboxtxty= $lmu_marktpt * 0.300;
121 our $lmu_txtboxh= $lmu_marktpt * 1.100;
122 our $lmu_txtboxpadx= $lmu_marktpt * 0.335;
123 our $lmu_txtboxoff= $lmu_marklw / 2;
124 our $lmu_txtboxlw= 1;
125
126 our $olu_left= 10 * $scale;
127 our $olu_right= 217 * $scale - $olu_left;
128 our $olu_bottom= 20 * $scale;
129 our $olu_top= 270 * $scale - $olu_bottom;
130 our $olu_gap_x= 30;
131 our $olu_gap_y= 60;
132 our $olu_textheight= 15;
133 our $olu_textallowperc= $lmu_marktpt * 5.0/11;
134
135 our $pi= atan2(0,-1);
136
137 sub allwidth2 ($) {
138     my ($radius)= @_;
139     return 27 unless defined $radius;
140     $radius= abs($radius);
141     return ($radius >= 450 ? 33 :
142             $radius >= 400 ? 35 :
143             37);
144 }
145 sub allwidth ($) { return allwidth2($_[0]) * 0.5; }
146
147 our $allwidthmax= allwidth(0);
148 our $allwidthmin= allwidth(undef);
149
150 # Data structures:
151 #  $ctx->{CmdLog}= undef                  } not in defobj
152 #  $ctx->{CmdLog}[]= [ command args ]     } in defobj
153 #  $ctx->{LocsMade}[]{Id}= $id
154 #  $ctx->{LocsMade}[]{Neg}= 1 or 0
155 #  $ctx->{Loc}{$id}{X}
156 #  $ctx->{Loc}{$id}{Y}
157 #  $ctx->{Loc}{$id}{A}
158 #  $ctx->{Loc}{$id}{LayerKind}
159 #  $ctx->{Trans}{X}       # transformation.  is ev representing
160 #  $ctx->{Trans}{Y}       # new origin.  (is applied at _input_
161 #  $ctx->{Trans}{A}       # not at plot-time)
162 #  $ctx->{Trans}{R}       # but multiply all y coords by this!
163 #  $ctx->{Draw}           # sequence of one or more chrs from uc $drawers
164 #                         #  possibly including X meaning never draw
165 #                         #  anything now (eg in defobj)
166 #  $ctx->{DrawMap}        # =$fn s.t.
167 #                         #  &$fn($drawchrs_spec_by_layer_cmdline)
168 #                         #   = $drawchrs_we_should_use_due_to_obj_etc
169 #  $ctx->{Layer}{Level}
170 #  $ctx->{Layer}{Kind}
171 #
172 #  $objs{$id}{CmdLog}
173 #  $objs{$id}{Loc}
174 #  $objs{$id}{Part}       # 1 iff object is a part
175 #
176 #  $eopts[]{GlobRe}       # regexp for K
177 #  $eopts[]{LayerCheck}   # =$fn where &$fn($l) is true iff layer matches
178 #  $eopts[]{DrawMods}     # modifier chars for drawing
179
180 our $ctx;
181 our %objs;
182 our @al; # current cmd
183
184 our $o='';
185 our $ol='';
186
187 our $param; # for parametric_curve
188
189 # ev_... functions
190 #
191 # Operate on Enhanced Vectors which are a location (coordinates) and a
192 # direction at that location.  Representation is a hash with members X
193 # Y and A (angle of the direction in radians, anticlockwise from
194 # East).  May be absolute, or interpreted as relative, according to
195 # context.
196 #
197 # Each function's first argument is a hashref whose X Y A members will
198 # be created or overwritten; this hashref will be returned (so you can
199 # use it `functionally' by passing {}).  The other arguments may be ev
200 # hashrefs, or other info.  The results are in general undefined if
201 # one of the arguments is the same hash as the result.
202
203 sub ev_byang ($$;$) {
204     # ev_byang(R, ANG,[LEN])
205     # result is evec LEN (default=1.0) from origin pointing in direction ANG
206     my ($res,$ang,$len)=@_;
207     $len=1.0 unless defined $len;
208     $res->{X}= $len * cos($ang);
209     $res->{Y}= $len * sin($ang);
210     $res->{A}= $ang;
211     $res;
212 }
213 sub ev_compose ($$$) {
214     # ev_compose(SUM_R, A,B);
215     # appends B to A, result is end of new B
216     # (B's X is forwards from end of A, Y is translating left from end of A)
217     # A may have a member R, which if provided then it should be 1.0 or -1.0,
218     # and B's Y and A will be multiplied by R first (ie, we can reflect);
219     my ($sum,$a,$b) = @_;
220     my ($r);
221     $r= defined $a->{R} ? $a->{R} : 1.0;
222     $sum->{X}= $a->{X} + $b->{X} * cos($a->{A}) - $r * $b->{Y} * sin($a->{A});
223     $sum->{Y}= $a->{Y} + $r * $b->{Y} * cos($a->{A}) + $b->{X} * sin($a->{A});
224     $sum->{A}= $a->{A} + $r * $b->{A};
225     $sum;
226 }
227 sub ev_decompose ($$$) {
228     # ev_decompose(B_R, A,SUM)
229     # computes B_R s.t. ev_compose({}, A, B_R) gives SUM
230     my ($b,$a,$sum)=@_;
231     my ($r,$brx,$bry);
232     $r= defined $a->{R} ? $a->{R} : 1.0;
233     $brx= $sum->{X} - $a->{X};
234     $bry= $r * ($sum->{Y} - $a->{Y});
235     $b->{X}= $brx * cos($a->{A}) + $bry * sin($a->{A});
236     $b->{Y}= $bry * cos($a->{A}) - $brx * sin($a->{A});
237     $b->{A}= $r * ($sum->{A} - $a->{A});
238     $b;
239 }
240 sub ev_lincomb ($$$$) {
241     # ev_linkcomb(RES,A,B,P)
242     # gives P*A + (1-P)*B
243     my ($r,$a,$b,$p) = @_;
244     my ($q) = 1.0-$p;
245     map { $r->{$_} = $q * $a->{$_} + $p * $b->{$_} } qw(X Y A);
246     $r;
247 }
248 sub a_normalise ($$) {
249     # a_normalise(A,Z)
250     # adds or subtracts 2*$pi to/from A until it is in [ Z , Z+2*$pi >
251     my ($a,$z)=@_;
252     my ($r);
253     $r= $z + fmod($a - $z, 2.0*$pi);
254     $r += 2*$pi if $r < $z;
255     return $r;
256 }
257 sub ev_bearing ($$) {
258     # ev_bearing(A,B)
259     # returns bearing of B from A
260     # value returned is in [ A->{A}, A->{A} + 2*$pi >
261     # A->{A} and B->{A} are otherwise ignored
262     my ($a,$b)= @_;
263     my ($r);
264     $r= atan2($b->{Y} - $a->{Y},
265               $b->{X} - $a->{X});
266     $r= a_normalise($r,$a->{A});
267     return $r;
268 }
269
270 sub v_rotateright ($) {
271     # v_rotateright(A)
272     # returns image of A rotated 90 deg clockwise
273     my ($a)= @_;
274     return { X => $a->{Y}, Y => -$a->{X} };
275 }
276 sub v_dotproduct ($$) {
277     # v_dotproduct(A,B)
278     my ($a,$b)= @_;
279     return $a->{X} * $b->{X} + $a->{Y} * $b->{Y};
280 }
281 sub v_scalarmult ($$) {
282     # v_scalarmult(S,V)
283     # multiplies V by scalar S and returns product
284     my ($s,$v)=@_;
285     return { X => $s * $v->{X}, Y => $s * $v->{Y} };
286 }
287 sub v_add ($;@) {
288     # v_add(A,B,...)
289     # vector sum of all inputs
290     my (@i) = @_;
291     my ($r,$i);
292     $r= { X => 0.0, Y => 0.0 };
293     foreach $i (@i) { $r->{X} += $i->{X}; $r->{Y} += $i->{Y}; }
294     return $r;
295 }    
296 sub v_subtract ($$) {
297     # v_subtract(A,B)
298     # returns vector from A to B, ie B - A
299     my ($a,$b)= @_;
300     return { X => $b->{X} - $a->{X},
301              Y => $b->{Y} - $a->{Y} };
302 }
303 sub v_len ($) {
304     # v_len(V)
305     # scalar length of V
306     my ($v)=@_;
307     my ($x,$y) = ($v->{X}, $v->{Y});
308     return sqrt($x*$x + $y*$y);
309 }
310 sub v_dist ($$) {
311     # v_dist(A,B)
312     # returns distance from A to B
313     return v_len(v_subtract($_[0],$_[1]));
314 }
315
316 sub upd_min ($$) {
317     my ($limr,$now)=@_;
318     $$limr= $now unless defined $$limr && $$limr <= $now;
319 }
320 sub upd_max ($$) {
321     my ($limr,$now)=@_;
322     $$limr= $now unless defined $$limr && $$limr >= $now;
323 }
324
325 sub canf ($$) {
326     my ($converter,$defaulter)=@_;
327     my ($spec,$v);
328     return &$defaulter unless @al;
329     $spec= shift @al;
330     $v= &$converter($spec);
331     dv('canf ','$spec',$spec, '$v',$v);
332     return $v;
333 }
334 sub can ($) { my ($c)=@_; canf($c, sub { die "too few args"; }); }
335 sub cano ($$) { my ($c,$def)=@_; canf($c, sub { return $def }); }
336
337 sub signum ($) { return ($_[0] > 0) - ($_[0] < 0); }
338
339 sub bbox ($) {
340     my ($objhash) = @_;
341     my ($min_x, $max_x, $min_y, $max_y);
342     my ($loc);
343     foreach $loc (values %$objhash) {
344         upd_min(\$min_x, $loc->{X} - abs($allwidthmax * sin($loc->{A})));
345         upd_max(\$max_x, $loc->{X} + abs($allwidthmax * sin($loc->{A})));
346         upd_min(\$min_y, $loc->{Y} - abs($allwidthmax * cos($loc->{A})));
347         upd_max(\$max_y, $loc->{Y} + abs($allwidthmax * cos($loc->{A})));
348     }
349     return ($min_x, $max_x, $min_y, $max_y);
350 }
351
352 our %units_len= qw(- mm  mm 1  cm 10  m 1000);
353 our %units_ang= qw(- d   r 1); $units_ang{'d'}= 2*$pi / 360;
354
355 sub cva_len ($) { my ($sp)=@_; cva_units($sp,\%units_len); }
356 sub cva_identity ($) { my ($sp)=@_; $sp; }
357 sub cva_ang ($) { my ($sp)=@_; cva_units($sp,\%units_ang); }
358 sub cva_absang ($) { input_absang(cva_ang($_[0])) }
359 sub cva_units ($$) {
360     my ($sp,$ua)=@_;
361     my ($n,$u,$r);
362     $sp =~ m/^([-0-9eE.]*[0-9.])([A-Za-z]*)$/
363         or die "lexically invalid quantity";
364     ($n,$u)= ($1,$2);
365     $u=$ua->{'-'} unless length $u;
366     defined $ua->{$u} or die "unknown unit $u";
367     $r= $n * $ua->{$u};
368     print DEBUG "cva_units($sp,)=$r ($n $u $ua->{$u})\n";
369     return $r;
370 }
371 sub cva_idstr ($) {
372     my ($sp)=@_;
373     die "invalid id" unless $sp =~ m/^[a-z][_0-9A-Za-z]*$/;
374     return $&;
375 }
376 sub cva_idex ($) {
377     my ($sp)=@_;
378     my ($id,$r,$d,$k,$neg,$na,$obj_id,$vflip,$locs);
379     if ($sp =~ s/^(\^?)(\w+)\!//) {
380         $vflip= length($1);
381         $obj_id= $2;
382         die "invalid obj $obj_id in loc" unless exists $objs{$obj_id};
383         $locs= $objs{$obj_id}{Loc};
384     } else {
385         $locs= $ctx->{Loc};
386         $vflip= 0;
387     }
388     $neg= $sp =~ s/^\-//;
389     $id= cva_idstr($sp);
390     die "unknown $id" unless defined $locs->{$id};
391     $r= $locs->{$id};
392     $d= "idex $id";
393     foreach $k (sort keys %$r) { $d .= " $k=$r->{$k}"; }
394     printf DEBUG "%s\n", $d;
395     if ($vflip) {
396         $r= { X => $r->{X}, Y => -$r->{Y}, A => -$r->{A} };
397     }
398     if ($neg) {
399         $na= $r->{A} + $pi;
400         $na= a_normalise($na,0);
401         $r= { X => $r->{X}, Y => $r->{Y}, A => $na };
402     }
403     return $r;
404 }
405 sub cva_idnew ($) {
406     my ($sp)=@_;
407     my ($id, $neg);
408     $neg = $sp =~ s/^\-//;
409     $id=cva_idstr($sp);
410     die "duplicate $id" if exists $ctx->{Loc}{$id};
411     $ctx->{Loc}{$id}{LayerKind}= $ctx->{Layer}{Kind};
412     push @{ $ctx->{LocsMade} }, {
413         Id => $id,
414         Neg => $neg,
415     };
416     return $ctx->{Loc}{$id};
417 }
418 sub cva_cmd ($) { return cva_idstr($_[0]); }
419 sub cva__enum ($$) {
420     my ($sp,$el)=@_;
421     return $sp if grep { $_ eq $sp } @$el;
422     die "invalid option (permitted: @$el)";
423 }
424 sub cvam_enum { my (@e) = @_; return sub { cva__enum($_[0],\@e); }; }
425
426 sub cmd_abs {
427     my ($i,$nl);
428     $nl= can(\&cva_idnew);
429     $i->{X}= can(\&cva_len);
430     $i->{Y}= can(\&cva_len);
431     $i->{A}= can(\&cva_ang);
432     ev_compose($nl, $ctx->{Trans}, $i);
433 }
434 sub cmd_rel {
435     my ($from,$to,$len,$right,$turn);
436     $from= can(\&cva_idex);
437     $to= can(\&cva_idnew);
438     $len= cano(\&cva_len,0);
439     $right= cano(\&cva_len,0) * $ctx->{Trans}{R};
440     $turn= cano(\&cva_ang, 0) * $ctx->{Trans}{R};
441     my ($u)= ev_compose({}, $from, { X => $len, Y => -$right, A => 0 });
442     ev_compose($to, $u, { X => 0, Y => 0, A => $turn });
443 }
444
445 sub dv__evreff ($) {
446     my ($pfx) = @_;
447     $pfx . ($pfx =~ m/\}$|\]$/ ? '' : '->');
448 }
449 sub dv__evr ($) {
450     my ($v) = @_;
451     return 'undef' if !defined $v;
452     return $v if $v !~ m/\W/ && $v =~ m/[A-Z]/ && $v =~ m/^[a-z_]/i;
453     return $v if $v =~ m/^[0-9.]+/;
454     $v =~ s/[\\\']/\\$&/g;
455     return "'$v'";
456 }
457 sub dv1 ($$$);
458 sub dv1_kind ($$$$$$$) {
459     my ($pfx,$expr,$ref,$ref_exp,$ixfmt,$ixesfn,$ixmapfn) = @_;
460     my ($ix,$any);
461     return 0 if $ref ne $ref_exp;
462     $any=0;
463     foreach $ix (&$ixesfn) {
464         $any=1;
465         my ($v)= &$ixmapfn($ix);
466 #print STDERR "dv1_kind($pfx,$expr,$ref,$ref_exp,$ixmapfn) ix=$ix v=$v\n";
467         dv1($pfx,$expr.sprintf($ixfmt,dv__evr($ix)),$v);
468     }
469     if (!$any) {
470         printf DEBUG "%s%s= $ixfmt\n", $pfx, $expr, ' ';
471     }
472     1;
473 }    
474 sub dv1 ($$$) {
475     return 0 unless $debug;
476     my ($pfx,$expr,$v) = @_;
477     my ($ref);
478     $ref= ref $v;
479 #print STDERR "dv1 >$pfx|$ref<\n";
480     if (!$ref) {
481         printf DEBUG "%s%s= %s\n", $pfx,$expr, dv__evr($v);
482         return;
483     } elsif ($ref eq 'SCALAR') {
484         dv1($pfx, ($expr =~ m/^\$/ ? "\$$expr" : '${'.$expr.'}'), $$v);
485         return;
486     }
487     $expr.='->' unless $expr =~ m/\]$|\}$/;
488     return if dv1_kind($pfx,$expr,$ref,'ARRAY','[%s]',
489                        sub { ($[ .. $#$v) },
490                        sub { $v->[$_[0]] });
491     return if dv1_kind($pfx,$expr,$ref,'HASH','{%s}',
492                        sub { sort keys %$v },
493                        sub { $v->{$_[0]} });
494     printf DEBUG "%s%s is %s\n", $pfx, $expr, $ref;
495 }
496     
497 sub dv {
498     my ($pfx,@l) = @_;
499     my ($expr,$v,$ref);
500     while (@l) {
501         ($expr,$v,@l)=@l;
502         dv1($pfx,$expr,$v);
503     }
504 }                   
505
506 sub o ($) { $o .= $_[0]; }
507 sub ol ($) { $ol .= $_[0]; }
508 sub oflushpage () {
509     print $o, $ol, "  showpage\n"
510         or die $!;
511     $o=$ol='';
512 }
513
514 our $o_path_verb;
515
516 sub o_path_begin () {
517     o("      newpath\n");
518     $o_path_verb= 'moveto';
519 }
520 sub o_path_point ($) {
521     my ($pt)=@_;
522     o("        $pt $o_path_verb\n");
523     $o_path_verb= 'lineto';
524 }
525 sub o_path_stroke ($) {
526     my ($width)=@_;
527     o("        $width setlinewidth stroke\n");
528 }    
529
530 sub o_line ($$$) {
531     my ($a,$b,$width)=@_;
532     o_path_begin();
533     o_path_point($a);
534     o_path_point($b);
535     o_path_stroke($width);
536 }
537
538 sub current_draw () {
539     my ($r);
540     $r= $ctx->{Draw} =~ m/X/ ? '' : $ctx->{Draw};
541     $r;
542 }
543
544 sub psu_coords ($$$) {
545     my ($ends,$inunit,$across)=@_;
546     # $ends->[0]{X} etc.; $inunit 0 to 1 (but go to 1.5);
547     # $across in mm, +ve to right.
548     my (%ea_zo, $zo, $prop);
549     $ea_zo{X}=$ea_zo{Y}=0;
550     foreach $zo (qw(0 1)) {
551         $prop= $zo ? $inunit : (1.0 - $inunit);
552         $ea_zo{X} += $prop * ($ends->[$zo]{X} - $across * sin($ends->[0]{A}));
553         $ea_zo{Y} += $prop * ($ends->[$zo]{Y} + $across * cos($ends->[0]{A}));
554     }
555 #    dv("psu_coords ", '$ends',$ends, '$inunit',$inunit, '$across',$across,
556 #       '\\%ea_zo', \%ea_zo);
557     return $ea_zo{X}." ".$ea_zo{Y};
558 }
559
560 sub parametric__o_pt ($) {
561     my ($pt)=@_;
562     o_path_point("$pt->{X} $pt->{Y}");
563 }
564
565 sub parametric_segment ($$$$$) {
566     my ($p0,$p1,$lenperp,$minradius,$calcfn) = @_;
567     # makes $p (global) go from $p0 to $p1  ($p1>$p0)
568     # $lenperp is the length of one unit p, ie the curve
569     # must have a uniform `density' in parameter space
570     # $calcfn is invoked with $p set and should return a loc
571     # (ie, ref to X =>, Y =>, A =>).
572     my ($pa,$pb,@ends,$side,$ppu,$e,$v,$tick,$draw,$allwidth);
573     return unless $ctx->{Draw} =~ m/[ARSC]/;
574     $ppu= $psu_ulen/$lenperp;
575     $allwidth= allwidth($minradius);
576     my ($railctr)=($psu_gauge + $psu_raillw)*0.5;
577     my ($tickend)=($allwidth - $psu_ticklen);
578     my ($tickpitch)=($psu_ulen / $psu_ticksperu);
579     my ($sleeperctr)=($psu_ulen*0.5);
580     my ($sleeperend)=($psu_sleeperlen*0.5);
581 print DEBUG "ps $p0 $p1 $lenperp ($ppu)\n";
582     $draw= current_draw();
583     if ($draw =~ m/C/) {
584         my ($pt);
585         o("    $psu_thinlw setlinewidth\n");
586         o_path_begin();
587         for ($param=$p0; $param<$p1; $param += $ppu) {
588             parametric__o_pt(&$calcfn);
589         }
590         $param=$p1;
591         parametric__o_pt(&$calcfn);
592         o("      stroke\n");
593     }
594     return unless $draw =~ m/[ARS]/;
595     for ($pa= $p0; $pa<$p1; $pa=$pb) {
596         $pb= $pa + $ppu;
597         $param= $pa; $ends[0]= @ends ? $ends[1] : &$calcfn;
598         $param= $pb; $ends[1]= &$calcfn;
599 #print DEBUG "pa $pa $ends[0]{X} $ends[0]{Y} $ends[0]{A}\n";
600 #print DEBUG "pb $pb $ends[1]{X} $ends[1]{Y} $ends[1]{A}\n";
601         $e= $pb<=$p1 ? 1.0 : ($p1-$pa)/$ppu;
602         o("    gsave\n");
603         o_path_begin();
604         o_path_point(psu_coords(\@ends,0,-$allwidth));
605         o_path_point(psu_coords(\@ends,0,$allwidth));
606         o_path_point(psu_coords(\@ends,$e,$allwidth));
607         o_path_point(psu_coords(\@ends,$e,-$allwidth));
608         o("        closepath clip\n");
609         foreach $side qw(-1 1) {
610             if ($draw =~ m/R/) {
611                 o_line(psu_coords(\@ends,0,$side*$railctr),
612                        psu_coords(\@ends,1.5,$side*$railctr),
613                        $psu_raillw);
614             }
615         }
616         if ($draw =~ m/S/) {
617             o_line(psu_coords(\@ends,$sleeperctr,-$sleeperend),
618                    psu_coords(\@ends,$sleeperctr,+$sleeperend),
619                    $psu_sleeperlw);
620         }
621         if ($draw =~ m/A/) {
622             o("        0.5 setgray\n");
623             foreach $side qw(-1 1) {
624                 o_line(psu_coords(\@ends,0,$side*$allwidth),
625                        psu_coords(\@ends,1.5,$side*$allwidth),
626                        $psu_edgelw);
627                 for ($tick=0; $tick<1.5; $tick+=$tickpitch/$psu_ulen) {
628                     o_line(psu_coords(\@ends,$tick,$side*$allwidth),
629                            psu_coords(\@ends,$tick,$side*$tickend),
630                            $psu_ticklw);
631                 }
632             }
633         }
634         o("      grestore\n");
635     }
636 }
637
638 sub arc ($$$$$) {
639     my ($to, $ctr,$from, $radius,$delta) = @_;
640     # does parametric_segment to draw an arc centred on $ctr
641     # ($ctr->{A} ignored)
642     # from $from with radius $radius (this must be consistent!)
643     # and directionally-subtending an angle $delta.
644     # sets $to->... to be the other end, and returns $to
645     my ($beta);
646     $to->{A}= $beta= $from->{A} + $delta;
647     $to->{X}= $ctr->{X} - $radius * sin($beta);
648     $to->{Y}= $ctr->{Y} + $radius * cos($beta);
649     return if abs($delta*$radius) < 1e-9;
650     parametric_segment(0.0,1.0, abs($radius*$delta), $radius, sub {
651         my ($beta) = $from->{A} + $delta * $param;
652         return { X => $ctr->{X} - $radius * sin($beta),
653                  Y => $ctr->{Y} + $radius * cos($beta),
654                  A => $beta }
655     });
656 }
657
658 # joins_xxx all take $results, $from, $to, $minradius
659 # where $results->[]{Path}{K} etc. and $results->[]{SolKinds}[]
660
661 sub joins_twoarcs ($$$$) {
662     my ($results, $from,$to,$minradius) = @_;
663     # two circular arcs of equal maximum possible radius
664     # algorithm courtesy of Simon Tatham (`Railway problem',
665     # pers.comm. to ijackson@chiark 23.1.2004)
666     my ($sigma,$distfact, $theta,$phi, $a,$b,$c,$d, $m,$r, $radius);
667     my ($cvec,$cfrom,$cto,$midpt, $delta1,$delta2, $path,$reverse);
668     $sigma= ev_bearing($from,$to);
669     $distfact= v_dist($from,$to);
670     $theta= 0.5 * $pi - ($from->{A} - $sigma);
671     $phi=   0.5 * $pi - ($to->{A} + $pi - $sigma);
672     $a= 2 * (1 + cos($theta - $phi));
673     $b= 2 * (cos($theta) - cos($phi));
674     $c= -1;
675     $d= sqrt($b*$b - 4*$a*$c);
676     o("%     twoarcs theta=".ang2deg($theta)." phi=".ang2deg($phi).
677       " ${a}r^2 + ${b}r + ${c} = 0\n");
678     foreach $m (qw(-1 1)) {
679         if ($a < 1e-6) {
680             o("%     twoarcs $m insoluble\n");
681             next;
682         }
683         $r= -0.5 * (-$b + $m*$d) / $a;
684         $radius= -$r * $distfact;
685         o("%     twoarcs $m radius $radius ");
686         if (abs($radius) < $minradius) { o("too-small\n"); next; }
687         $cfrom=  ev_compose({}, $from, { X=>0, Y=>-$radius, A=>-0.5*$pi });
688         $cto=    ev_compose({}, $to,   { X=>0, Y=> $radius, A=> 0.5*$pi });
689         $midpt=  ev_lincomb({}, $cfrom, $cto, 0.5);
690         $reverse= signum($r);
691         if ($reverse<0) {
692             $cfrom->{A} += $pi;
693             $cto->{A} += $pi;
694         }
695         $delta1= ev_bearing($cfrom, $midpt) - $cfrom->{A};
696         $delta2= ev_bearing($cto,   $midpt) - $cto->{A};
697         o("ok deltas ".ang2deg($delta1)." ".ang2deg($delta2)."\n");
698         if ($reverse<0) {
699             $delta1 -= 2*$pi;
700             $delta2 -= 2*$pi;
701         }
702         my ($fs);
703         $path= [{ T=>Arc, F=>$from, C=>$cfrom, R=> $radius, D=>$delta1 },
704                 { T=>Arc, F=>$to,   C=>$cto,   R=>-$radius, D=>$delta2 }];
705         push @$results, { Path => $path,
706                           SolKinds =>  [ 'twoarcs', 'cross' ] };
707     }
708 }
709     
710 sub joins_arcsline ($$$$) {
711     my ($results, $from,$to,$minradius) = @_;
712     # two circular arcs of specified radius
713     # with an intervening straight
714     my ($lr,$inv, $c,$d,$alpha,$t,$k,$l,$rpmsina,$rcosa,$linelen, $path);
715     if ($minradius<=1e-6) { o("%     arcsline no-radius\n"); return; }
716     foreach $lr (qw(-1 +1)) {
717         foreach $inv (qw(-1 +1)) {
718             $c=ev_compose({},$from,{X=>0,Y=>-$lr*$minradius, A=>0 });
719             $d=ev_compose({},$to,{X=>0, Y=>-$inv*$lr*$minradius, A=>$pi });
720             $t= v_dist($c,$d);
721             o("%     arcsline $lr $inv t=$t ");
722             if ($t < 1e-6) { o("concentric"); next; }
723             $c->{A}= $d->{A}= ev_bearing($c,$d);
724             o("bearing ".ang2deg($c->{A}));
725             if ($inv>0) {
726                 o("\n");
727                 $k= ev_compose({}, $c, { X=>0, Y=>$lr*$minradius, A=>0 });
728                 $l= ev_compose({}, $d, { X=>0, Y=>$lr*$minradius, A=>0 });
729                 $linelen= $t;
730             } else {
731                 my ($cosalpha) = 2.0 * $minradius / $t;
732                 if ($cosalpha > (1.0 - 1e-6)) { o(" too-close\n"); next; }
733                 $alpha= acos($cosalpha);
734                 $rpmsina= $lr * $minradius * sin($alpha);
735                 $rcosa= $minradius * $cosalpha;
736                 $k= ev_compose({}, $c, { X=>$rcosa, Y=>$rpmsina, A=>0 });
737                 $l= ev_compose({}, $d, { X=>-$rcosa, Y=>-$rpmsina, A=>0 });
738                 $k->{A}= $l->{A}= ev_bearing($k,$l);
739                 o(" alpha=".ang2deg($alpha)." kl^=".ang2deg($k->{A})."\n");
740                 $linelen= v_dist($k,$l);
741             }
742             $path= [{ T => Arc, F => $from, C => $c,
743                       R =>$lr*$minradius,
744                       D => -$lr * a_normalise
745                           ($lr * ($from->{A} - $k->{A}), 0) },
746                     { T => Line, A => $k, B => $l, L => $linelen },
747                     { T => Arc, F => $l, C => $d,
748                       R => $inv*$lr*$minradius,
749                       D => -$lr*$inv * a_normalise
750                           (-$lr*$inv * ($to->{A} - $l->{A}), 0) }];
751             push @$results,
752             { Path => $path,
753               SolKinds => [ 'arcsline', ($inv<0 ? 'cross' : 'loop') ] };
754         }
755     }
756 }
757
758 sub joins_arcline ($$$$) {
759     my ($results, $from,$to,$minradius) = @_;
760     # one circular arc and a straight line
761     my ($swap,$echoice,$path, $ap,$bp,$av,$bv, $e,$f, $ae,$af,$afae);
762     my ($dak,$ak,$kj,$k,$j,$aja,$jl,$l,$jc,$lc,$c,$rj,$rb);
763     foreach $swap (qw(-1 +1)) {
764         foreach $echoice (qw(0 1)) {
765             $ap= $from; $bp= { %$to }; $bp->{A} += $pi;
766             ($ap,$bp)= ($bp,$ap) if $swap<0;
767             $av= ev_byang({}, $ap->{A});
768             $bv= ev_byang({}, $bp->{A});
769             $e= ev_byang({}, 0.5 * ($ap->{A} + $bp->{A} + $echoice * $pi));
770             $f= v_rotateright($e);
771             o("%     arcline $swap $echoice e ".loc2dbg($e)."\n");
772             $ae= v_dotproduct($av,$e);
773             $af= v_dotproduct($av,$f);
774             o("%     arcline $swap $echoice a.e=$ae a.f=$af ");
775             if (abs($ae) < 1e-6) { o(" singular\n"); next; }
776             $afae= $af/$ae;
777             o("a.f/a.e=$afae\n");
778             $dak= v_dotproduct(v_subtract($ap,$bp), $e);
779             $ak= v_scalarmult($dak, $e);
780             $kj= v_scalarmult($dak * $afae, $f);
781             $k= v_add($ap, $ak);
782             $j= v_add($k, $kj);
783             $aja= v_dotproduct(v_subtract($ap,$j), $av);
784             o("%     arcline $swap $echoice d_ak=$dak aj.a=$aja ");
785             if ($aja < 0) { o(" backwards aj\n"); next; }
786             $jl= v_scalarmult(0.5, v_subtract($j, $bp));
787             $lc= v_scalarmult(-v_dotproduct($jl, $f) * $afae, $e);
788             $l= v_add($j, $jl);
789             $c= v_add($l, $lc);
790             $rj= v_dotproduct(v_subtract($j,$c), v_rotateright($av));
791             $rb= v_dotproduct(v_subtract($c,$bp), v_rotateright($bv));
792             o("r_j=$rj r_b=$rb ");
793             if ($rj * $rb < 0) { o(" backwards b\n"); next; }
794             if (abs($rj) < $minradius) { o(" too-small\n"); next; }
795             o("ok\n");
796             $j->{A}= $ap->{A};
797             $c->{A}= 0;
798             $path= [{ T => Line, A => $ap, B => $j, L => $aja },
799                     { T => Arc, F => $j, C => $c, R => $rj,
800                       D => -signum($rj) * a_normalise
801                           (-signum($rj) * ($bp->{A} + $pi - $j->{A}), 0) }];
802             $path= [ reverse @$path ] if $swap<0;
803             push @$results, { Path => $path, SolKinds =>  [ 'arcline' ] };
804         }
805     }
806 }
807
808 sub cmd_join {
809     my ($from,$to,$minradius);
810     my (@results,$result);
811     my ($path,$segment,$bestpath,$len,$scores,$bestscores,@bends,$skl);
812     my ($crit,$cs,$i,$cmp);
813     $from= can(\&cva_idex);
814     $to= can(\&cva_idex);
815     $minradius= can(\&cva_len);
816     o("%   join ".loc2dbg($from)."..".loc2dbg($to)." $minradius\n");
817     joins_twoarcs(\@results, $from,$to,$minradius);
818     joins_arcsline(\@results, $from,$to,$minradius);
819     joins_arcline(\@results, $from,$to,$minradius);
820     foreach $result (@results) {
821         $path= $result->{Path};
822         $skl= $result->{SolKinds};
823         o("%   possible path @$skl $path\n");
824         $len= 0;
825         @bends= ();
826         foreach $segment (@$path) {
827             if ($segment->{T} eq Arc) {
828                 o("%     Arc C ".loc2dbg($segment->{C}).
829                   " R $segment->{R} D ".ang2deg($segment->{D})."\n");
830                 $len += abs($segment->{R} * $segment->{D});
831                 push @bends, -abs($segment->{R}) * $segment->{D}; # right +ve
832             } elsif ($segment->{T} eq Line) {
833                 o("%     Line A ".loc2dbg($segment->{A}).
834                   " B ".loc2dbg($segment->{A})." L $segment->{L}\n");
835                 $len += abs($segment->{L});
836             } else {
837                 die "unknown segment $segment->{T}";
838             }
839         }
840         o("%    length $len bends @bends.\n");
841         $scores= [];
842         foreach $crit (@al, 'short') {
843             if ($crit eq 'long') { $cs= $len; }
844             elsif ($crit eq 'short') { $cs= -$len; }
845             elsif ($crit =~ m/^(begin|end|)(left|right)$/) {
846                 if ($1 eq 'begin') { $cs= $bends[0]; }
847                 elsif ($1 eq 'end') { $cs= $bends[$#bends]; }
848                 else { $cs=0; map { $cs += $_ } @bends; }
849                 $cs= -$cs if $2 eq 'left';
850             } elsif ($crit =~ m/^(\!?)(twoarcs|arcs?line|cross|loop)$/) {
851                 $cs= !!(grep { $2 eq $_ } @$skl) != ($1 eq '!');
852             } else {
853                 die "unknown sort criterion $crit";
854             }
855             push @$scores, $cs;
856         }
857         o("%    scores @$scores\n");
858         if (defined $bestpath) {
859             for ($i=0,$cmp=0; !$cmp && $i<@$scores; $i++) {
860                 $cmp= $scores->[$i] <=> $bestscores->[$i];
861             }
862             next if $cmp < 0;
863         }
864         $bestpath= $path;
865         $bestscores= $scores;
866     }
867     die "no solution" unless defined $bestpath;
868     o("%   chose path $bestpath @al\n");
869     @al= ();
870     foreach $segment (@$bestpath) {
871         if ($segment->{T} eq 'Arc') {
872             arc({}, $segment->{C},$segment->{F},$segment->{R},$segment->{D});
873         } elsif ($segment->{T} eq 'Line') {
874             line($segment->{A}, $segment->{B}, $segment->{L});
875         } else {
876             die "unknown segment";
877         }
878     }
879 }
880
881 sub line ($$$) {
882     my ($from,$to,$len) = @_;
883     parametric_segment(0.0, 1.0, abs($len) + 1e-6, undef, sub {
884         ev_lincomb({}, $from, $to, $param);
885     });
886 }
887
888 sub cmd_extend {
889     my ($from,$to,$radius,$len,$upto,$ctr,$beta,$ang,$how,$sign_r);
890     $from= can(\&cva_idex);
891     $to= can(\&cva_idnew);
892     printf DEBUG "from $from->{X} $from->{Y} $from->{A}\n";
893     $how= can(cvam_enum(qw(len upto ang uptoang parallel)));
894     if ($how eq 'len') { $len= can(\&cva_len); }
895     elsif ($how =~ m/ang$/) { $ang= can(\&cva_ang); }
896     elsif ($how eq 'parallel' || $how eq 'upto') { $upto= can(\&cva_idex); }
897     $radius= cano(\&cva_len, 'Inf'); # +ve is right hand bend
898     if ($radius eq 'Inf') {
899 #       print DEBUG "extend inf $len\n";
900         if ($how eq 'upto') {
901             $len= ($upto->{X} - $from->{X}) * cos($from->{A})
902                 + ($upto->{Y} - $from->{Y}) * sin($from->{A});
903         } elsif ($how eq 'len') {
904         } else {
905             die "len of straight spec by angle";
906         }
907         printf DEBUG "len $len\n";
908         $to->{X}= $from->{X} + $len * cos($from->{A});
909         $to->{Y}= $from->{Y} + $len * sin($from->{A});
910         $to->{A}= $from->{A};
911         line($from,$to,$len);
912     } else {
913         my ($sign_r, $sign_ang, $ctr, $beta_interval, $beta, $delta);
914         print DEBUG "radius >$radius<\n";
915         $radius *= $ctx->{Trans}{R};
916         $sign_r= signum($radius);
917         $sign_ang= 1;
918         $ctr->{X}= $from->{X} + $radius * sin($from->{A});
919         $ctr->{Y}= $from->{Y} - $radius * cos($from->{A});
920         if ($how eq 'upto') {
921             $beta= atan2(-$sign_r * ($upto->{X} - $ctr->{X}),
922                          $sign_r * ($upto->{Y} - $ctr->{Y}));
923             $beta_interval= 1.0;
924         } elsif ($how eq 'parallel') {
925             $beta= $upto->{A};
926             $beta_interval= 1.0;
927         } elsif ($how eq 'uptoang') {
928             $beta= input_absang($ang);
929             $beta_interval= 2.0;
930         } elsif ($how eq 'len') {
931             $sign_ang= signum($len);
932             $beta= $from->{A} - $sign_r * $len / abs($radius);
933             $beta_interval= 2.0;
934         } else {
935             $sign_ang= signum($ang);
936             $beta= $from->{A} - $sign_r * $ang;
937             $beta_interval= 2.0;
938         }
939     printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n";
940         $beta += $sign_ang * $sign_r * 4.0 * $pi;
941         for (;;) {
942             $delta= $beta - $from->{A};
943             last if $sign_ang * $sign_r * $delta <= 0;
944             $beta -= $sign_ang * $sign_r * $beta_interval * $pi;
945         }
946     printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n";
947         arc($to, ,$ctr,$from, $radius,$delta);
948     }
949     printf DEBUG "to $to->{X} $to->{Y} $to->{A}\n";
950 }
951
952 sub loc2dbg ($) {
953     my ($loc) = @_;
954     return "$loc->{X} $loc->{Y} ".ang2deg($loc->{A});
955 }
956 sub ang2deg ($) {
957     return $_[0] * 180 / $pi;
958 }
959 sub input_absang ($) {
960     return $_[0] * $ctx->{Trans}{R} + $ctx->{Trans}{A};
961 }
962 sub input_abscoords ($$) {
963     my ($in,$out);
964     ($in->{X}, $in->{Y}) = @_;
965     $in->{A}= 0.0;
966     $out= ev_compose({}, $ctx->{Trans}, $in);
967     return ($out->{X}, $out->{Y});
968 }
969
970 sub newctx (;$) {
971     my ($ctx_save) = @_;
972     $ctx= {
973         Trans => { X => 0.0, Y => 0.0, A => 0.0, R => 1.0 },
974         InRunObj => "",
975         DrawMap => sub { $_[0]; }
976         };
977     %{ $ctx->{Layer} }= %{ $ctx_save->{Layer} }
978         if defined $ctx_save;
979 }
980
981 our $defobj_save;
982 our $defobj_ispart;
983
984 sub cmd_defobj { cmd__defobj(0); }
985 sub cmd_defpart { cmd__defobj(1); }
986 sub cmd__defobj ($) {
987     my ($ispart) = @_;
988     my ($id);
989     $id= can(\&cva_idstr);
990     die "nested defobj" if $defobj_save;
991     die "repeated defobj" if exists $objs{$id};
992     $defobj_save= $ctx;
993     $defobj_ispart= $ispart;
994     newctx($defobj_save);
995     $ctx->{CmdLog}= [ ];
996     $ctx->{InDefObj}= $id;
997     $ctx->{Draw}= $defobj_save->{Draw}.'X';
998     $ctx->{DrawMap}= sub { ''; };
999     $ctx->{Layer}= { Level => 5, Kind => '' };
1000 }
1001
1002 sub cmd_enddef {
1003     my ($bit,$id);
1004     $id= $ctx->{InDefObj};
1005     die "unmatched enddef" unless defined $id;
1006     foreach $bit (qw(CmdLog Loc)) {
1007         $objs{$id}{$bit}= $ctx->{$bit};
1008     }
1009     $objs{$id}{Part}= $defobj_ispart;
1010     $ctx= $defobj_save;
1011     $defobj_save= undef;
1012     $defobj_ispart= undef;
1013 }
1014
1015 sub cmd__runobj ($) {
1016     my ($obj_id)=@_;
1017     my ($c);
1018     local (@al);
1019     dv("cmd__runobj $obj_id ",'$ctx',$ctx);
1020     foreach $c (@{ $objs{$obj_id}{CmdLog} }) {
1021         @al= @$c;
1022         next if $al[0] eq 'enddef';
1023         cmd__one();
1024     }
1025 }
1026
1027 sub layer_draw ($$) {
1028     my ($k,$l) = @_;
1029     my ($eo,$cc, $r);
1030     if ($k eq '') {
1031         $r= 'RLMN';
1032     } elsif ($k eq 's') {
1033         $r= '';
1034     } elsif ($k eq 'l') {
1035         $r= 'CLMN';
1036     } else {
1037         $r= 'ARSCLMNO';
1038     }
1039     foreach $eo (@eopts) {
1040 #print STDERR "$. layer $k$l eo $eo re $eo->{GlobRe} then $eo->{DrawMods} now $r\n";
1041         next unless $k =~ m/^$eo->{GlobRe}$/;
1042 #print STDERR "$. layer $k$l eo re $eo->{GlobRe} match\n";
1043         next unless &{ $eo->{LayerCheck} }($l);
1044 #print STDERR "$. layer $k$l eo re $eo->{GlobRe} checked\n";
1045         foreach $cc (split //, $eo->{DrawMods}) {
1046             $r =~ s/$cc//ig;
1047             $r .= $cc if $cc =~ m/[A-Z]/;
1048         }
1049     }
1050 #print STDERR "layer $k$l gives $r (before map)\n";
1051     $r= &{ $ctx->{DrawMap} }($r);
1052     return $r;
1053 }
1054
1055 sub cmd_layer {
1056     my ($kl, $k,$l);
1057     $kl= can(\&cva_identity);
1058     $kl =~ m/^([A-Za-z_]*)(\d*|\=|\*)$/ or die "invalid layer spec";
1059     ($k,$l)=($1,$2);
1060     $l= $output_layer if $l eq '*';
1061     $l= $ctx->{Layer}{Level} if $l =~ m/^\=?$/;
1062     $ctx->{Layer}{Kind}= $k;
1063     $ctx->{Layer}{Level}= $l;
1064     $ctx->{Draw}= layer_draw($k,$l);
1065 }    
1066
1067 sub cmd_part { cmd__obj(Part); }
1068 sub cmd_obj { cmd__obj(1); }
1069 sub cmd_objflip { cmd__obj(-1); }
1070
1071 sub cmd__obj ($) {
1072     my ($how)=@_;
1073     my ($obj_id, $ctx_save, $pfx, $actual, $formal_id, $formal, $formcv);
1074     my ($part_name, $ctx_inobj, $obj, $id, $newid, $newpt);
1075     if ($how eq Part) {
1076         $part_name= can(\&cva_idstr);
1077         $how= (@al && $al[0] =~ s/^\^//) ? -1 : +1;
1078     }
1079     $obj_id= can(\&cva_idstr);
1080     if (defined $part_name) {
1081         $formal_id= can(\&cva_idstr);
1082         $actual= cano(\&cva_idex, undef);
1083         if (!defined $actual) {
1084             $actual= cva_idex("${part_name}_${formal_id}");
1085         }
1086     } else {
1087         $actual= can(\&cva_idex);
1088         $formal_id= can(\&cva_idstr);
1089     }
1090     $obj= $objs{$obj_id};
1091     dv("cmd__obj ",'$obj',$obj);
1092     die "unknown obj $obj_id" unless $obj;
1093     $formal= $obj->{Loc}{$formal_id};
1094     die "unknown formal $formal_id" unless $formal;
1095     $ctx_save= $ctx;
1096     newctx($ctx_save);
1097     $how *= $ctx_save->{Trans}{R};
1098     $ctx->{Trans}{R}= $how;
1099     $ctx->{Trans}{A}= $actual->{A} - $formal->{A}/$how;
1100     $formcv= ev_compose({}, $ctx->{Trans},$formal);
1101     $ctx->{Trans}{X}= $actual->{X} - $formcv->{X};
1102     $ctx->{Trans}{Y}= $actual->{Y} - $formcv->{Y};
1103     if (defined $part_name) {
1104         $ctx->{InRunObj}= $ctx_save->{InRunObj}."${part_name}:";
1105     } else {
1106         $ctx->{InRunObj}= $ctx_save->{InRunObj}."${obj_id}::";
1107     }
1108     $ctx->{DrawMap}= sub {
1109         my ($i) = @_;
1110         $i= &{ $ctx_save->{DrawMap} }($i);
1111         if ($obj->{Part}) {
1112             $i =~ s/[LMN]//g;
1113             $i =~ s/O/MNO/;
1114         } else {
1115             $i =~ s/[LM]//g;
1116             $i =~ s/N/MN/;
1117         }
1118         return $i;
1119     };
1120     $ctx->{Draw}= &{ $ctx->{DrawMap} }($ctx_save->{Draw});
1121     cmd__runobj($obj_id);
1122     if (defined $part_name) {
1123         $pfx= $part_name.'_';
1124     } else {
1125         if (@al && $al[0] eq '=') {
1126             $pfx= ''; shift @al;
1127         } else {
1128             $pfx= cano(\&cva_idstr,undef);
1129         }
1130     }
1131     $ctx_inobj= $ctx;
1132     $ctx= $ctx_save;
1133     if (defined $pfx) {
1134         foreach $id (keys %{ $ctx_inobj->{Loc} }) {
1135             next if $id eq $formal_id;
1136             $newid= $pfx.$id;
1137             next if exists $ctx_save->{Loc}{$newid};
1138             $newpt= cva_idnew($newid);
1139             %$newpt= %{ $ctx_inobj->{Loc}{$id} };
1140         }
1141     }
1142     if (defined $part_name) {
1143         my ($formalr_id, $actualr_id, $formalr, $actualr);
1144         while (@al) {
1145             die "part results come in pairs\n" unless @al>=2;
1146             ($formalr_id, $actualr_id, @al) = @al;
1147             if ($actualr_id =~ s/^\-//) {
1148                 $formalr_id= "-$formalr_id";
1149                 $formalr_id =~ s/^\-\-//;
1150             }
1151             {
1152                 local ($ctx) = $ctx_inobj;
1153                 $formalr= cva_idex($formalr_id);
1154             }
1155             $actualr= cva_idnew($actualr_id);
1156             %$actualr= %$formalr;
1157         }
1158     }
1159 }
1160
1161 sub cmd__do {
1162     my ($cmd);
1163 dv("cmd__do $ctx @al ",'$ctx',$ctx);
1164     $cmd= can(\&cva_cmd);
1165     my ($lm,$id,$loc,$io,$ad,$draw,$thendrawre);
1166     $io= defined $ctx->{InDefObj} ? "$ctx->{InDefObj}!" : $ctx->{InRunObj};
1167     o("%L cmd   $io $cmd @al\n");
1168     $ctx->{LocsMade}= [ ];
1169     {
1170         no strict 'refs';
1171         &{ "cmd_$cmd" };
1172     };
1173     die "too many args" if @al;
1174     foreach $lm (@{ $ctx->{LocsMade} }) {
1175         $id= $lm->{Id};
1176         $loc= $ctx->{Loc}{$id};
1177         $loc->{A} += $pi if $lm->{Neg};
1178         $ad= ang2deg($loc->{A});
1179         ol("%L point $io$id ".loc2dbg($loc)." ($lm->{Neg})\n");
1180         $draw= layer_draw($loc->{LayerKind}, $ctx->{Layer}{Level});
1181         if ($draw =~ m/[LM]/) {
1182             ol("    gsave\n".
1183                "      $loc->{X} $loc->{Y} translate $ad rotate\n");
1184             if ($draw =~ m/M/) {
1185                 ol("      0 $allwidthmin newpath moveto\n".
1186                    "      0 -$allwidthmin lineto\n".
1187                    "      $lmu_marklw setlinewidth stroke\n");
1188             }
1189             if ($draw =~ m/L/) {
1190                 ol("      /s ($id) def\n".
1191                    "      lf setfont\n".
1192                    "      /sx5  s stringwidth pop\n".
1193                    "      0.5 mul $lmu_txtboxpadx add def\n".
1194                    "      -90 rotate  0 $lmu_txtboxoff translate  newpath\n".
1195                    "      sx5 neg  0             moveto\n".
1196                    "      sx5 neg  $lmu_txtboxh  lineto\n".
1197                    "      sx5      $lmu_txtboxh  lineto\n".
1198                    "      sx5      0             lineto closepath\n".
1199                    "      gsave  1 setgray fill  grestore\n".
1200                    "      $lmu_txtboxlw setlinewidth stroke\n".
1201                    "      sx5 neg $lmu_txtboxpadx add  $lmu_txtboxtxty\n".
1202                    "      moveto s show\n");
1203             }
1204             ol("      grestore\n");
1205         }
1206     }
1207 }
1208
1209 sub cmd_showlibrary {
1210     my ($obj_id, $y, $x, $ctx_save, $width, $height);
1211     my ($max_x, $min_x, $max_y, $min_y, $nxty, $obj, $loc, $pat, $got, $glob);
1212     my ($adj);
1213     $x=$olu_left; $y=$olu_bottom; undef $nxty;
1214     $ctx_save= $ctx;
1215     foreach $obj_id (sort keys %objs) {
1216         $got= 1;
1217         foreach $glob (@al) {
1218             $pat= $glob;
1219             $got= !($pat =~ s/^\!//);
1220             die "bad pat" if $pat =~ m/[^0-9a-zA-Z_*?]/;
1221             $pat =~ s/\*/\.*/g; $pat =~ s/\?/./g;
1222             last if $obj_id =~ m/^$pat$/;
1223             $got= !$got;
1224         }
1225         next unless $got;           
1226         $obj= $objs{$obj_id};
1227         next unless $obj->{Part};
1228         ($min_x, $max_x, $min_y, $max_y) = bbox($obj->{Loc});
1229         newctx($ctx_save);
1230
1231         for (;;) {
1232             $width= $max_x - $min_x;
1233             $height= $max_y - $min_y;
1234             if ($width < $height) {
1235                 $ctx->{Trans}{A}= 0;
1236                 $ctx->{Trans}{X}= $x - $min_x;
1237                 $ctx->{Trans}{Y}= $y - $min_y + $olu_textheight;
1238             } else {
1239                 ($width,$height)=($height,$width);
1240                 $ctx->{Trans}{A}= 0.5 * $pi;
1241                 $ctx->{Trans}{X}= $x + $max_y;
1242                 $ctx->{Trans}{Y}= $y - $min_x + $olu_textheight;
1243             }
1244             $adj= length($obj_id) * $olu_textallowperc - $width;
1245             $adj=0 if $adj<0;
1246             $width += $adj;
1247             $ctx->{Trans}{X} += 0.5 * $adj;
1248             if ($x + $width > $olu_right && defined $nxty) {
1249                 $x= $olu_left;
1250                 $y= $nxty;
1251                 undef $nxty;
1252             } elsif ($y + $height > $olu_top && $y > $olu_bottom) {
1253                 oflushpage();
1254                 $x= $olu_left; $y= $olu_bottom;
1255                 undef $nxty;
1256             } else {
1257                 last;
1258             }
1259         }
1260             
1261         $ctx->{InRunObj}= $ctx_save->{InRunObj}."${obj_id}//";
1262         $ctx->{Draw}= $ctx_save->{Draw};
1263         cmd__runobj($obj_id);
1264         ol("    gsave\n".
1265            "      /s ($obj_id) def\n".
1266            "      lf setfont\n      ".
1267            ($x + 0.5*$width)." ".($y - $olu_textheight)." moveto\n".
1268            "      s stringwidth pop -0.5 mul  0  rmoveto\n".
1269            "      s show grestore\n");
1270         $x += $width + $olu_gap_x;
1271         upd_max(\$nxty, $y + $height + $olu_gap_y + $olu_textheight);
1272     }
1273     @al= ();
1274     $ctx= $ctx_save;
1275 }
1276
1277 sub cmd__one {
1278     cmd__do();
1279 }
1280
1281 print
1282     "%!\n".
1283     "  /lf /Courier-New findfont $lmu_marktpt scalefont def\n".
1284     "  615 0 translate 90 rotate\n".
1285     "  -709 $page_x mul  -566 $page_y mul  translate\n".
1286     "  $ptscale $ptscale scale\n"
1287     or die $!;
1288
1289 newctx();
1290
1291 open DEBUG, ($debug ? ">&2" : ">/dev/null") or die $!;
1292
1293 if ($debug) {
1294     select(DEBUG); $|=1;
1295     select(STDOUT); $|=1;
1296 }
1297
1298 $ctx->{Draw}= '';
1299
1300 @al= qw(layer 5);
1301 cmd__one();
1302
1303 while (<>) {
1304     next if m/^\s*\#/;
1305     chomp; s/^\s+//; s/\s+$//;
1306     @al= split /\s+/, $_;
1307     next unless @al;
1308     print DEBUG "=== @al\n";
1309     last if $al[0] eq 'eof';
1310     push @{ $ctx->{CmdLog} }, [ @al ] if exists $ctx->{CmdLog};
1311     cmd__one();
1312 }
1313
1314 oflushpage();
1315
1316 {
1317     my ($min_x, $max_x, $min_y, $max_y) = bbox($ctx->{Loc});
1318     my ($bboxstr);
1319     if (defined $min_x) {
1320         $bboxstr= sprintf("width  %.2d (%.2d..%2.d)\n".
1321                           "height %.2d (%.2d..%2.d)\n",
1322                           $max_x - $min_x, $min_x, $max_x,
1323                           $max_y - $min_y, $min_y, $max_y);
1324     } else {
1325         $bboxstr= "no locs, no bbox\n";
1326     }
1327     if (!$quiet) { print STDERR $bboxstr; }
1328     $bboxstr =~ s/^/\%L bbox /mg;
1329     print $bboxstr or die $!;
1330 }