#!/usr/bin/perl -w use POSIX; # Data structures: # $ctx->{CmdLog}= undef } not in defobj # $ctx->{CmdLog}[]= [ command args ] } in defobj # $ctx->{LocsMade}[]= $id # $ctx->{Loc}{$id}{X} # $ctx->{Loc}{$id}{Y} # $ctx->{Loc}{$id}{A} may be undef # $ctx->{Trans}{X0} } transformation # $ctx->{Trans}{Y0} } matrix # $ctx->{Trans}{XY} } # $ctx->{Trans}{YX} } # $ctx->{Trans}{XX} } # $ctx->{Trans}{YY} } # $ctx->{Trans}{AA} } # $ctx->{Trans}{AS} } # # $objs{$id}{CmdLog} # $objs{$id}{Loc} $debug=1; open DEBUG, ($debug ? ">&2" : ">/dev/null") or die $!; if ($debug) { select(DEBUG); $|=1; select(STDOUT); $|=1; } sub canf ($$) { my ($converter,$defaulter)=@_; my ($spec,$v); return &$defaulter unless @al; $spec= shift @al; $v= &$converter($spec); dv('canf ','$spec',$spec, '$v',$v); return $v; } 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); %units_len= qw(- mm mm 1 cm 10 m 1000); %units_ang= qw(- d r 1); $units_ang{'d'}= 2*$pi / 360; sub cva_len ($) { my ($sp)=@_; cva_units($sp,\%units_len); } sub cva_ang ($) { my ($sp)=@_; cva_units($sp,\%units_ang); } sub cva_absang ($) { input_absang(cva_ang($_[0])) } sub cva_units ($$) { my ($sp,$ua)=@_; my ($n,$u,$r); $sp =~ m/^([-0-9eE.]*[0-9.])([A-Za-z]*)$/ or die "lexically invalid quantity"; ($n,$u)= ($1,$2); $u=$ua->{'-'} unless length $u; defined $ua->{$u} or die "unknown unit $u"; $r= $n * $ua->{$u}; print DEBUG "cva_units($sp,)=$r ($n $u $ua->{$u})\n"; return $r; } sub cva_idstr ($) { my ($sp)=@_; die "invalid id" unless $sp =~ m/^[a-z][_0-9A-Za-z]*$/; return $&; } sub cva_idex ($) { my ($sp,$id)=@_; my ($r,$d,$k); $id=cva_idstr($sp); die "unknown $id" unless defined $ctx->{Loc}{$id}; $r= $ctx->{Loc}{$id}; $d= "idex $id"; foreach $k (sort keys %$r) { $d .= " $k=$r->{$k}"; } printf DEBUG "%s\n", $d; return $r; } sub cva_idnew ($) { my ($sp)=@_; my ($id); $id=cva_idstr($sp); die "duplicate $id" if exists $ctx->{Loc}{$id}; exists $ctx->{Loc}{$id}{X}; push @{ $ctx->{LocsMade} }, $id; return $ctx->{Loc}{$id}; } sub cva_cmd ($) { return cva_idstr($_[0]); } sub cva__enum ($$) { my ($sp,$el)=@_; return $sp if grep { $_ eq $sp } @$el; die "invalid option (permitted: @$el)"; } sub cvam_enum { my (@e) = @_; return sub { cva__enum($_[0],\@e); }; } sub cmd_abs { my ($x,$y); $nl= can(\&cva_idnew); $x= can(\&cva_len); $y= can(\&cva_len); ($nl->{X}, $nl->{Y})= input_abscoords($x,$y); $nl->{A}= cano(\&cva_absang, undef); } sub cmd_rel { $from= can(\&cva_idex); $to= can(\&cva_idnew); $len= can(\&cva_len); $right= can(\&cva_len); $turn= cano(\&cva_ang, 0); $to->{X}= $from->{X} + $len * cos($from->{A}) + $right * sin($from->{A}); $to->{Y}= $from->{Y} + $len * sin($from->{A}) - $right * cos($from->{A}); $to->{A}= $from->{A} + $turn; } sub evreff ($) { my ($pfx) = @_; $pfx . ($pfx =~ m/\}$|\]$/ ? '' : '->'); } sub evr ($) { my ($v) = @_; return $v if $v !~ m/\W/ && $v =~ m/[A-Z]/ && $v =~ m/^[a-z_]/i; return $v if $v =~ m/^[0-9.]+/; $v =~ s/[\\\']/\\$&/g; return "'$v'"; } sub dv1 ($$$); sub dv1_kind ($$$$$$$) { my ($pfx,$expr,$ref,$ref_exp,$ixfmt,$ixesfn,$ixmapfn) = @_; my ($ix,$any); return 0 if $ref ne $ref_exp; $any=0; foreach $ix (&$ixesfn) { $any=1; my ($v)= &$ixmapfn($ix); #print STDERR "dv1_kind($pfx,$expr,$ref,$ref_exp,$ixmapfn) ix=$ix v=$v\n"; dv1($pfx,$expr.sprintf($ixfmt,evr($ix)),$v); } if (!$any) { printf DEBUG "%s%s= $ixfmt\n", $pfx, $expr, ' '; } 1; } sub dv1 ($$$) { return 0 unless $debug; my ($pfx,$expr,$v) = @_; $ref= ref $v; #print STDERR "dv1 >$pfx|$ref<\n"; if (!$ref) { printf DEBUG "%s%s= %s\n", $pfx,$expr, evr($v); return; } elsif ($ref eq 'SCALAR') { dv1($pfx, ($expr =~ m/^\$/ ? "\$$expr" : '${'.$expr.'}'), $$v); return; } $expr.='->' unless $expr =~ m/\]$|\}$/; return if dv1_kind($pfx,$expr,$ref,'ARRAY','[%s]', sub { ($[ .. $#$v) }, sub { $v->[$_[0]] }); return if dv1_kind($pfx,$expr,$ref,'HASH','{%s}', sub { sort keys %$v }, sub { $v->{$_[0]} }); printf DEBUG "%s%s is %s\n", $pfx, $expr, $ref; } sub dv { my ($pfx,@l) = @_; my ($expr,$v,$ref); while (@l) { ($expr,$v,@l)=@l; dv1($pfx,$expr,$v); } } sub loc_lin_comb ($$$) { my ($a,$b,$p) = @_; my ($q,$r) = 1.0-$p; map { $r->{$_} = $q * $a->{$_} + $p * $b->{$_} } qw(X Y A); # dv("loc_lin_comb ",'$a',$a,'$b',$b,'$p',$p,'$r',$r); return $r; } $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; sub o ($) { # fixme optional marking print "$_[0]" or die $!; } sub o_path_begin () { o(" newpath\n"); $o_path_verb= 'moveto'; } sub o_path_point ($) { my ($pt)=@_; o(" $pt $o_path_verb\n"); $o_path_verb= 'lineto'; } sub o_path_stroke ($) { my ($width)=@_; o(" $width setlinewidth stroke\n"); } sub o_line ($$$) { my ($a,$b,$width)=@_; o_path_begin(); o_path_point($a); o_path_point($b); o_path_stroke($width); } 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); $ea_zo{X}=$ea_zo{Y}=0; foreach $zo (qw(0 1)) { $prop= $zo ? $inunit : (1.0 - $inunit); $ea_zo{X} += $prop * ($ends->[$zo]{X} - $across * sin($ends->[0]{A})); $ea_zo{Y} += $prop * ($ends->[$zo]{Y} + $across * cos($ends->[0]{A})); } # dv("psu_coords ", '$ends',$ends, '$inunit',$inunit, '$across',$across, # '\\%ea_zo', \%ea_zo); return $ea_zo{X}." ".$ea_zo{Y}; } 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) # $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 # (ie, ref to X =>, Y =>, A =>). my ($pa,$pb,@ends,$side,$ppu,$e,$v,$tick); return if defined $ctx->{InDefObj}; $ppu= $psu_ulen/$lenperp; my ($railctr)=($psu_gauge + $psu_raillw)*0.5; my ($tickend)=($psu_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"; for ($pa= $p0; $pa<$p1; $pa=$pb) { $pb= $pa + $ppu; $p= $pa; $ends[0]= @ends ? $ends[1] : &$calcfn; $p= $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(" 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); } } o_line(psu_coords(\@ends,$sleeperctr,-$sleeperend), psu_coords(\@ends,$sleeperctr,+$sleeperend), $psu_sleeperlw); o(" grestore\n"); } } sub cmd_extend { my ($from,$to,$radius,$ctr,$beta,$ang,$how,$signum); $from= can(\&cva_idex); $to= can(\&cva_idnew); printf DEBUG "from $from->{X} $from->{Y} $from->{A}\n"; die "no ang" unless defined $from->{A}; $how= can(cvam_enum(qw(len upto ang uptoang parallel))); if ($how eq 'len') { $len= can(\&cva_len); } elsif ($how =~ m/ang$/) { $ang= can(\&cva_ang); } 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"; } if ($how eq 'upto') { $len= ($upto->{X} - $from->{X}) * cos($from->{A}) + ($upto->{Y} - $from->{Y}) * sin($from->{A}); } 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, $len, sub { loc_lin_comb($from, $to, $p); }); } else { print DEBUG "radius >$radius<\n"; $radius *= $ctx->{Trans}{AA}; $signum= $radius / abs($radius); $ctr->{X}= $from->{X} + $radius * sin($from->{A}); $ctr->{Y}= $from->{Y} - $radius * cos($from->{A}); if ($how eq 'upto') { $beta= atan2(-$signum * ($upto->{X} - $ctr->{X}), $signum * ($upto->{Y} - $ctr->{Y})); $beta_interval= 1.0; } elsif ($how eq 'parallel') { $beta= $upto->{A}; $beta_interval= 1.0; } elsif ($how eq 'uptoang') { $beta= input_absang($ang); $beta_interval= 2.0; } elsif ($how eq 'len') { $beta= $from->{A} - $signum * $len / abs($radius); $beta_interval= 2.0; } else { $beta= $from->{A} - $signum * $ang; $beta_interval= 2.0; } printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n"; $beta += $signum * 4.0 * $pi; for (;;) { $delta= $beta - $from->{A}; last if $signum * $delta <= 0; $beta -= $signum * $beta_interval * $pi; } printf DEBUG "ctr->{Y}=$ctr->{Y} radius=$radius beta=$beta\n"; $to->{A}= $beta; $to->{X}= $ctr->{X} - $radius * sin($beta); $to->{Y}= $ctr->{Y} + $radius * cos($beta); parametric_segment(II, 0.0, 1.0, abs($radius*$delta), sub { my ($beta) = $from->{A} + $delta * $p; return { X => $ctr->{X} - $radius * sin($beta), Y => $ctr->{Y} + $radius * cos($beta), A => $beta } }); } printf DEBUG "to $to->{X} $to->{Y} $to->{A}\n"; } sub cmd__do { my ($id, $cmd, $loc); $ctx->{LocsMade}= [ ]; $cmd= can(\&cva_cmd); &{ "cmd_$cmd" }; die "too many args" if @al; foreach $id (@{ $ctx->{LocsMade} }) { $loc= $ctx->{Loc}{$id}; o("% point $id $loc->{X} $loc->{Y} ".ang2deg($loc->{A})."\n"); } } sub cmd__one { cmd__do(); } sub ang2deg ($) { return $_[0] * 180 / $pi; } sub input_absang ($) { return $_[0] * $ctx->{Trans}{AA} + $ctx->{Trans}{A0}; } sub input_abscoords ($$) { my ($in,$out, $i); ($in->{X}, $in->{Y})= @_; foreach $o (qw(X Y)) { $out->{$o}= $ctx->{Trans}{$o.0}; foreach $i (qw(X Y)) { $out->{$o} += $ctx->{Trans}{"$i$o"} * $in->{$i}; } } return ($out->{X}, $out->{Y}); } sub newctx () { $ctx= { Trans => { X0 => 0.0, Y0 => 0.0, XY => 0.0, YX => 0.0, A0 => 0.0, AA => 1.0, XX => 1.0, YY => 1.0 } } } sub cmd_defobj { my ($id); $id= can(\&cva_idstr); die "nested defobj" if $defobj_save; die "repeated defobj" if exists $objs{$id}; $defobj_save= $ctx; newctx(); $ctx->{CmdLog}= [ ]; $ctx->{InDefObj}= $id; } sub cmd_enddefobj { my ($bit,$id); $id= $ctx->{InDefObj}; die "unmatched enddefobj" unless defined $id; foreach $bit (qw(CmdLog Loc)) { $objs{$id}{$bit}= $ctx->{$bit}; } $ctx= $defobj_save; $defobj_save= undef; } sub cmd_obj { cmd__obj(1); } sub cmd_objflip { cmd__obj(-1); } sub cmd__obj ($) { my ($flipsignum)=@_; my ($obj_id, $ctx_save, $pfx); $obj_id= can(\&cva_idstr); $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(); o("% obj $obj_id\n"); $ctx->{Trans}{AA}= $flipsignum; $ctx->{Trans}{A0}= $actual->{A} - $formal->{A}/$flipsignum; $ctx->{Trans}{XX}= cos($ctx->{Trans}{A0}); $ctx->{Trans}{YY}= $flipsignum * cos($ctx->{Trans}{A0}); $ctx->{Trans}{XY}= $flipsignum * sin($ctx->{Trans}{A0}); $ctx->{Trans}{YX}= -$flipsignum * sin($ctx->{Trans}{A0}); ($xformcv,$yformcv)= input_abscoords($formal->{X}, $formal->{Y}); print STDERR ">$xformcv|$yformcv<\n"; $ctx->{Trans}{X0}= $actual->{X} - $xformcv; $ctx->{Trans}{Y0}= $actual->{Y} - $yformcv; { local (@al); foreach $c (@{ $obj->{CmdLog} }) { @al= @$c; next if $al[0] eq 'enddefobj'; cmd__one(); } } $pfx= cano(\&cva_idstr,''); if (length $pfx) { foreach $id (keys %{ $ctx->{Loc} }) { $newid= $pfx.$id; next if exists $ctx_save->{Loc}{$newid}; $pt= $ctx->{Loc}{$id}; $newpt= { A => input_absang($pt->{A}) }; ($newpt->{X}, $newpt->{Y})= input_abscoords($pt->{X}, $pt->{Y}); $ctx_save->{Loc}{$newid}= $newpt; } } $ctx= $ctx_save; } $ptscale= 72/25.4 / 5.0; o("%!\n". " $ptscale $ptscale scale\n"); newctx(); while (<>) { next if m/^\s*\#/; chomp; s/^\s+//; s/\s+$//; @al= split /\s+/, $_; next unless @al; print DEBUG "=== @al\n"; push @{ $ctx->{CmdLog} }, [ @al ] if exists $ctx->{CmdLog}; cmd__one(); } o(" showpage\n");