chiark / gitweb /
trim-ps script to make files print faster
[trains.git] / layout / layout
index c509ecc0abcd98d33d01ce63f5f8dbc8743b59f9..d20102316e4e885b4956c60bcc3c9ce069f8f079 100755 (executable)
 #!/usr/bin/perl -w
 
 use POSIX;
+use IO::Handle;
+use IO::File;
+
+use strict;
+no strict 'subs';
+
+our $scale= 7.0;
+our $page_x= 0;
+our $page_y= 0;
+our $quiet=0;
+our $debug=0;
+our $output_layer= '*';
+our $subsegcmapreq=0;
+our $subsegmovfeatpos='f';
+our $subsegcmapangscale;
+
+our $ps_page_shift= 615;
+our $ps_page_xmul= 765.354;
+our $ps_page_ymul= 538.583;
+
+our @eopts;
+our @segments= ('/');
+our %subsegcmap;
+
+our $drawers= 'arscldmnog';
+our %chdraw_emap= qw(A ARScgd
+                    R aRscgD
+                    S aRScgd
+                    C arsCgd
+                    c Arscgd
+                    r arcs
+                    L LMg
+                    l l
+                    M Mnog
+                    N MNog
+                    O MNOg
+                    m mnol
+                    G Garsclmno);
+
+while (@ARGV && $ARGV[0] =~ m/^\-/) {
+    last if $ARGV[0] eq '-';
+    $_= shift @ARGV;
+    last if $_ eq '--';
+    s/^\-//;
+    while (length) {
+       if (s/^D(\d+)//) { $debug= $1; }
+       elsif (s/^D//) { $debug++; }
+       elsif (s/^q//) { $quiet=1; }
+       elsif (s/^l(\d+|\*)//) { $output_layer=$1; }
+       elsif (s/^S([0-9.]+)$//) { $scale= $1 * 1.0; }
+       elsif (s/^P(\d+)x(\d+)$//) { $page_x= $1; $page_y= $2; }
+       elsif (s/^GR//) { $subsegcmapreq=1; }
+       elsif (s/^GP(\d+|f)$//) { $subsegmovfeatpos=$1; }
+       elsif (s/^GL(.*)$//) {
+           my ($sscmfn) = $1;
+           my ($sscmf, $datum, $csss, $angbits);
+           local ($_);
+           $sscmf= new IO::File $sscmfn, 'r'
+               or die "$sscmfn: cannot open: $!\n";
+           for (;;) {
+               $!=0; $_= <$sscmf>; die $! unless defined $_;
+               last if m/^E/;
+               next unless m/^C/;
+               m,^C\s+(\w*/(?:[A-Za-z_]+)?)\s+(0x[0-9a-f]+)\s+(\d+)\s*$,
+                   or die "$sscmfn:$.: syntax error in subseg cmap\n";
+               ($csss,$datum,$angbits)= ($1,$2,$3);
+               if (!defined $subsegcmapangscale) {
+                   $subsegcmapangscale= 1<<$angbits;
+               } else {
+                   die "angbits varies" if $subsegcmapangscale != 1<<$angbits;
+               }
+               $datum= hex($datum);
+               if ($datum & 0x0ff) {
+                   die "sorry, cannot put any movfeatpos or segment in red";
+               }
+               $subsegcmap{$csss}= sprintf("%.6f %.6f",
+                                           (($datum >> 8) & 0xff)/255.0,
+                                           (($datum >> 16) & 0xff)/255.0);
+           }
+           $sscmf->error and die "$sscmfn: error reading: $!\n";
+           close $sscmf;
+       } elsif (s/^(e)
+              ((?:[a-z]|\*|\?|\[[a-z][-a-z]*\])*?)
+              (\~?) (\d*) (\=*|\-+|\++) (\d*|\*)
+              ([a-z]+)$//ix) {
+           my ($ee,$g,$n,$d,$c,$v,$cc) = ($1,$2,$3,$4,$5,$6,$7);
+           my ($eo, $invert, $lfn, $ccc, $sense,$limit);
+           $g =~ s/\?/\./g; $g =~ s/\*/\.\*/g;
+           die '-[eE]GND[=]* not allowed' if $v eq '*' && length $d;
+           $d= $output_layer if !length $d;
+           $d= 5 if $d eq '*';
+           $invert= length $n;
+           $c= '=' if !length $c;
+           if (length $v && $v ne '*') {
+               die '-[eE]GN[D]CCV not allowed' if length $c > 1;
+               $c= $c x $v;
+           }
+           if ($c =~ m/^[-+]/) {
+               die '-[eE]GN+/-* not allowed' if $v eq '*';
+               $sense= ($&.'1') + 0;
+               $limit= ($sense * $d) + length($c) - 1;
+               $lfn= sub {
+                   ($output_layer eq '*' ? $d
+                    : $_[0]) * $sense >= $limit
+                        xor $invert;
+               };
+           } elsif ($v eq '*') {
+               $lfn= sub { !$invert; };
+           } else {
+               $limit= length($c) - 1;
+               $lfn= sub {
+#my ($lfn_result)=(
+                   ($output_layer eq '*' ? 1
+                    : abs($_[0] - $d) <= $limit)
+                       xor $invert
+#)
+                           ;
+#print STDERR "output layer $output_layer; asking re $_[0] rel $d lim $limit invert $invert result $lfn_result\n";
+#$lfn_result;
+               };
+           }
+           $ccc= '';
+           foreach $c (split //, $cc) {
+               if ($ee eq 'e') {
+                   die "bad -e option $c" unless defined $chdraw_emap{$c};
+                   $ccc .=  $chdraw_emap{$c};
+               } else {
+                   die "bad -E option $c" unless $c =~ m/[$drawers]/i;
+                   $ccc .= $c;
+               }
+           }
+           $eo->{GlobRe}= $g;
+           $eo->{LayerCheck}= $lfn;
+           $eo->{DrawMods}= $ccc;
+#print STDERR "created eo $eo re $eo->{GlobRe} n=$n d=$d v=$v c=$c limit=$limit cc=$cc\n";
+           push @eopts, $eo;
+       } elsif (m/^S/) {
+           die "-S option must come right at the start and have numeric arg";
+       } else {
+           die "unknown option -$_";
+       }
+    }
+}
+
+our $ptscale= 72/25.4 / $scale;
+
+our $psu_ulen= 4.5;
+our $psu_edgelw= 0.5;
+our $psu_ticklw= 0.1;
+our $psu_ticksperu= 1;
+our $psu_ticklen= 5.0;
+our $psu_gauge= 9;
+our $psu_sleeperlen= 17;
+our $psu_sleeperlw= 15;
+our $psu_raillw= 1.0;
+our $psu_thinlw= 1.0;
+our %psu_subseglw;
+$psu_subseglw{'e'}= 20.0;
+$psu_subseglw{'m'}= 15.0;
+
+our $lmu_marklw= 4;
+our $lmu_marktpt= 11;
+our $lmu_txtboxtxty= $lmu_marktpt * 0.300;
+our $lmu_txtboxh= $lmu_marktpt * 1.100;
+our $lmu_lenboxh= $lmu_marktpt * 1.100;
+our $lmu_txtboxpadx= $lmu_marktpt * 0.335;
+our $lmu_lenboxpadx= $lmu_marktpt * 0.005;
+our $lmu_txtboxoff= $lmu_marklw / 2;
+our $lmu_lenboxoff= -$lmu_marklw * 1.0;
+our $lmu_txtboxlw= 1;
+
+our $olu_left= 10 * $scale;
+our $olu_right= 217 * $scale - $olu_left;
+our $olu_bottom= 20 * $scale;
+our $olu_top= 270 * $scale - $olu_bottom;
+our $olu_gap_x= 30;
+our $olu_gap_y= 60;
+our $olu_textheight= 15;
+our $olu_textallowperc= $lmu_marktpt * 5.0/11;
+
+our $pi= atan2(0,-1);
+
+sub allwidth2 ($) {
+    my ($radius)= @_;
+    return 27 unless defined $radius;
+    $radius= abs($radius);
+    return ($radius >= 450 ? 33 :
+           $radius >= 400 ? 35 :
+           37);
+}
+sub allwidth ($) { return allwidth2($_[0]) * 0.5; }
+
+our $allwidthmax= allwidth(0);
+our $allwidthmin= allwidth(undef);
 
 # Data structures:
 #  $ctx->{CmdLog}= undef                  } not in defobj
 #  $ctx->{CmdLog}[]= [ command args ]     } in defobj
-#  $ctx->{LocsMade}[]= $id
+#  $ctx->{Parent}= $parent_ctx or undef
+#  $ctx->{LocsMade}[]{Id}= $id
+#  $ctx->{LocsMade}[]{Neg}= 1 or 0
 #  $ctx->{Loc}{$id}{X}
 #  $ctx->{Loc}{$id}{Y}
 #  $ctx->{Loc}{$id}{A}
+#  $ctx->{Loc}{$id}{LayerKind}
 #  $ctx->{Trans}{X}       # transformation.  is ev representing
 #  $ctx->{Trans}{Y}       # new origin.  (is applied at _input_
 #  $ctx->{Trans}{A}       # not at plot-time)
 #  $ctx->{Trans}{R}       # but multiply all y coords by this!
-#  $ctx->{Draw}{T}        # 1 or '' for drawing track
-#  $ctx->{Draw}{L}        # L1 or 1 or '' for labelling or drawing locs
+#  $ctx->{Draw}           # sequence of one or more chrs from uc $drawers
+#                         #  possibly including X meaning never draw
+#                         #  anything now (eg in defobj)
+#  $ctx->{DrawMap}        # =$fn s.t.
+#                         #  &$fn($drawchrs_spec_by_layer_cmdline)
+#                         #   = $drawchrs_we_should_use_due_to_obj_etc
+#  $ctx->{SegName}        # initial segment name (at start of object or file)
+#                         #  or nonexistent if in object in unknown segment
+#                         #  may have leading `-'
+#  $ctx->{SegMapN}{$s}= $o
+#  $ctx->{SegMapNM}{$s}= $o
+#  $ctx->{SavedSegment}   # exists iff segment command used, is a $csss
+#  $ctx->{Layer}{Level}
+#  $ctx->{Layer}{Kind}
 #
 #  $objs{$id}{CmdLog}
 #  $objs{$id}{Loc}
+#  $objs{$id}{Part}       # 1 iff object is a part
+#
+#  $eopts[]{GlobRe}       # regexp for K
+#  $eopts[]{LayerCheck}   # =$fn where &$fn($l) is true iff layer matches
+#  $eopts[]{DrawMods}     # modifier chars for drawing
+#
+#  @segments= ( $csss0, $dist0, $csss1, $dist1, ..., $csssn )
+#                         # here each csss may have preceding `-'
+#
+#  $subsegcmap{$csss} = "$green $blue"
+#                         # $csss is canonical subseg spec; always has '/'
 
-#$debug=1;
-open DEBUG, ($debug ? ">&2" : ">/dev/null") or die $!;
+our $ctx;
+our %objs;
+our @al; # current cmd
 
-if ($debug) {
-    select(DEBUG); $|=1;
-    select(STDOUT); $|=1;
-}
+our $o='';
+our $ol='';
+
+our $param; # for parametric_segment
 
 # ev_... functions
 #
@@ -43,7 +264,7 @@ if ($debug) {
 
 sub ev_byang ($$;$) {
     # ev_byang(R, ANG,[LEN])
-    # result is evec of specified angle and length (default=1.0)
+    # result is evec LEN (default=1.0) from origin pointing in direction ANG
     my ($res,$ang,$len)=@_;
     $len=1.0 unless defined $len;
     $res->{X}= $len * cos($ang);
@@ -53,7 +274,8 @@ sub ev_byang ($$;$) {
 }
 sub ev_compose ($$$) {
     # ev_compose(SUM_R, A,B);
-    # appends B to A, result is end of B'
+    # appends B to A, result is end of new B
+    # (B's X is forwards from end of A, Y is translating left from end of A)
     # A may have a member R, which if provided then it should be 1.0 or -1.0,
     # and B's Y and A will be multiplied by R first (ie, we can reflect);
     my ($sum,$a,$b) = @_;
@@ -85,24 +307,82 @@ sub ev_lincomb ($$$$) {
     map { $r->{$_} = $q * $a->{$_} + $p * $b->{$_} } qw(X Y A);
     $r;
 }
-sub v_bearing ($$) {
-    # v_bearing(A,B)
-    # returns bearing of B from A (in radians)
-    # A->{A} and B->{A} are ignored
+sub a_normalise ($$) {
+    # a_normalise(A,Z)
+    # adds or subtracts 2*$pi to/from A until it is in [ Z , Z+2*$pi >
+    my ($a,$z)=@_;
+    my ($r);
+    $r= $z + fmod($a - $z, 2.0*$pi);
+    $r += 2*$pi if $r < $z;
+    return $r;
+}
+sub ev_bearing ($$) {
+    # ev_bearing(A,B)
+    # returns bearing of B from A
+    # value returned is in [ A->{A}, A->{A} + 2*$pi >
+    # A->{A} and B->{A} are otherwise ignored
     my ($a,$b)= @_;
-    return atan2($b->{Y} - $a->{Y},
-                $b->{X} - $a->{X});
-}               
+    my ($r);
+    $r= atan2($b->{Y} - $a->{Y},
+             $b->{X} - $a->{X});
+    $r= a_normalise($r,$a->{A});
+    return $r;
+}
+
+sub v_rotateright ($) {
+    # v_rotateright(A)
+    # returns image of A rotated 90 deg clockwise
+    my ($a)= @_;
+    return { X => $a->{Y}, Y => -$a->{X} };
+}
+sub v_dotproduct ($$) {
+    # v_dotproduct(A,B)
+    my ($a,$b)= @_;
+    return $a->{X} * $b->{X} + $a->{Y} * $b->{Y};
+}
+sub v_scalarmult ($$) {
+    # v_scalarmult(S,V)
+    # multiplies V by scalar S and returns product
+    my ($s,$v)=@_;
+    return { X => $s * $v->{X}, Y => $s * $v->{Y} };
+}
+sub v_add ($;@) {
+    # v_add(A,B,...)
+    # vector sum of all inputs
+    my (@i) = @_;
+    my ($r,$i);
+    $r= { X => 0.0, Y => 0.0 };
+    foreach $i (@i) { $r->{X} += $i->{X}; $r->{Y} += $i->{Y}; }
+    return $r;
+}    
+sub v_subtract ($$) {
+    # v_subtract(A,B)
+    # returns vector from A to B, ie B - A
+    my ($a,$b)= @_;
+    return { X => $b->{X} - $a->{X},
+            Y => $b->{Y} - $a->{Y} };
+}
+sub v_len ($) {
+    # v_len(V)
+    # scalar length of V
+    my ($v)=@_;
+    my ($x,$y) = ($v->{X}, $v->{Y});
+    return sqrt($x*$x + $y*$y);
+}
 sub v_dist ($$) {
     # v_dist(A,B)
     # returns distance from A to B
-    # A->{A} and B->{A} are ignored
-    my ($a,$b)= @_;
-    my ($xd,$yd);
-    $xd= $b->{X} - $a->{X};
-    $yd= $b->{Y} - $a->{Y};
-    return sqrt($xd*$xd + $yd*$yd);
-}               
+    return v_len(v_subtract($_[0],$_[1]));
+}
+
+sub upd_min ($$) {
+    my ($limr,$now)=@_;
+    $$limr= $now unless defined $$limr && $$limr <= $now;
+}
+sub upd_max ($$) {
+    my ($limr,$now)=@_;
+    $$limr= $now unless defined $$limr && $$limr >= $now;
+}
 
 sub canf ($$) {
     my ($converter,$defaulter)=@_;
@@ -116,13 +396,26 @@ sub canf ($$) {
 sub can ($) { my ($c)=@_; canf($c, sub { die "too few args"; }); }
 sub cano ($$) { my ($c,$def)=@_; canf($c, sub { return $def }); }
 
-$pi= atan2(0,-1);
 sub signum ($) { return ($_[0] > 0) - ($_[0] < 0); }
 
-%units_len= qw(- mm  mm 1  cm 10  m 1000);
-%units_ang= qw(- d   r 1); $units_ang{'d'}= 2*$pi / 360;
+sub bbox ($) {
+    my ($objhash) = @_;
+    my ($min_x, $max_x, $min_y, $max_y);
+    my ($loc);
+    foreach $loc (values %$objhash) {
+       upd_min(\$min_x, $loc->{X} - abs($allwidthmax * sin($loc->{A})));
+       upd_max(\$max_x, $loc->{X} + abs($allwidthmax * sin($loc->{A})));
+       upd_min(\$min_y, $loc->{Y} - abs($allwidthmax * cos($loc->{A})));
+       upd_max(\$max_y, $loc->{Y} + abs($allwidthmax * cos($loc->{A})));
+    }
+    return ($min_x, $max_x, $min_y, $max_y);
+}
+
+our %units_len= qw(- mm  mm 1  cm 10  m 1000);
+our %units_ang= qw(- d   r 1); $units_ang{'d'}= 2*$pi / 360;
 
 sub cva_len ($) { my ($sp)=@_; cva_units($sp,\%units_len); }
+sub cva_identity ($) { my ($sp)=@_; $sp; }
 sub cva_ang ($) { my ($sp)=@_; cva_units($sp,\%units_ang); }
 sub cva_absang ($) { input_absang(cva_ang($_[0])) }
 sub cva_units ($$) {
@@ -143,29 +436,45 @@ sub cva_idstr ($) {
     return $&;
 }
 sub cva_idex ($) {
-    my ($sp,$id)=@_;
-    my ($r,$d,$k,$neg,$na);
+    my ($sp)=@_;
+    my ($id,$r,$d,$k,$neg,$na,$obj_id,$vflip,$locs);
+    if ($sp =~ s/^(\^?)(\w+)\!//) {
+       $vflip= length($1);
+       $obj_id= $2;
+       die "invalid obj $obj_id in loc" unless exists $objs{$obj_id};
+       $locs= $objs{$obj_id}{Loc};
+    } else {
+       $locs= $ctx->{Loc};
+       $vflip= 0;
+    }
     $neg= $sp =~ s/^\-//;
     $id= cva_idstr($sp);
-    die "unknown $id" unless defined $ctx->{Loc}{$id};
-    $r= $ctx->{Loc}{$id};
+    die "unknown $id" unless defined $locs->{$id};
+    $r= $locs->{$id};
     $d= "idex $id";
     foreach $k (sort keys %$r) { $d .= " $k=$r->{$k}"; }
     printf DEBUG "%s\n", $d;
+    if ($vflip) {
+       $r= { X => $r->{X}, Y => -$r->{Y}, A => -$r->{A} };
+    }
     if ($neg) {
        $na= $r->{A} + $pi;
-       $na -= 2*$pi if $na >= 2*$pi;
+       $na= a_normalise($na,0);
        $r= { X => $r->{X}, Y => $r->{Y}, A => $na };
     }
     return $r;
 }
 sub cva_idnew ($) {
     my ($sp)=@_;
-    my ($id);
+    my ($id, $neg);
+    $neg = $sp =~ s/^\-//;
     $id=cva_idstr($sp);
     die "duplicate $id" if exists $ctx->{Loc}{$id};
-    exists $ctx->{Loc}{$id}{X};
-    push @{ $ctx->{LocsMade} }, $id;
+    $ctx->{Loc}{$id}{LayerKind}= $ctx->{Layer}{Kind};
+    push @{ $ctx->{LocsMade} }, {
+       Id => $id,
+       Neg => $neg,
+    };
     return $ctx->{Loc}{$id};
 }
 sub cva_cmd ($) { return cva_idstr($_[0]); }
@@ -188,9 +497,9 @@ sub cmd_rel {
     my ($from,$to,$len,$right,$turn);
     $from= can(\&cva_idex);
     $to= can(\&cva_idnew);
-    $len= can(\&cva_len);
-    $right= can(\&cva_len);
-    $turn= cano(\&cva_absang, 0);
+    $len= cano(\&cva_len,0);
+    $right= cano(\&cva_len,0) * $ctx->{Trans}{R};
+    $turn= cano(\&cva_ang, 0) * $ctx->{Trans}{R};
     my ($u)= ev_compose({}, $from, { X => $len, Y => -$right, A => 0 });
     ev_compose($to, $u, { X => 0, Y => 0, A => $turn });
 }
@@ -227,6 +536,7 @@ sub dv1_kind ($$$$$$$) {
 sub dv1 ($$$) {
     return 0 unless $debug;
     my ($pfx,$expr,$v) = @_;
+    my ($ref);
     $ref= ref $v;
 #print STDERR "dv1 >$pfx|$ref<\n";
     if (!$ref) {
@@ -255,29 +565,16 @@ sub dv {
     }
 }                  
 
-$ptscale= 72/25.4 / 7.0;
-
-$psu_ulen= 4.5;
-$psu_edgelw= 0.5;
-$psu_ticklw= 0.1;
-$psu_ticksperu= 1;
-$psu_ticklen= 5.0;
-$psu_allwidth= 37.0/2;
-$psu_gauge= 9;
-$psu_sleeperlen= 17;
-$psu_sleeperlw= 15;
-$psu_raillw= 1.0;
-
-$lmu_marklw= 4;
-$lmu_marktpt= 11;
-$lmu_txtboxtxty= $lmu_marktpt * 0.300;
-$lmu_txtboxh= $lmu_marktpt * 1.100;
-$lmu_txtboxpadx= $lmu_marktpt * 0.335;
-$lmu_txtboxoff= $lmu_marklw / 2;
-$lmu_txtboxlw= 1;
-
 sub o ($) { $o .= $_[0]; }
 sub ol ($) { $ol .= $_[0]; }
+sub oflushpage () {
+    return if $subsegcmapreq;
+    print $o, $ol, "  showpage\n"
+       or die $!;
+    $o=$ol='';
+}
+
+our $o_path_verb;
 
 sub o_path_begin () {
     o("      newpath\n");
@@ -291,7 +588,10 @@ sub o_path_point ($) {
 sub o_path_stroke ($) {
     my ($width)=@_;
     o("        $width setlinewidth stroke\n");
-}    
+}
+sub o_path_strokeonly () {
+    o("      stroke\n");
+}
 
 sub o_line ($$$) {
     my ($a,$b,$width)=@_;
@@ -301,11 +601,17 @@ sub o_line ($$$) {
     o_path_stroke($width);
 }
 
+sub current_draw () {
+    my ($r);
+    $r= $ctx->{Draw} =~ m/X/ ? '' : $ctx->{Draw};
+    $r;
+}
+
 sub psu_coords ($$$) {
     my ($ends,$inunit,$across)=@_;
     # $ends->[0]{X} etc.; $inunit 0 to 1 (but go to 1.5);
     # $across in mm, +ve to right.
-    my (%ea_zo);
+    my (%ea_zo, $zo, $prop);
     $ea_zo{X}=$ea_zo{Y}=0;
     foreach $zo (qw(0 1)) {
        $prop= $zo ? $inunit : (1.0 - $inunit);
@@ -317,105 +623,467 @@ sub psu_coords ($$$) {
     return $ea_zo{X}." ".$ea_zo{Y};
 }
 
+sub parametric__o_pt ($) {
+    my ($pt)=@_;
+    o_path_point("$pt->{X} $pt->{Y}");
+}
+
+our $segused_incurrent;
+our $segused_currentpt;
+our $segmentpart_counter=0;
+our $segused_restorecounter;
+
+sub segment_used__print ($) {
+    my ($pt) = @_;
+    if ($segused_incurrent > 0 && $segused_restorecounter==1) {
+       o("%L segmentpart ".
+         $segmentpart_counter++." ".
+         $ctx->{Layer}{Level}.$ctx->{Layer}{Kind}." ".
+         $segments[0]." ".
+         $segused_incurrent." ".
+         loc2dbg($segused_currentpt)." ".
+         loc2dbg($pt)."\n");
+    }
+    $segused_incurrent= undef;
+    $segused_currentpt= undef;
+}
+    
+sub segment_used__len ($$) {
+    my ($used,$pt) = @_;
+    $segused_incurrent++;
+
+    return if @segments < 3;
+    $segments[1] -= $used;
+    return if $segments[1] > 0;
+
+    segment_used__print($pt);
+    segment_used_begin($pt);
+
+    @segments= @segments[2..$#segments];
+    o("% segments @segments\n");
+}
+    
+sub segment_state_save () {
+    return [ 0, $segused_incurrent, $segused_currentpt,
+            $segmentpart_counter, @segments ];
+}
+sub segment_state_restore ($) {
+    my ($r) = @_;
+    ($segused_restorecounter, $segused_incurrent, $segused_currentpt,
+     $segmentpart_counter, @segments) = @$r;
+    $r->[0]++;
+}
+
+sub segment_used_begin ($) {
+    $segused_incurrent= 0;
+    $segused_currentpt= $_[0];
+}
+sub segment_used_middle ($$) {
+    my ($used,$pt) = @_;
+    segment_used__len($used,$pt);
+}
+sub segment_used_end ($$) {
+    my ($used,$pt) = @_;
+    segment_used__len($used,$pt);
+    segment_used__print($pt);
+}
 sub parametric_segment ($$$$$) {
-    my ($endstatuses,$p0,$p1,$lenperp,$calcfn) = @_;
-    # makes $p (global) go from $p0 to $p1  ($p1>$p0)
-    # $ends is II, SI, IS, SS (I=actual lineobj end, S=in mid of lineobj)
+    my ($p0,$p1,$lenperp,$minradius,$calcfn) = @_;
+    # makes $param (global) go from $p0 to $p1  ($p1>$p0)
     # $lenperp is the length of one unit p, ie the curve
     # must have a uniform `density' in parameter space
-    # $calcfn is invoked with $p set and should return a loc
+    # $calcfn is invoked with $param set and should return a loc
     # (ie, ref to X =>, Y =>, A =>).
-    my ($pa,$pb,@ends,$side,$ppu,$e,$v,$tick);
-    return unless $ctx->{Draw}{T} =~ m/1/;
+    my ($pa,$pb,@ends,$side,$ppu,$e,$v,$tick,$draw,$allwidth);
+    return unless $ctx->{Draw} =~ m/[ARSCG]/;
     $ppu= $psu_ulen/$lenperp;
+    $allwidth= allwidth($minradius);
     my ($railctr)=($psu_gauge + $psu_raillw)*0.5;
-    my ($tickend)=($psu_allwidth - $psu_ticklen);
+    my ($tickend)=($allwidth - $psu_ticklen);
     my ($tickpitch)=($psu_ulen / $psu_ticksperu);
     my ($sleeperctr)=($psu_ulen*0.5);
     my ($sleeperend)=($psu_sleeperlen*0.5);
 print DEBUG "ps $p0 $p1 $lenperp ($ppu)\n";
+    $draw= current_draw();
+    if ($draw =~ m/G/) {
+       my ($pt,$going,$red,$csegbare,$movfeat,$movstroke);
+       my ($used_last,$me,$segsave);
+       $segsave= segment_state_save();
+       foreach $me (qw(e m)) {
+           segment_state_restore($segsave);
+           $going=0;
+           o("% segments @segments\n");
+           $param=$p0;
+           $pt= &$calcfn;
+           segment_used_begin($pt);
+           for (;;) {
+               $movstroke= "      cmapreq-stroke\n";
+               $csegbare= $segments[0];
+               $csegbare =~ s/^\-//;
+               if ($subsegcmapreq) {
+                   if (!exists $subsegcmap{$csegbare}) {
+                       print "$csegbare\n" or die $!;
+                       $subsegcmap{$csegbare}++;
+                   }
+               } else {
+                   $movfeat= $csegbare =~ s,(/\D+)(\d+)$,$1, ? $2 : 'f';
+                   die "unknown subsegment colour for $csegbare\n"
+                       unless exists $subsegcmap{$csegbare};
+                   $red= $pt->{A} / (2*$pi);
+                   $red *= $subsegcmapangscale;
+                   $red += $subsegcmapangscale*2;
+                   $red += $subsegcmapangscale/2 if $segments[0] =~ m/^\-/;
+                   $red %= $subsegcmapangscale;
+                   $red += $subsegcmapangscale if $me eq 'e';
+                   $red= sprintf("%f", $red / 255.0);
+                   $movstroke=
+                       ("    $red $subsegcmap{$csegbare} setrgbcolor\n".
+                        "    $psu_subseglw{$me} setlinewidth stroke\n");
+                   if ($subsegmovfeatpos ne $movfeat ||
+                       ($me eq 'e' && $csegbare =~ m,^/,)) {
+                       $movstroke= "%     no-stroke\n";
+                   }
+               }
+               o_path_begin();
+               parametric__o_pt($pt);
+       
+               $param += $ppu;
+               last if $param>=$p1;
+               $pt= &$calcfn;
+               segment_used_middle($psu_ulen,$pt);
+               parametric__o_pt($pt);
+               o($movstroke);
+           }
+           $used_last= $p1-($param-$ppu);
+           $param=$p1;
+           $pt= &$calcfn;
+           segment_used_end($used_last * $lenperp, $pt);
+           parametric__o_pt($pt);
+           o($movstroke);
+       }
+    }
+    if ($draw =~ m/C/) {
+       my ($pt);
+       o("    $psu_thinlw setlinewidth\n");
+       o_path_begin();
+       for ($param=$p0; $param<$p1; $param += $ppu) {
+           parametric__o_pt(&$calcfn);
+       }
+       $param=$p1;
+       parametric__o_pt(&$calcfn);
+       o("      stroke\n");
+    }
+    if ($draw =~ m/D/) {
+       my ($pt,$ad,$len);
+       $param= ($p0+$p1)*0.5;
+       $pt= &$calcfn;
+       $ad= ang2deg($pt->{A});
+       $len= sprintf "%.0f", $lenperp * abs($p1-$p0);
+       ol("      gsave\n".
+          "        $pt->{X} $pt->{Y} translate\n".
+          "        $ad rotate\n".
+          "        lf setfont  0 $lmu_lenboxoff moveto  ($len) show\n".
+          "      grestore\n");
+    }
+    return unless $draw =~ m/[ARS]/;
     for ($pa= $p0; $pa<$p1; $pa=$pb) {
        $pb= $pa + $ppu;
-       $p= $pa; $ends[0]= @ends ? $ends[1] : &$calcfn;
-       $p= $pb; $ends[1]= &$calcfn;
+       $param= $pa; $ends[0]= @ends ? $ends[1] : &$calcfn;
+       $param= $pb; $ends[1]= &$calcfn;
 #print DEBUG "pa $pa $ends[0]{X} $ends[0]{Y} $ends[0]{A}\n";
 #print DEBUG "pb $pb $ends[1]{X} $ends[1]{Y} $ends[1]{A}\n";
        $e= $pb<=$p1 ? 1.0 : ($p1-$pa)/$ppu;
        o("    gsave\n");
        o_path_begin();
-       o_path_point(psu_coords(\@ends,0,-$psu_allwidth));
-       o_path_point(psu_coords(\@ends,0,$psu_allwidth));
-       o_path_point(psu_coords(\@ends,$e,$psu_allwidth));
-       o_path_point(psu_coords(\@ends,$e,-$psu_allwidth));
+       o_path_point(psu_coords(\@ends,0,-$allwidth));
+       o_path_point(psu_coords(\@ends,0,$allwidth));
+       o_path_point(psu_coords(\@ends,$e,$allwidth));
+       o_path_point(psu_coords(\@ends,$e,-$allwidth));
        o("        closepath clip\n");
        foreach $side qw(-1 1) {
-           o_line(psu_coords(\@ends,0,$side*$psu_allwidth),
-                  psu_coords(\@ends,1.5,$side*$psu_allwidth),
-                  $psu_edgelw);
-           o_line(psu_coords(\@ends,0,$side*$railctr),
-                  psu_coords(\@ends,1.5,$side*$railctr),
-                  $psu_raillw);
-           for ($tick=0; $tick<1.5; $tick+=$tickpitch/$psu_ulen) {
-               o_line(psu_coords(\@ends,$tick,$side*$psu_allwidth),
-                      psu_coords(\@ends,$tick,$side*$tickend),
-                      $psu_ticklw);
+           if ($draw =~ m/R/) {
+               o_line(psu_coords(\@ends,0,$side*$railctr),
+                      psu_coords(\@ends,1.5,$side*$railctr),
+                      $psu_raillw);
+           }
+       }
+       if ($draw =~ m/S/) {
+           o_line(psu_coords(\@ends,$sleeperctr,-$sleeperend),
+                  psu_coords(\@ends,$sleeperctr,+$sleeperend),
+                  $psu_sleeperlw);
+       }
+       if ($draw =~ m/A/) {
+           o("        0.5 setgray\n");
+           foreach $side qw(-1 1) {
+               o_line(psu_coords(\@ends,0,$side*$allwidth),
+                      psu_coords(\@ends,1.5,$side*$allwidth),
+                      $psu_edgelw);
+               for ($tick=0; $tick<1.5; $tick+=$tickpitch/$psu_ulen) {
+                   o_line(psu_coords(\@ends,$tick,$side*$allwidth),
+                          psu_coords(\@ends,$tick,$side*$tickend),
+                          $psu_ticklw);
+               }
            }
        }
-       o_line(psu_coords(\@ends,$sleeperctr,-$sleeperend),
-              psu_coords(\@ends,$sleeperctr,+$sleeperend),
-              $psu_sleeperlw);
        o("      grestore\n");
     }
 }
 
-sub arc ($$$$$$$) {
-    my ($to, $endstatuses, $ctr,$from,$fromsense, $radius,$delta) = @_;
+sub arc ($$$$$) {
+    my ($to, $ctr,$from, $radius,$delta) = @_;
     # does parametric_segment to draw an arc centred on $ctr
+    # ($ctr->{A} ignored)
     # from $from with radius $radius (this must be consistent!)
-    # and subtending an angle $delta.
+    # and directionally-subtending an angle $delta.
     # sets $to->... to be the other end, and returns $to
-    # $fromsense is 1 or -1, and affects only the interpretation
-    # of $from->{A} (not the result).
-    my ($beta, $fromadj);
-    $fromadj= (1.0 - $fromsense) * $pi;
-    $to->{A}= $beta= $from->{A} + $fromadj + $delta;
+    my ($beta);
+    $to->{A}= $beta= $from->{A} + $delta;
     $to->{X}= $ctr->{X} - $radius * sin($beta);
     $to->{Y}= $ctr->{Y} + $radius * cos($beta);
-    parametric_segment($endstatuses, 0.0,1.0, abs($radius*$delta), sub {
-       my ($beta) = $from->{A} + $delta * $p;
+    return if abs($delta*$radius) < 1e-9;
+    parametric_segment(0.0,1.0, abs($radius*$delta), $radius, sub {
+       my ($beta) = $from->{A} + $delta * $param;
        return { X => $ctr->{X} - $radius * sin($beta),
                 Y => $ctr->{Y} + $radius * cos($beta),
                 A => $beta }
     });
 }
 
+# joins_xxx all take $results, $from, $to, $minradius
+# where $results->[]{Path}{K} etc. and $results->[]{SolKinds}[]
+
+sub joins_twoarcs ($$$$) {
+    my ($results, $from,$to,$minradius) = @_;
+    # two circular arcs of equal maximum possible radius
+    # algorithm courtesy of Simon Tatham (`Railway problem',
+    # pers.comm. to ijackson@chiark 23.1.2004)
+    my ($sigma,$distfact, $theta,$phi, $a,$b,$c,$d, $m,$r, $radius);
+    my ($cvec,$cfrom,$cto,$midpt, $delta1,$delta2, $path,$reverse);
+    $sigma= ev_bearing($from,$to);
+    $distfact= v_dist($from,$to);
+    $theta= 0.5 * $pi - ($from->{A} - $sigma);
+    $phi=   0.5 * $pi - ($to->{A} + $pi - $sigma);
+    $a= 2 * (1 + cos($theta - $phi));
+    $b= 2 * (cos($theta) - cos($phi));
+    $c= -1;
+    $d= sqrt($b*$b - 4*$a*$c);
+    o("%     twoarcs theta=".ang2deg($theta)." phi=".ang2deg($phi).
+      " ${a}r^2 + ${b}r + ${c} = 0\n");
+    foreach $m (qw(-1 1)) {
+       if ($a < 1e-6) {
+           o("%     twoarcs $m insoluble\n");
+           next;
+       }
+       $r= -0.5 * (-$b + $m*$d) / $a;
+       $radius= -$r * $distfact;
+       o("%     twoarcs $m radius $radius ");
+       if (abs($radius) < $minradius) { o("too-small\n"); next; }
+       $cfrom=  ev_compose({}, $from, { X=>0, Y=>-$radius, A=>-0.5*$pi });
+       $cto=    ev_compose({}, $to,   { X=>0, Y=> $radius, A=> 0.5*$pi });
+       $midpt=  ev_lincomb({}, $cfrom, $cto, 0.5);
+       $reverse= signum($r);
+       if ($reverse<0) {
+           $cfrom->{A} += $pi;
+           $cto->{A} += $pi;
+       }
+       $delta1= ev_bearing($cfrom, $midpt) - $cfrom->{A};
+       $delta2= ev_bearing($cto,   $midpt) - $cto->{A};
+       o("ok deltas ".ang2deg($delta1)." ".ang2deg($delta2)."\n");
+       if ($reverse<0) {
+           $delta1 -= 2*$pi;
+           $delta2 -= 2*$pi;
+       }
+       my ($fs);
+       $path= [{ T=>Arc, F=>$from, C=>$cfrom, R=> $radius, D=>$delta1 },
+               { T=>Arc, F=>$to,   C=>$cto,   R=>-$radius, D=>$delta2 }];
+       push @$results, { Path => $path,
+                         SolKinds =>  [ 'twoarcs', 'cross' ] };
+    }
+}
+    
+sub joins_arcsline ($$$$) {
+    my ($results, $from,$to,$minradius) = @_;
+    # two circular arcs of specified radius
+    # with an intervening straight
+    my ($lr,$inv, $c,$d,$alpha,$t,$k,$l,$rpmsina,$rcosa,$linelen, $path);
+    if ($minradius<=1e-6) { o("%     arcsline no-radius\n"); return; }
+    foreach $lr (qw(-1 +1)) {
+       foreach $inv (qw(-1 +1)) {
+           $c=ev_compose({},$from,{X=>0,Y=>-$lr*$minradius, A=>0 });
+           $d=ev_compose({},$to,{X=>0, Y=>-$inv*$lr*$minradius, A=>$pi });
+           $t= v_dist($c,$d);
+           o("%     arcsline $lr $inv t=$t ");
+           if ($t < 1e-6) { o("concentric"); next; }
+           $c->{A}= $d->{A}= ev_bearing($c,$d);
+           o("bearing ".ang2deg($c->{A}));
+           if ($inv>0) {
+               o("\n");
+               $k= ev_compose({}, $c, { X=>0, Y=>$lr*$minradius, A=>0 });
+               $l= ev_compose({}, $d, { X=>0, Y=>$lr*$minradius, A=>0 });
+               $linelen= $t;
+           } else {
+               my ($cosalpha) = 2.0 * $minradius / $t;
+               if ($cosalpha > (1.0 - 1e-6)) { o(" too-close\n"); next; }
+               $alpha= acos($cosalpha);
+               $rpmsina= $lr * $minradius * sin($alpha);
+               $rcosa= $minradius * $cosalpha;
+               $k= ev_compose({}, $c, { X=>$rcosa, Y=>$rpmsina, A=>0 });
+               $l= ev_compose({}, $d, { X=>-$rcosa, Y=>-$rpmsina, A=>0 });
+               $k->{A}= $l->{A}= ev_bearing($k,$l);
+               o(" alpha=".ang2deg($alpha)." kl^=".ang2deg($k->{A})."\n");
+               $linelen= v_dist($k,$l);
+           }
+           $path= [{ T => Arc, F => $from, C => $c,
+                     R =>$lr*$minradius,
+                     D => -$lr * a_normalise
+                         ($lr * ($from->{A} - $k->{A}), 0) },
+                   { T => Line, A => $k, B => $l, L => $linelen },
+                   { T => Arc, F => $l, C => $d,
+                     R => $inv*$lr*$minradius,
+                     D => -$lr*$inv * a_normalise
+                         (-$lr*$inv * ($to->{A} - $l->{A}), 0) }];
+           push @$results,
+           { Path => $path,
+             SolKinds => [ 'arcsline', ($inv<0 ? 'cross' : 'loop') ] };
+       }
+    }
+}
+
+sub joins_arcline ($$$$) {
+    my ($results, $from,$to,$minradius) = @_;
+    # one circular arc and a straight line
+    my ($swap,$echoice,$path, $ap,$bp,$av,$bv, $e,$f, $ae,$af,$afae);
+    my ($dak,$ak,$kj,$k,$j,$aja,$jl,$l,$jc,$lc,$c,$rj,$rb);
+    foreach $swap (qw(-1 +1)) {
+       foreach $echoice (qw(0 1)) {
+           $ap= $from; $bp= { %$to }; $bp->{A} += $pi;
+           ($ap,$bp)= ($bp,$ap) if $swap<0;
+           $av= ev_byang({}, $ap->{A});
+           $bv= ev_byang({}, $bp->{A});
+           $e= ev_byang({}, 0.5 * ($ap->{A} + $bp->{A} + $echoice * $pi));
+           $f= v_rotateright($e);
+           o("%     arcline $swap $echoice e ".loc2dbg($e)."\n");
+           $ae= v_dotproduct($av,$e);
+           $af= v_dotproduct($av,$f);
+           o("%     arcline $swap $echoice a.e=$ae a.f=$af ");
+           if (abs($ae) < 1e-6) { o(" singular\n"); next; }
+           $afae= $af/$ae;
+           o("a.f/a.e=$afae\n");
+           $dak= v_dotproduct(v_subtract($ap,$bp), $e);
+           $ak= v_scalarmult($dak, $e);
+           $kj= v_scalarmult($dak * $afae, $f);
+           $k= v_add($ap, $ak);
+           $j= v_add($k, $kj);
+           $aja= v_dotproduct(v_subtract($ap,$j), $av);
+           o("%     arcline $swap $echoice d_ak=$dak aj.a=$aja ");
+           if ($aja < 0) { o(" backwards aj\n"); next; }
+           $jl= v_scalarmult(0.5, v_subtract($j, $bp));
+           $lc= v_scalarmult(-v_dotproduct($jl, $f) * $afae, $e);
+           $l= v_add($j, $jl);
+           $c= v_add($l, $lc);
+           $rj= v_dotproduct(v_subtract($j,$c), v_rotateright($av));
+           $rb= v_dotproduct(v_subtract($c,$bp), v_rotateright($bv));
+           o("r_j=$rj r_b=$rb ");
+           if ($rj * $rb < 0) { o(" backwards b\n"); next; }
+           if (abs($rj) < $minradius) { o(" too-small\n"); next; }
+           o("ok\n");
+           $j->{A}= $ap->{A};
+           $c->{A}= 0;
+           $path= [{ T => Line, A => $ap, B => $j, L => $aja },
+                   { T => Arc, F => $j, C => $c, R => $rj,
+                     D => -signum($rj) * a_normalise
+                         (-signum($rj) * ($bp->{A} + $pi - $j->{A}), 0) }];
+           $path= [ reverse @$path ] if $swap<0;
+           push @$results, { Path => $path, SolKinds =>  [ 'arcline' ] };
+       }
+    }
+}
+
 sub cmd_join {
-    my ($from,$to,$how);
+    my ($from,$to,$minradius);
+    my (@results,$result);
+    my ($path,$segment,$bestpath,$len,$scores,$bestscores,@bends,$skl);
+    my ($crit,$cs,$i,$cmp);
     $from= can(\&cva_idex);
     $to= can(\&cva_idex);
-    $how= can(cvam_enum(qw(arcs arcsm)));
-    my ($sigma,$distfact, $theta,$phi, $a,$b,$c,$d2, $r,$cfrom);
-    $sigma= v_bearing($from,$to);
-    $distfact= v_dist($from,$to);
-    $theta= $from - $sigma;
-    $phi= $to + 2 * $pi - $sigma;
-    $a= 2 * (1 + sin($theta - $phi));
-    $b= 2 * (sin($theta) - sin($phi));
-    $c= -1;
-    die "too close" if $a<1.1e-10;
-    $d2= $b*$b - 4*$a*$c;
-    $pm = $how =~ /m$/ ? -1 : +1;
-    $r= (-$b + $pm*sqrt($d2))/$a;
-    $rf= $r*$distfact;
-    $cfrom= ev_compose({}, $from, { Y =>  $rf, X => 0, A => 0 });
-    $cto=   ev_compose({}, $to,   { Y => -$rf, X => 0, A => 0 });
-    $cbearing= v_bearing($cfrom,$cto);
-    arc({}, IS, $cfrom,$from,1.0,  $rf, $cbearing - $cfrom->{A});
-    arc({}, IS, $cto,  $to, -1.0, -$rf, $cbearing - $cfrom->{A} + 2*$pi);
+    $minradius= can(\&cva_len);
+    o("%   join ".loc2dbg($from)."..".loc2dbg($to)." $minradius\n");
+    joins_twoarcs(\@results, $from,$to,$minradius);
+    joins_arcsline(\@results, $from,$to,$minradius);
+    joins_arcline(\@results, $from,$to,$minradius);
+    foreach $result (@results) {
+       $path= $result->{Path};
+       $skl= $result->{SolKinds};
+       o("%   possible path @$skl $path\n");
+       $len= 0;
+       @bends= ();
+       foreach $segment (@$path) {
+           if ($segment->{T} eq Arc) {
+               o("%     Arc C ".loc2dbg($segment->{C}).
+                 " R $segment->{R} D ".ang2deg($segment->{D})."\n");
+               $len += abs($segment->{R} * $segment->{D});
+               push @bends, -abs($segment->{R}) * $segment->{D}; # right +ve
+           } elsif ($segment->{T} eq Line) {
+               o("%     Line A ".loc2dbg($segment->{A}).
+                 " B ".loc2dbg($segment->{A})." L $segment->{L}\n");
+               $len += abs($segment->{L});
+           } else {
+               die "unknown segment $segment->{T}";
+           }
+       }
+       o("%    length $len bends @bends.\n");
+       $scores= [];
+       foreach $crit (@al, 'short') {
+           if ($crit eq 'long') { $cs= $len; }
+           elsif ($crit eq 'short') { $cs= -$len; }
+           elsif ($crit =~ m/^(begin|end|)(left|right)$/) {
+               if ($1 eq 'begin') { $cs= $bends[0]; }
+               elsif ($1 eq 'end') { $cs= $bends[$#bends]; }
+               else { $cs=0; map { $cs += $_ } @bends; }
+               $cs= -$cs if $2 eq 'left';
+           } elsif ($crit =~ m/^(\!?)(twoarcs|arcs?line|cross|loop)$/) {
+               $cs= !!(grep { $2 eq $_ } @$skl) != ($1 eq '!');
+           } else {
+               die "unknown sort criterion $crit";
+           }
+           push @$scores, $cs;
+       }
+       o("%    scores @$scores\n");
+       if (defined $bestpath) {
+           for ($i=0,$cmp=0; !$cmp && $i<@$scores; $i++) {
+               $cmp= $scores->[$i] <=> $bestscores->[$i];
+           }
+           next if $cmp < 0;
+       }
+       $bestpath= $path;
+       $bestscores= $scores;
+    }
+    die "no solution" unless defined $bestpath;
+    o("%   chose path $bestpath @al\n");
+    @al= ();
+    foreach $segment (@$bestpath) {
+       if ($segment->{T} eq 'Arc') {
+           arc({}, $segment->{C},$segment->{F},$segment->{R},$segment->{D});
+       } elsif ($segment->{T} eq 'Line') {
+           line($segment->{A}, $segment->{B}, $segment->{L});
+       } else {
+           die "unknown segment";
+       }
+    }
+}
+
+sub line ($$$) {
+    my ($from,$to,$len) = @_;
+    if ($len < 0) {
+       ($from,$to,$len) = ($to,$from,-$len);
+    }
+    parametric_segment(0.0, 1.0, $len + 1e-6, undef, sub {
+       ev_lincomb({}, $from, $to, $param);
+    });
 }
 
 sub cmd_extend {
-    my ($from,$to,$radius,$ctr,$beta,$ang,$how,$sign_r);
+    my ($from,$to,$radius,$len,$upto,$ctr,$beta,$ang,$how,$sign_r);
     $from= can(\&cva_idex);
     $to= can(\&cva_idnew);
     printf DEBUG "from $from->{X} $from->{Y} $from->{A}\n";
@@ -425,20 +1093,21 @@ sub cmd_extend {
     elsif ($how eq 'parallel' || $how eq 'upto') { $upto= can(\&cva_idex); }
     $radius= cano(\&cva_len, 'Inf'); # +ve is right hand bend
     if ($radius eq 'Inf') {
-       print DEBUG "extend inf $len\n";
-       if ($how eq 'ang') { die "len of straight spec by angle"; }
+#      print DEBUG "extend inf $len\n";
        if ($how eq 'upto') {
            $len= ($upto->{X} - $from->{X}) * cos($from->{A})
                + ($upto->{Y} - $from->{Y}) * sin($from->{A});
+       } elsif ($how eq 'len') {
+       } else {
+           die "len of straight spec by angle";
        }
        printf DEBUG "len $len\n";
        $to->{X}= $from->{X} + $len * cos($from->{A});
        $to->{Y}= $from->{Y} + $len * sin($from->{A});
        $to->{A}= $from->{A};
-       parametric_segment(II, 0.0, 1.0, abs($len), sub {
-           ev_lincomb({}, $from, $to, $p);
-       });
+       line($from,$to,$len);
     } else {
+       my ($sign_r, $sign_ang, $ctr, $beta_interval, $beta, $delta);
        print DEBUG "radius >$radius<\n";
        $radius *= $ctx->{Trans}{R};
        $sign_r= signum($radius);
@@ -470,13 +1139,17 @@ sub cmd_extend {
            $delta= $beta - $from->{A};
            last if $sign_ang * $sign_r * $delta <= 0;
            $beta -= $sign_ang * $sign_r * $beta_interval * $pi;
-       }       
+       }
     printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n";
-       arc($to, II, ,$ctr,$from,1.0, $radius,$delta);
+       arc($to, ,$ctr,$from, $radius,$delta);
     }
     printf DEBUG "to $to->{X} $to->{Y} $to->{A}\n";
 }
 
+sub loc2dbg ($) {
+    my ($loc) = @_;
+    return "$loc->{X} $loc->{Y} ".ang2deg($loc->{A});
+}
 sub ang2deg ($) {
     return $_[0] * 180 / $pi;
 }
@@ -491,73 +1164,261 @@ sub input_abscoords ($$) {
     return ($out->{X}, $out->{Y});
 }
 
-sub newctx () {
+sub newctx (;$) {
+    my ($ctx_save) = @_;
     $ctx= {
        Trans => { X => 0.0, Y => 0.0, A => 0.0, R => 1.0 },
        InRunObj => "",
-       Draw => { T => 1, L => L1 }
+       DrawMap => sub { $_[0]; },
+       SegMapN => { },
+       SegMapNM => { }
        };
+    if (defined $ctx_save) {
+       %{ $ctx->{Layer} }= %{ $ctx_save->{Layer} };
+       $ctx->{Parent}= $ctx_save;
+    }
 }
 
-sub cmd_defobj {
+our $defobj_save;
+our $defobj_ispart;
+
+sub cmd_defobj { cmd__defobj(0); }
+sub cmd_defpart { cmd__defobj(1); }
+sub cmd__defobj ($) {
+    my ($ispart) = @_;
     my ($id);
     $id= can(\&cva_idstr);
     die "nested defobj" if $defobj_save;
     die "repeated defobj" if exists $objs{$id};
     $defobj_save= $ctx;
-    newctx();
+    $defobj_ispart= $ispart;
+    newctx($defobj_save);
     $ctx->{CmdLog}= [ ];
     $ctx->{InDefObj}= $id;
-    $ctx->{Draw}= { T => '', L => '' }
+    $ctx->{Draw}= $defobj_save->{Draw}.'X';
+    $ctx->{DrawMap}= sub { ''; };
+    $ctx->{Layer}= { Level => 5, Kind => '' };
 }
 
-sub cmd_enddefobj {
+sub cmd_enddef {
     my ($bit,$id);
     $id= $ctx->{InDefObj};
-    die "unmatched enddefobj" unless defined $id;
+    die "unmatched enddef" unless defined $id;
     foreach $bit (qw(CmdLog Loc)) {
        $objs{$id}{$bit}= $ctx->{$bit};
     }
+    $objs{$id}{Part}= $defobj_ispart;
     $ctx= $defobj_save;
     $defobj_save= undef;
+    $defobj_ispart= undef;
+}
+
+sub cmd__runobj ($) {
+    my ($obj_id)=@_;
+    my ($c);
+    local (@al);
+    dv("cmd__runobj $obj_id ",'$ctx',$ctx);
+    foreach $c (@{ $objs{$obj_id}{CmdLog} }) {
+       @al= @$c;
+       next if $al[0] eq 'enddef';
+       cmd__one();
+    }
+}
+
+sub cva_subsegspec ($) {
+    my ($sp)=@_;
+    die "invalid subsegment spec" unless
+       $sp =~ m,^(\-?)([0-9A-Za-z_]*)(?:/(?:([A-Za-z_]+)(\d+))?)?$,;
+    my ($sign,$segname,$movfeat,$movconf)=($1,$2,$3,$4);
+
+    if (!exists $ctx->{SegName}) {
+       $segname= '';
+       $sign= '';
+    } else {
+       my ($map_ctx);
+       
+       $ctx->{SegName} =~ m/^\-?/ or die;
+       $sign .= $&;
+       $segname= $'.$segname;
+       
+       for ($map_ctx= $ctx;
+            defined $map_ctx;
+            $map_ctx= $map_ctx->{Parent}) {
+           if (defined $movfeat &&
+               exists $map_ctx->{SegMapNM}{"$segname/$movfeat"}) {
+               $movfeat= $map_ctx->{SegMapNM}{"$segname/$movfeat"};
+           }
+           if (exists $map_ctx->{SegMapN}{$segname}) {
+               $map_ctx->{SegMapN}{$segname} =~ m/^\-?/ or die;
+               $sign .= $&;
+               $segname= $';
+           }
+       }
+       $sign =~ s/\-\-//g;
+    }
+
+    return $sign.$segname.'/'.
+       (defined $movfeat ? sprintf "%s%d", $movfeat, $movconf : '');
+}
+
+sub cmd_segment {
+    my ($csss,$length);
+    $ctx->{SavedSegment}= pop @segments
+       unless exists $ctx->{SavedSegment};
+    @segments= ();
+    while (@al>1) {
+       $csss= can(\&cva_subsegspec);
+       $length= can(\&cva_len);
+       push @segments, $csss, $length;
+    }
+    $csss= can(\&cva_subsegspec);
+    push @segments, $csss;
+}
+
+sub cva_segmap_s {
+    my ($sp) = @_;
+    $sp =~ m,^\w+(?:/[a-zA-Z_]+)?$,
+        or die "invalid (sub)segment mapping S \`$sp'";
+    return $sp;
+}
+
+sub cva_segmap_n {
+    my ($sp) = @_;
+    $sp =~ m,^\-?\w+$, or die "invalid segment mapping N' \`$sp'";
+    return $sp;
+}
+    
+sub cva_segmap_m {
+    my ($sp) = @_;
+    $sp =~ m,^[a-zA-Z_]+$, or die "invalid segment mapping M' \`$sp'";
+    return $sp;
+}
+    
+sub cmd_segmap {
+    my ($s,$d);
+    while (@al) {
+       $s= can(\&cva_segmap_s);
+       if ($s =~ m,/,) {
+           $ctx->{SegMapNM}{$s}= can(\&cva_segmap_m);
+       } else {
+           $ctx->{SegMapN}{$s}= can(\&cva_segmap_n);
+       }
+    }
+}
+
+sub layer_draw ($$) {
+    my ($k,$l) = @_;
+    my ($eo,$cc, $r);
+    if ($k eq '') {
+       $r= 'RLMN';
+    } elsif ($k eq 's') {
+       $r= '';
+    } elsif ($k eq 'l') {
+       $r= 'CLMN';
+    } else {
+       $r= 'ARSCLMNO';
+    }
+    foreach $eo (@eopts) {
+#print STDERR "$. layer $k$l eo $eo re $eo->{GlobRe} then $eo->{DrawMods} now $r\n";
+       next unless $k =~ m/^$eo->{GlobRe}$/;
+#print STDERR "$. layer $k$l eo re $eo->{GlobRe} match\n";
+       next unless &{ $eo->{LayerCheck} }($l);
+#print STDERR "$. layer $k$l eo re $eo->{GlobRe} checked\n";
+       foreach $cc (split //, $eo->{DrawMods}) {
+           $r =~ s/$cc//ig;
+           $r .= $cc if $cc =~ m/[A-Z]/;
+       }
+    }
+#print STDERR "layer $k$l gives $r (before map)\n";
+    $r= &{ $ctx->{DrawMap} }($r);
+    return $r;
 }
 
+sub cmd_layer {
+    my ($kl, $k,$l);
+    $kl= can(\&cva_identity);
+    $kl =~ m/^([A-Za-z_]*)(\d*|\=|\*)$/ or die "invalid layer spec";
+    ($k,$l)=($1,$2);
+    $l= $output_layer if $l eq '*';
+    $l= $ctx->{Layer}{Level} if $l =~ m/^\=?$/;
+    $ctx->{Layer}{Kind}= $k;
+    $ctx->{Layer}{Level}= $l;
+    $ctx->{Draw}= layer_draw($k,$l);
+}    
+
+sub cmd_part { cmd__obj(Part); }
 sub cmd_obj { cmd__obj(1); }
 sub cmd_objflip { cmd__obj(-1); }
+
 sub cmd__obj ($) {
-    my ($flipsignum)=@_;
+    my ($how)=@_;
     my ($obj_id, $ctx_save, $pfx, $actual, $formal_id, $formal, $formcv);
-    my ($c, $ctx_inobj);
+    my ($part_name, $ctx_inobj, $obj, $id, $newid, $newpt);
+    if ($how eq Part) {
+       $part_name= can(\&cva_idstr);
+       $how= (@al && $al[0] =~ s/^\^//) ? -1 : +1;
+    }
     $obj_id= can(\&cva_idstr);
-    $actual= can(\&cva_idex);
-    $formal_id= can(\&cva_idstr);
+    if (defined $part_name) {
+       $formal_id= can(\&cva_idstr);
+       $actual= cano(\&cva_idex, undef);
+       if (!defined $actual) {
+           $actual= cva_idex("${part_name}_${formal_id}");
+       }
+    } else {
+       $actual= can(\&cva_idex);
+       $formal_id= can(\&cva_idstr);
+    }
     $obj= $objs{$obj_id};
     dv("cmd__obj ",'$obj',$obj);
     die "unknown obj $obj_id" unless $obj;
     $formal= $obj->{Loc}{$formal_id};
     die "unknown formal $formal_id" unless $formal;
     $ctx_save= $ctx;
-    newctx();
-    $ctx->{Trans}{R}= $flipsignum;
-    $ctx->{Trans}{A}= $actual->{A} - $formal->{A}/$flipsignum;
+    newctx($ctx_save);
+    $how *= $ctx_save->{Trans}{R};
+    $ctx->{Trans}{R}= $how;
+    $ctx->{Trans}{A}= $actual->{A} - $formal->{A}/$how;
     $formcv= ev_compose({}, $ctx->{Trans},$formal);
     $ctx->{Trans}{X}= $actual->{X} - $formcv->{X};
     $ctx->{Trans}{Y}= $actual->{Y} - $formcv->{Y};
-    $ctx->{InRunObj}= $ctx_save->{InRunObj}."${obj_id}::";
-    $ctx->{Draw}{L} =~ s/L//;
-dv("cmd__obj $obj_id ",'$ctx',$ctx);
-    {
-       local (@al);
-       foreach $c (@{ $obj->{CmdLog} }) {
-           @al= @$c;
-           next if $al[0] eq 'enddefobj';
-           cmd__one();
+    if (defined $part_name) {
+       $ctx->{InRunObj}= $ctx_save->{InRunObj}."${part_name}:";
+    } else {
+       $ctx->{InRunObj}= $ctx_save->{InRunObj}."${obj_id}::";
+    }
+    if ($segments[0] =~ m,(.*[^-]+)/,) {
+       $ctx->{SegName}= $1;
+    }
+    $ctx->{DrawMap}= sub {
+       my ($i) = @_;
+       $i= &{ $ctx_save->{DrawMap} }($i);
+       if ($obj->{Part}) {
+           $i =~ s/[LMN]//g;
+           $i =~ s/O/MNO/;
+       } else {
+           $i =~ s/[LM]//g;
+           $i =~ s/N/MN/;
        }
+       return $i;
     };
-    $pfx= cano(\&cva_idstr,'');
+    $ctx->{Draw}= &{ $ctx->{DrawMap} }($ctx_save->{Draw});
+    cmd__runobj($obj_id);
+    if (defined $part_name) {
+       $pfx= $part_name.'_';
+    } else {
+       if (@al && $al[0] eq '=') {
+           $pfx= ''; shift @al;
+       } else {
+           $pfx= cano(\&cva_idstr,undef);
+       }
+    }
+    if (exists $ctx->{SavedSegment}) {
+       @segments= ($ctx->{SavedSegment});
+    }
     $ctx_inobj= $ctx;
     $ctx= $ctx_save;
-    if (length $pfx) {
+    if (defined $pfx) {
        foreach $id (keys %{ $ctx_inobj->{Loc} }) {
            next if $id eq $formal_id;
            $newid= $pfx.$id;
@@ -566,71 +1427,230 @@ dv("cmd__obj $obj_id ",'$ctx',$ctx);
            %$newpt= %{ $ctx_inobj->{Loc}{$id} };
        }
     }
+    if (defined $part_name) {
+       my ($formalr_id, $actualr_id, $formalr, $actualr);
+       while (@al) {
+           die "part results come in pairs\n" unless @al>=2;
+           ($formalr_id, $actualr_id, @al) = @al;
+           if ($actualr_id =~ s/^\-//) {
+               $formalr_id= "-$formalr_id";
+               $formalr_id =~ s/^\-\-//;
+           }
+           {
+               local ($ctx) = $ctx_inobj;
+               $formalr= cva_idex($formalr_id);
+           }
+           $actualr= cva_idnew($actualr_id);
+           %$actualr= %$formalr;
+       }
+    }
 }
 
 sub cmd__do {
     my ($cmd);
 dv("cmd__do $ctx @al ",'$ctx',$ctx);
     $cmd= can(\&cva_cmd);
-    my ($id,$loc,$io,$ad);
+    my ($lm,$id,$loc,$io,$ad,$draw,$thendrawre);
     $io= defined $ctx->{InDefObj} ? "$ctx->{InDefObj}!" : $ctx->{InRunObj};
     o("%L cmd   $io $cmd @al\n");
     $ctx->{LocsMade}= [ ];
-    &{ "cmd_$cmd" };
+    {
+       no strict 'refs';
+       &{ "cmd_$cmd" };
+    };
     die "too many args" if @al;
-    foreach $id (@{ $ctx->{LocsMade} }) {
+    foreach $lm (@{ $ctx->{LocsMade} }) {
+       $id= $lm->{Id};
        $loc= $ctx->{Loc}{$id};
+       $loc->{A} += $pi if $lm->{Neg};
        $ad= ang2deg($loc->{A});
-       ol("%L point $io$id $loc->{X} $loc->{Y} $ad\n");
-       if (length $ctx->{Draw}{L}) {
+       ol("%L point $io$id ".loc2dbg($loc)." ($lm->{Neg})\n");
+       $draw= layer_draw($loc->{LayerKind}, $ctx->{Layer}{Level});
+       if ($draw =~ m/[LM]/) {
            ol("    gsave\n".
               "      $loc->{X} $loc->{Y} translate $ad rotate\n");
-           if ($ctx->{Draw}{L} =~ m/1/) {
-               ol("      0 $psu_allwidth newpath moveto\n".
-                  "      0 -$psu_allwidth lineto\n".
+           if ($draw =~ m/M/) {
+               ol("      0 $allwidthmin newpath moveto\n".
+                  "      0 -$allwidthmin lineto\n".
                   "      $lmu_marklw setlinewidth stroke\n");
            }
-           if ($ctx->{Draw}{L} =~ m/L/) {
-               ol("      /s ($id) def\n".
-                  "      lf setfont\n".
-                  "      /sx5  s stringwidth pop\n".
-                  "      0.5 mul $lmu_txtboxpadx add def\n".
-                  "      -90 rotate  0 $lmu_txtboxoff translate  newpath\n".
-                  "      sx5 neg  0             moveto\n".
-                  "      sx5 neg  $lmu_txtboxh  lineto\n".
-                  "      sx5      $lmu_txtboxh  lineto\n".
-                  "      sx5      0             lineto closepath\n".
-                  "      gsave  1 setgray fill  grestore\n".
-                  "      $lmu_txtboxlw setlinewidth stroke\n".
-                  "      sx5 neg $lmu_txtboxpadx add  $lmu_txtboxtxty\n".
-                  "      moveto s show\n");
+           if ($draw =~ m/L/) {
+               ol("      $lmu_txtboxlw $lmu_txtboxh $lmu_txtboxpadx".
+                  " $lmu_txtboxoff ($id) label_in_box\n");
            }
            ol("      grestore\n");
        }
     }
 }
 
+sub cmd_showlibrary {
+    my ($obj_id, $y, $x, $ctx_save, $width, $height);
+    my ($max_x, $min_x, $max_y, $min_y, $nxty, $obj, $loc, $pat, $got, $glob);
+    my ($adj);
+    $x=$olu_left; $y=$olu_bottom; undef $nxty;
+    $ctx_save= $ctx;
+    foreach $obj_id (sort keys %objs) {
+       $got= 1;
+       foreach $glob (@al) {
+           $pat= $glob;
+           $got= !($pat =~ s/^\!//);
+           die "bad pat" if $pat =~ m/[^0-9a-zA-Z_*?]/;
+           $pat =~ s/\*/\.*/g; $pat =~ s/\?/./g;
+           last if $obj_id =~ m/^$pat$/;
+           $got= !$got;
+       }
+       next unless $got;           
+       $obj= $objs{$obj_id};
+       next unless $obj->{Part};
+       ($min_x, $max_x, $min_y, $max_y) = bbox($obj->{Loc});
+       newctx($ctx_save);
+
+       for (;;) {
+           $width= $max_x - $min_x;
+           $height= $max_y - $min_y;
+           if ($width < $height) {
+               $ctx->{Trans}{A}= 0;
+               $ctx->{Trans}{X}= $x - $min_x;
+               $ctx->{Trans}{Y}= $y - $min_y + $olu_textheight;
+           } else {
+               ($width,$height)=($height,$width);
+               $ctx->{Trans}{A}= 0.5 * $pi;
+               $ctx->{Trans}{X}= $x + $max_y;
+               $ctx->{Trans}{Y}= $y - $min_x + $olu_textheight;
+           }
+           $adj= length($obj_id) * $olu_textallowperc - $width;
+           $adj=0 if $adj<0;
+           $width += $adj;
+           $ctx->{Trans}{X} += 0.5 * $adj;
+           if ($x + $width > $olu_right && defined $nxty) {
+               $x= $olu_left;
+               $y= $nxty;
+               undef $nxty;
+           } elsif ($y + $height > $olu_top && $y > $olu_bottom) {
+               oflushpage();
+               $x= $olu_left; $y= $olu_bottom;
+               undef $nxty;
+           } else {
+               last;
+           }
+       }
+           
+       $ctx->{InRunObj}= $ctx_save->{InRunObj}."${obj_id}//";
+       $ctx->{Draw}= $ctx_save->{Draw};
+       cmd__runobj($obj_id);
+       ol("    gsave\n".
+          "      /s ($obj_id) def\n".
+          "      lf setfont\n      ".
+          ($x + 0.5*$width)." ".($y - $olu_textheight)." moveto\n".
+          "      s stringwidth pop -0.5 mul  0  rmoveto\n".
+          "      s show grestore\n");
+       $x += $width + $olu_gap_x;
+       upd_max(\$nxty, $y + $height + $olu_gap_y + $olu_textheight);
+    }
+    @al= ();
+    $ctx= $ctx_save;
+}
+
 sub cmd__one {
     cmd__do();
 }
 
-print
-    "%!\n".
-    "  /lf /Courier-New findfont $lmu_marktpt scalefont def\n".
-    "  $ptscale $ptscale scale\n"
-    or die $!;
+o("%!\n".
+  "  /lf /Courier-New findfont $lmu_marktpt scalefont def\n".
+  "  $ps_page_shift 0 translate 90 rotate\n");
+
+if ($page_x || $page_y) {
+    o("  /Courier-New findfont 15 scalefont setfont\n".
+      "  30 30 moveto (${page_x}x${page_y}) show\n");
+}
+
+o("  -$ps_page_xmul $page_x mul  -$ps_page_ymul $page_y mul  translate\n".
+  "  $ptscale $ptscale scale\n");
+
+o("/label_in_box {\n".
+  '% linewidth $lmu_*boxh $lmu_*padx $lmu_*boxoff (s)'.
+  '  label_in_box  => _'."\n".
+  "  /s exch def\n".
+  "  /boxoff exch def\n".
+  "  /padx exch def\n".
+  "  /boxh exch def\n".
+  "  setlinewidth\n".
+  "  lf setfont\n".
+  "  /sx5  s stringwidth pop\n".
+  "  0.5 mul padx add def\n".
+  "  -90 rotate  0 boxoff translate  newpath\n".
+  "  sx5 neg  0             moveto\n".
+  "  sx5 neg  boxh  lineto\n".
+  "  sx5      boxh  lineto\n".
+  "  sx5      0             lineto closepath\n".
+  "  gsave  1 setgray fill  grestore\n".
+  "  stroke\n".
+  "  sx5 neg padx add  $lmu_txtboxtxty\n".
+  "  moveto s show\n".
+  "} def\n");
 
 newctx();
-    
+
+open DEBUG, ($debug ? ">&2" : ">/dev/null") or die $!;
+
+if ($debug) {
+    select(DEBUG); $|=1;
+    select(STDOUT); $|=1;
+}
+
+$ctx->{Draw}= '';
+$ctx->{SegName}= '';
+
+@al= qw(layer 5);
+cmd__one();
+
 while (<>) {
     next if m/^\s*\#/;
     chomp; s/^\s+//; s/\s+$//;
     @al= split /\s+/, $_;
     next unless @al;
     print DEBUG "=== @al\n";
+    last if $al[0] eq 'eof';
     push @{ $ctx->{CmdLog} }, [ @al ] if exists $ctx->{CmdLog};
     cmd__one();
 }
 
-print $o, $ol, "  showpage\n"
-    or die $!;
+{
+    my ($min_x, $max_x, $min_y, $max_y) = bbox($ctx->{Loc});
+    my ($bboxstr);
+    if (defined $min_x) {
+       $bboxstr= sprintf("width  %.2d (%.2d..%2.d)\n".
+                         "height %.2d (%.2d..%2.d)\n",
+                         $max_x - $min_x, $min_x, $max_x,
+                         $max_y - $min_y, $min_y, $max_y);
+    } else {
+       $bboxstr= "no locs, no bbox\n";
+    }
+    if (!$quiet) { print STDERR $bboxstr; }
+    $bboxstr =~ s/^/\%L bbox /mg;
+    o($bboxstr) or die $!;
+
+    if ($scale < 1.5) {
+       my ($tick_x, $tick_y, $ticklen);
+       $ticklen= 10;
+       printf("    gsave 0.5 setgray 0.33 setlinewidth\n".
+              "      /regmark {\n".
+              "        newpath moveto\n".
+              "        -%d 0 rmoveto %d 0 rlineto\n".
+              "        -%d -%d rmoveto 0 %d rlineto stroke\n".
+              "      } def\n",
+              $ticklen, $ticklen*2, $ticklen, $ticklen, $ticklen*2)
+           or die $!;
+       for ($tick_x= $min_x; $tick_x < $max_x; $tick_x += 150) {
+           for ($tick_y= $min_y; $tick_y < $max_y; $tick_y += 150) {
+               printf("      %f %f regmark\n",
+                      $tick_x, $tick_y)
+                   or die $!;
+           }
+       }
+       printf("    grestore\n")
+           or die $!;
+    }
+}
+
+oflushpage();