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