chiark / gitweb /
first cut of distance labels
[trains.git] / layout / layout
index 868216da84d2e8d1624a5461b330db4b88784f6c..ed8ba2f2c41b4507a18b961c817763eb78964489 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;
@@ -17,13 +157,19 @@ 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.5;
 our $lmu_txtboxlw= 1;
 
 our $olu_left= 10 * $scale;
@@ -31,12 +177,11 @@ 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= 30;
+our $olu_gap_y= 60;
 our $olu_textheight= 15;
 our $olu_textallowperc= $lmu_marktpt * 5.0/11;
 
 our $pi= atan2(0,-1);
-our $output_layer= '*';
 
 sub allwidth2 ($) {
     my ($radius)= @_;
@@ -54,17 +199,29 @@ our $allwidthmin= allwidth(undef);
 # Data structures:
 #  $ctx->{CmdLog}= undef                  } not in defobj
 #  $ctx->{CmdLog}[]= [ command args ]     } in defobj
-#  $ctx->{LocsMade}[]{Id}=  $id
-#  $ctx->{LocsMade}[]{Neg}= $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}           # sequence of one or more chrs from uc $drawers
-#                         #  or X meaning never draw anything (eg in defobj)
+#                         #  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}
 #
@@ -75,17 +232,21 @@ our $allwidthmin= allwidth(undef);
 #  $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 '/'
 
 our $ctx;
 our %objs;
-our @eopts;
 our @al; # current cmd
 
 our $o='';
 our $ol='';
 
-our $param; # for parametric_curve
-our $debug=0;
+our $param; # for parametric_segment
 
 # ev_... functions
 #
@@ -309,8 +470,11 @@ sub cva_idnew ($) {
     $neg = $sp =~ s/^\-//;
     $id=cva_idstr($sp);
     die "duplicate $id" if exists $ctx->{Loc}{$id};
-    exists $ctx->{Loc}{$id}{X};
-    push @{ $ctx->{LocsMade} }, { Id => $id, Neg => $neg };
+    $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]); }
@@ -404,6 +568,7 @@ sub dv {
 sub o ($) { $o .= $_[0]; }
 sub ol ($) { $ol .= $_[0]; }
 sub oflushpage () {
+    return if $subsegcmapreq;
     print $o, $ol, "  showpage\n"
        or die $!;
     $o=$ol='';
@@ -423,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)=@_;
@@ -433,6 +601,12 @@ 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);
@@ -454,15 +628,74 @@ sub parametric__o_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 ($p0,$p1,$lenperp,$minradius,$calcfn) = @_;
-    # makes $p (global) go from $p0 to $p1  ($p1>$p0)
+    # 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,$draw,$allwidth);
-    return unless $ctx->{Draw} =~ m/[ARSC]/;
+    return unless $ctx->{Draw} =~ m/[ARSCG]/;
     $ppu= $psu_ulen/$lenperp;
     $allwidth= allwidth($minradius);
     my ($railctr)=($psu_gauge + $psu_raillw)*0.5;
@@ -471,7 +704,64 @@ sub parametric_segment ($$$$$) {
     my ($sleeperctr)=($psu_ulen*0.5);
     my ($sleeperend)=($psu_sleeperlen*0.5);
 print DEBUG "ps $p0 $p1 $lenperp ($ppu)\n";
-    $draw= $ctx->{Draw};
+    $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");
@@ -483,6 +773,19 @@ print DEBUG "ps $p0 $p1 $lenperp ($ppu)\n";
        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}) + 90;
+       $len= sprintf "%.0f", $lenperp * abs($p1-$p0);
+       ol("      gsave\n".
+          "        $pt->{X} $pt->{Y} translate\n".
+          "        $ad rotate 0.75 dup scale\n".
+          "        0 $lmu_lenboxh $lmu_lenboxpadx $lmu_lenboxoff".
+          " ($len) label_in_box\n".
+          "      grestore\n");
+    }
     return unless $draw =~ m/[ARS]/;
     for ($pa= $p0; $pa<$p1; $pa=$pb) {
        $pb= $pa + $ppu;
@@ -653,11 +956,7 @@ sub joins_arcline ($$$$) {
     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)) {
-#    {
-#      $swap=+1;
        foreach $echoice (qw(0 1)) {
-#      {
-#          $echoice=0;
            $ap= $from; $bp= { %$to }; $bp->{A} += $pi;
            ($ap,$bp)= ($bp,$ap) if $swap<0;
            $av= ev_byang({}, $ap->{A});
@@ -665,13 +964,10 @@ sub joins_arcline ($$$$) {
            $e= ev_byang({}, 0.5 * ($ap->{A} + $bp->{A} + $echoice * $pi));
            $f= v_rotateright($e);
            o("%     arcline $swap $echoice e ".loc2dbg($e)."\n");
-#          o("%     arcline $swap $echoice f ".loc2dbg($f)."\n");
-#          o("%     arcline $swap $echoice av ".loc2dbg($av)."\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;
-                              o("%");}
+           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);
@@ -681,8 +977,7 @@ sub joins_arcline ($$$$) {
            $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;
-                       o("%");}
+           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);
@@ -690,8 +985,9 @@ sub joins_arcline ($$$$) {
            $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;
-                            o("%");}
+           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 },
@@ -713,8 +1009,8 @@ sub cmd_join {
     $to= can(\&cva_idex);
     $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_twoarcs(\@results, $from,$to,$minradius);
+    joins_arcsline(\@results, $from,$to,$minradius);
     joins_arcline(\@results, $from,$to,$minradius);
     foreach $result (@results) {
        $path= $result->{Path};
@@ -746,7 +1042,7 @@ sub cmd_join {
                elsif ($1 eq 'end') { $cs= $bends[$#bends]; }
                else { $cs=0; map { $cs += $_ } @bends; }
                $cs= -$cs if $2 eq 'left';
-           } elsif ($crit =~ m/^(\!?)(twoarcs|arcline|cross|loop)$/) {
+           } elsif ($crit =~ m/^(\!?)(twoarcs|arcs?line|cross|loop)$/) {
                $cs= !!(grep { $2 eq $_ } @$skl) != ($1 eq '!');
            } else {
                die "unknown sort criterion $crit";
@@ -779,7 +1075,10 @@ sub cmd_join {
 
 sub line ($$$) {
     my ($from,$to,$len) = @_;
-    parametric_segment(0.0, 1.0, abs($len), undef, sub {
+    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);
     });
 }
@@ -870,10 +1169,15 @@ sub newctx (;$) {
     my ($ctx_save) = @_;
     $ctx= {
        Trans => { X => 0.0, Y => 0.0, A => 0.0, R => 1.0 },
-       InRunObj => ""
+       InRunObj => "",
+       DrawMap => sub { $_[0]; },
+       SegMapN => { },
+       SegMapNM => { }
        };
-    %{ $ctx->{Layer} }= %{ $ctx_save->{Layer} }
-        if defined $ctx_save;
+    if (defined $ctx_save) {
+       %{ $ctx->{Layer} }= %{ $ctx_save->{Layer} };
+       $ctx->{Parent}= $ctx_save;
+    }
 }
 
 our $defobj_save;
@@ -892,7 +1196,8 @@ sub cmd__defobj ($) {
     newctx($defobj_save);
     $ctx->{CmdLog}= [ ];
     $ctx->{InDefObj}= $id;
-    $ctx->{Draw}= 'X';
+    $ctx->{Draw}= $defobj_save->{Draw}.'X';
+    $ctx->{DrawMap}= sub { ''; };
     $ctx->{Layer}= { Level => 5, Kind => '' };
 }
 
@@ -921,34 +1226,125 @@ sub cmd__runobj ($) {
     }
 }
 
-sub cmd_layer {
-    my ($kl, $k,$l, $eo,$cc);
-    $kl= can(\&cva_identity);
-    $kl =~ m/^([A-Za-z_]*)(\d*|\=)$/ or die "invalid layer spec";
-    ($k,$l)=($1,$2);
-    $l= $ctx->{Layer}{Level} if $l =~ m/^\=?$/;
-    $ctx->{Layer}{Kind}= $l;
-    $ctx->{Layer}{Level}= $l;
-    return if $ctx->{Draw} =~ m/X/;
-    if ($output_layer ne '*' && $l != $output_layer) {
-       $ctx->{Draw} = '';
-    } elsif ($k eq '') {
-       $ctx->{Draw}= 'RLMN';
+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') {
-       $ctx->{Draw}= '';
+       $r= '';
     } elsif ($k eq 'l') {
-       $ctx->{Draw}= 'CLMN';
+       $r= 'CLMN';
     } else {
-       $ctx->{Draw}= 'ARSCLMNO';
+       $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}) {
-           $ctx->{Draw} =~ s/$cc//ig;
-           $ctx->{Draw} .= $cc if $cc =~ m/[A-Z]/;
+           $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); }
@@ -992,14 +1388,22 @@ sub cmd__obj ($) {
     } else {
        $ctx->{InRunObj}= $ctx_save->{InRunObj}."${obj_id}::";
     }
-    $ctx->{Draw}= $ctx_save->{Draw};
-    if ($obj->{Part}) {
-       $ctx->{Draw} =~ s/[LMN]//g;
-       $ctx->{Draw} =~ s/O/MNO/;
-    } else {
-       $ctx->{Draw} =~ s/[LM]//g;
-       $ctx->{Draw} =~ s/N/MN/;
+    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;
+    };
+    $ctx->{Draw}= &{ $ctx->{DrawMap} }($ctx_save->{Draw});
     cmd__runobj($obj_id);
     if (defined $part_name) {
        $pfx= $part_name.'_';
@@ -1010,6 +1414,9 @@ sub cmd__obj ($) {
            $pfx= cano(\&cva_idstr,undef);
        }
     }
+    if (exists $ctx->{SavedSegment}) {
+       @segments= ($ctx->{SavedSegment});
+    }
     $ctx_inobj= $ctx;
     $ctx= $ctx_save;
     if (defined $pfx) {
@@ -1044,7 +1451,7 @@ sub cmd__do {
     my ($cmd);
 dv("cmd__do $ctx @al ",'$ctx',$ctx);
     $cmd= can(\&cva_cmd);
-    my ($lm,$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}= [ ];
@@ -1059,28 +1466,18 @@ dv("cmd__do $ctx @al ",'$ctx',$ctx);
        $loc->{A} += $pi if $lm->{Neg};
        $ad= ang2deg($loc->{A});
        ol("%L point $io$id ".loc2dbg($loc)." ($lm->{Neg})\n");
-       if ($ctx->{Draw} =~ m/[LM]/) {
+       $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} =~ m/M/) {
+           if ($draw =~ m/M/) {
                ol("      0 $allwidthmin newpath moveto\n".
                   "      0 -$allwidthmin lineto\n".
                   "      $lmu_marklw setlinewidth stroke\n");
            }
-           if ($ctx->{Draw} =~ 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");
        }
@@ -1159,89 +1556,41 @@ sub cmd__one {
     cmd__do();
 }
 
-print
-    "%!\n".
-    "  /lf /Courier-New findfont $lmu_marktpt scalefont def\n".
-    "  615 0 translate 90 rotate\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");
 
-newctx();
+if ($page_x || $page_y) {
+    o("  /Courier-New findfont 15 scalefont setfont\n".
+      "  30 30 moveto (${page_x}x${page_y}) show\n");
+}
 
-our $drawers= 'arsclmno';
-our %chdraw_emap= qw(A ARSc
-                    R aRsc
-                    S aRSc
-                    C arsC
-                    c Arsc
-                    L LM
-                    l l
-                    M Mno
-                    N MNo
-                    O MNO
-                    m mnol);
+o("  -$ps_page_xmul $page_x mul  -$ps_page_ymul $page_y mul  translate\n".
+  "  $ptscale $ptscale scale\n");
 
-our $quiet=0;
+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");
 
-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/^(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;
-           $d= $output_layer if !length $d;
-           $d= 5 if $d eq '*';
-           $invert= length $n;
-           $c= '=' if !length $c;
-           if (length $v) {
-               die '-[eE]GN[D]CCV not allowed' if length $c > 1;
-               $c= $c x $v;
-           }
-           if ($c =~ m/^[-+]/) {
-               $sense= ($c.'1') + 0;
-               $limit= ($sense * $d) + length($c) - 1;
-               $lfn= sub {
-                   ($output_layer eq '*' ? $d
-                    : $_[0]) * $sense >= $limit
-                        xor $invert;
-               };
-           } else {
-               $limit= length($c) - 1;
-               $lfn= sub {
-                   ($output_layer eq '*' ? 1
-                    : abs($_[0] - $d) <= $limit)
-                       xor $invert;
-               };
-           }
-           $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;
-           push @eopts, $eo;
-       } else {
-           die "unknown option -$_";
-       }
-    }
-}
+newctx();
 
 open DEBUG, ($debug ? ">&2" : ">/dev/null") or die $!;
 
@@ -1251,6 +1600,7 @@ if ($debug) {
 }
 
 $ctx->{Draw}= '';
+$ctx->{SegName}= '';
 
 @al= qw(layer 5);
 cmd__one();
@@ -1266,8 +1616,6 @@ while (<>) {
     cmd__one();
 }
 
-oflushpage();
-
 {
     my ($min_x, $max_x, $min_y, $max_y) = bbox($ctx->{Loc});
     my ($bboxstr);
@@ -1281,5 +1629,29 @@ oflushpage();
     }
     if (!$quiet) { print STDERR $bboxstr; }
     $bboxstr =~ s/^/\%L bbox /mg;
-    print $bboxstr or die $!;
+    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();