File Coverage

lib/Kite/Profile.pm
Criterion Covered Total %
statement 81 247 32.7
branch 20 94 21.2
condition 12 45 26.6
subroutine 22 46 47.8
pod 26 41 63.4
total 161 473 34.0


line stmt bran cond sub pod time code
1             #============================================================= -*-perl-*-
2             #
3             # Kite::Profile
4             #
5             # DESCRIPTION
6             # Module defining an object class used to represent and manipulate
7             # 2D profiles.
8             #
9             # AUTHOR
10             # Andy Wardley
11             #
12             # COPYRIGHT
13             # Copyright (C) 2000 Andy Wardley. All Rights Reserved.
14             #
15             # This module is free software; you can redistribute it and/or
16             # modify it under the same terms as Perl itself.
17             #
18             # VERSION
19             # $Id: Profile.pm,v 1.3 2000/10/18 08:37:49 abw Exp $
20             #
21             #========================================================================
22            
23             package Kite::Profile;
24              
25             require 5.004;
26              
27 1     1   6 use strict;
  1         2  
  1         44  
28 1     1   6 use Kite::Base;
  1         3  
  1         30  
29 1     1   32 use base qw( Kite::Base );
  1         3  
  1         145  
30 1     1   7 use vars qw( $VERSION $ERROR $DEBUG );
  1         2  
  1         3671  
31              
32             $VERSION = sprintf("%d.%02d", q$Revision: 1.3 $ =~ /(\d+)\.(\d+)/);
33             $DEBUG = 0 unless defined $DEBUG;
34             $ERROR = '';
35              
36              
37             #------------------------------------------------------------------------
38             # init(\%params)
39             #
40             # Initialisation method called by the Kite::Base base class
41             # constructor, new(). A reference to a hash array of configuration
42             # parameters is passed. The method returns a true value ($self) if
43             # successful, or undef on error, with the internal ERROR value set.
44             #------------------------------------------------------------------------
45              
46             sub init {
47 3     3 1 5 my ($self, $params) = @_;
48            
49             # if a FILE parameter is defined then we call parse_file() to load and
50             # parse the profile. If TEXT is defined then we call _parse_text().
51             # Otherwise we copy any NAME, X and Y parameters.
52              
53             # map all config parameters to upper case
54 3         48 @$params{ map { uc $_ } keys %$params } = values %$params;
  7         50  
55              
56 3 50       14 if ($params->{ FILE }) {
    50          
57 0 0       0 $self->parse_file($params->{ FILE }) || return undef; ## RETURN ##
58             }
59             elsif ($params->{ TEXT }) {
60 0 0       0 $self->parse($params->{ TEXT }) || return undef; ## RETURN ##
61             }
62             else {
63 3         6 my @keys = qw( NAME X Y );
64 3         16 @$self{ @keys } = @$params{ @keys };
65             return $self->error("profile NAME not specified")
66 3 50       10 unless $self->{ NAME };
67             return $self->error("profile X values not specified")
68 3 100       15 unless $self->{ X };
69             return $self->error("invalid profile X values (expects list ref)")
70 2 50       8 unless ref $self->{ X } eq 'ARRAY';
71             return $self->error("profile Y values not specified")
72 2 50       7 unless $self->{ Y };
73             return $self->error("invalid profile Y values (expects list ref)")
74 2 50       6 unless ref $self->{ Y } eq 'ARRAY';
75             }
76              
77 2         12 return $self;
78             }
79              
80              
81             #------------------------------------------------------------------------
82             # parse_file($file)
83             #
84             # Method called by init() to load a file and parse the contents
85             # (via a call to parse_text()) when a FILE parameter is specified.
86             #------------------------------------------------------------------------
87              
88             sub parse_file {
89 0     0 0 0 my ($self, $filename) = @_;
90 0         0 my $text;
91 0         0 local *FIN;
92              
93 0 0       0 print STDERR "parse_file($filename)\n" if $DEBUG;
94              
95 0         0 $self->{ FILENAME } = $filename;
96            
97             # undefine Input Record Separator and read entire file in one go
98 0         0 local $/ = undef;
99 0 0       0 open(FIN, $filename)
100             || return $self->error("$filename: $!");
101 0         0 $text = ;
102 0         0 close(FIN);
103              
104 0         0 $self->parse_text($text);
105             }
106              
107              
108             #------------------------------------------------------------------------
109             # parse_text($text)
110             #
111             # Method called by init() or parse_file() to parse the profile
112             # definition text into an internal form.
113             #------------------------------------------------------------------------
114              
115             sub parse_text {
116 0     0 0 0 my ($self, $text) = @_;
117 0         0 my @lines = split(/\n/, $text);
118 0   0     0 my $source = $self->{ FILENAME } ||= 'input text';
119 0         0 my ($line, $x, $y, @x, @y);
120              
121 0 0       0 print STDERR "parse_text(\"$text\")\n" if $DEBUG;
122              
123 0   0     0 $self->{ NAME } = shift(@lines)
124             || return $self->error("Profile name not found in $source");
125              
126 0         0 while (defined($line = shift @lines)) {
127 0         0 chomp $line;
128             # ignore blank lines and comments, starting '#' or '%'
129 0 0 0     0 next if $line =~ /^[#%]/ || $line =~ /^\s*$/;
130 0         0 ($x, $y) = $line =~ /([\d\-\.]+)\s*([\d\-\.]+)/;
131 0         0 push(@x, $x);
132 0         0 push(@y, $y);
133             }
134 0         0 $self->{ X } = \@x;
135 0         0 $self->{ Y } = \@y;
136              
137 0         0 return 1;
138             }
139              
140              
141             #------------------------------------------------------------------------
142             # name($newname)
143             #
144             # Returns the existing NAME member if called without any arguments.
145             # Updates the NAME if called with a new name parameter.
146             #------------------------------------------------------------------------
147              
148             sub name {
149 2     2 1 16 my $self = shift;
150 2 50       5 if (@_) {
151 0         0 $self->{ NAME } = shift;
152             }
153             else {
154 2         9 return $self->{ NAME };
155             }
156             }
157              
158              
159             #------------------------------------------------------------------------
160             # x()
161             # y()
162             # nodes()
163             #
164             # Return a pair of references to the X and Y value lists.
165             #------------------------------------------------------------------------
166              
167             sub x {
168 0     0 0 0 my $self = shift;
169 0         0 return $self->{ X };
170             }
171              
172             sub y {
173 0     0 0 0 my $self = shift;
174 0         0 return $self->{ Y };
175             }
176              
177             sub nodes {
178 0     0 0 0 my $self = shift;
179 0         0 return ($self->{ X }, $self->{ Y });
180             }
181              
182              
183             #------------------------------------------------------------------------
184             # n_nodes()
185             #
186             # Return the number of nodes that constitute the profile.
187             #------------------------------------------------------------------------
188              
189             sub n_nodes {
190 3     3 0 4 my $self = shift;
191 3   50     11 return $self->{ SIZE } ||= scalar @{ $self->{ X } };
  3         15  
192             }
193              
194              
195             #------------------------------------------------------------------------
196             # min(\@set)
197             # max(\@set)
198             #
199             # Respectively return the minimum and maximum values of the items in the
200             # list passed by reference.
201             #------------------------------------------------------------------------
202              
203             sub min {
204 4     4 0 5 my ($self, $set) = @_;
205 4         5 my $min;
206              
207 4         7 foreach (@$set) {
208 20 100       40 $min = $_, next
209             unless defined $min;
210 16 50       30 $min = $_
211             if $_ < $min;
212             }
213 4         32 $min;
214             }
215              
216             sub max {
217 4     4 0 5 my ($self, $set) = @_;
218 4         5 my $max;
219              
220 4         8 foreach (@$set) {
221 20 100       33 $max = $_, next
222             unless defined $max;
223 16 100       32 $max = $_
224             if $_ > $max;
225             }
226 4         23 $max;
227             }
228              
229             sub min_x {
230 4     4 1 6 my $self = shift;
231 4   66     24 $self->{ MINX } ||= $self->min($self->{ X });
232             }
233              
234             sub min_y {
235 4     4 1 4 my $self = shift;
236 4   66     39 $self->{ MINY } ||= $self->min($self->{ Y });
237             }
238              
239             sub max_x {
240 4     4 1 5 my $self = shift;
241 4   66     19 $self->{ MAXX } ||= $self->max($self->{ X });
242             }
243              
244             sub max_y {
245 4     4 1 5 my $self = shift;
246 4   66     20 $self->{ MAXY } ||= $self->max($self->{ Y });
247             }
248              
249              
250             #------------------------------------------------------------------------
251             # length()
252             # height()
253             #
254             # Return the length and height of the profile as calculated by the
255             # difference between maximum and minimum points in x and y respectively.
256             #------------------------------------------------------------------------
257              
258             sub length {
259 2     2 1 3 my $self = shift;
260 2   33     29 $self->{ LENGTH } ||= $self->max_x() - $self->min_x();
261             }
262              
263             sub height {
264 2     2 1 5 my $self = shift;
265 2   33     10 $self->{ HEIGHT } ||= $self->max_y() - $self->min_y();
266             }
267              
268              
269             #------------------------------------------------------------------------
270             # translate(\@set, $amount);
271             # translate_x($amount)
272             # translate_y($amount)
273             #
274             # Translate all the X/Y values by the specified amount.
275             #------------------------------------------------------------------------
276              
277             sub translate {
278 1     1 0 2 my ($self, $set, $amount) = @_;
279 1         2 foreach my $i (@$set) {
280 5         6 $i = $i + $amount;
281             }
282             }
283              
284             sub translate_x {
285 1     1 1 3 my ($self, $amount) = @_;
286 1 50       6 print STDERR "translate_x($amount)\n" if $DEBUG;
287 1         3 $self->translate($self->{ X }, $amount);
288 1         3 $self->_changed_size(); # clear memoised size values
289             }
290            
291             sub translate_y {
292 0     0 1 0 my ($self, $amount) = @_;
293 0 0       0 print STDERR "translate_y($amount)\n" if $DEBUG;
294 0         0 $self->translate($self->{ Y }, $amount);
295 0         0 $self->_changed_size(); # clear memoised size values
296             }
297              
298              
299              
300             #------------------------------------------------------------------------
301             # scale(\@set, $factor);
302             # scale_xy($factor)
303             # scale_x($factor)
304             # scale_y($factor)
305             #
306             # Scale all the X/Y values by the specified factor.
307             #------------------------------------------------------------------------
308              
309             sub scale {
310 1     1 0 2 my ($self, $set, $factor) = @_;
311 1         3 foreach my $i (@$set) {
312 5         8 $i = $i * $factor;
313             }
314             }
315              
316             sub scale_x {
317 0     0 1 0 my ($self, $factor) = @_;
318 0 0       0 print STDERR "scale_x($factor)\n" if $DEBUG;
319 0         0 $self->scale($self->{ X }, $factor);
320 0         0 $self->_changed_size(); # clear memoised size values
321             }
322            
323             sub scale_y {
324 1     1 1 2 my ($self, $factor) = @_;
325 1 50       4 print STDERR "scale_y($factor)\n" if $DEBUG;
326 1         4 $self->scale($self->{ Y }, $factor);
327 1         4 $self->_changed_size(); # clear memoised size values
328             }
329            
330             sub scale_xy {
331 0     0 1 0 my ($self, $factor) = @_;
332 0 0       0 print STDERR "scale_xy($factor)\n" if $DEBUG;
333 0         0 $self->scale($self->{ X }, $factor);
334 0         0 $self->scale($self->{ Y }, $factor);
335 0         0 $self->_changed_size(); # clear memoised size values
336             }
337              
338              
339             #------------------------------------------------------------------------
340             # normalise()
341             # normalise_x()
342             # normalise_y()
343             #
344             # normalise_x() adjusts the profile so that the X values range from 0
345             # to 1. It first translates the profile along the X axis so that min_x
346             # is 0 and then scales the X values by 1/length (i.e. 1/max_x) so that
347             # they are normalised to the range 0 - 1.
348             #
349             # normalise_y() scales the profile to height 1 but does not perform any
350             # translation. Airfoil profiles typically extend above and below Y=0
351             # and any such translation would change the centre line position, something
352             # we probably don't want to do.
353             #
354             # normalise() adjusts the X values as per normalise_x() and then scales
355             # the Y values by the *same* factor. This normalises the profile length
356             # to 1, and scales the Y values in proportion.
357             #------------------------------------------------------------------------
358              
359             sub normalise {
360 0     0 1 0 my $self = shift;
361 0 0       0 print STDERR "normalise()\n" if $DEBUG;
362 0         0 $self->translate_x(-$self->min_x); # translate so that min_x == 0
363 0         0 $self->scale_xy(1 / $self->max_x); # scale so that max_x == 1
364             }
365              
366             sub normalise_x {
367 0     0 1 0 my $self = shift;
368 0 0       0 print STDERR "normalise_x()\n" if $DEBUG;
369 0         0 $self->translate_x(-$self->min_x); # translate so that min_x == 0
370 0         0 $self->scale_xy(1 / $self->max_x); # scale so that max_x == 1
371 0         0 $self->scale_x(1 / $self->max_x);
372             }
373              
374             sub normalise_y {
375 0     0 1 0 my $self = shift;
376 0 0       0 print STDERR "normalise_y()\n" if $DEBUG;
377 0         0 $self->scale_y(1 / $self->height);
378             }
379              
380              
381             #------------------------------------------------------------------------
382             # origin()
383             # origin_x()
384             # origin_y()
385             #
386             # Translate X/Y, X or Y values so that min_x/min_y lies at the origin 0.
387             #------------------------------------------------------------------------
388              
389             sub origin {
390 0     0 1 0 my $self = shift;
391 0 0       0 print STDERR "origin()\n" if $DEBUG;
392 0         0 $self->translate_x(-$self->min_x);
393 0         0 $self->translate_y(-$self->min_y);
394             }
395            
396             sub origin_x {
397 0     0 1 0 my $self = shift;
398 0 0       0 print STDERR "origin_x()\n" if $DEBUG;
399 0         0 $self->translate_x(-$self->min_x);
400             }
401            
402             sub origin_y {
403 0     0 1 0 my $self = shift;
404 0 0       0 print STDERR "origin_y()\n" if $DEBUG;
405 0         0 $self->translate_y(-$self->min_y);
406             }
407              
408            
409             #------------------------------------------------------------------------
410             # insert($n, $x, $y)
411             #
412             # Insert a new node at position $n with the values $x and $y. The
413             # existing node $n and remainder of the list are moved down by 1 to
414             # make room for the new node.
415             #------------------------------------------------------------------------
416              
417             sub insert {
418 0     0 1 0 my ($self, $n, $x, $y) = @_;
419 0         0 my $size = $self->n_nodes();
420            
421 0 0 0     0 return $self->error("specific node is out of range ($n)")
422             if $n < 0 || $n > $size;
423              
424 0 0       0 print STDERR "insert($n, $x, $y)\n" if $DEBUG;
425            
426 0         0 splice(@{ $self->{ X } }, $n, 0, $x);
  0         0  
427 0         0 splice(@{ $self->{ Y } }, $n, 0, $y);
  0         0  
428              
429 0         0 $self->_changed_size();
430              
431 0         0 return 1;
432             }
433              
434              
435             #------------------------------------------------------------------------
436             # delete($n)
437             #
438             # Delete node $n and shift the remainder of the list up by one to fill
439             # the gap.
440             #------------------------------------------------------------------------
441              
442             sub delete {
443 0     0 1 0 my ($self, $n) = @_;
444 0         0 my $size = $self->n_nodes();
445            
446 0 0 0     0 return $self->error("specific node is out of range ($n)")
447             if $n < 0 || $n >= $size;
448              
449 0 0       0 print STDERR "delete($n)\n" if $DEBUG;
450            
451 0         0 splice(@{ $self->{ X } }, $n, 1);
  0         0  
452 0         0 splice(@{ $self->{ Y } }, $n, 1);
  0         0  
453              
454 0         0 $self->_changed_size();
455              
456 0         0 return 1;
457             }
458              
459              
460             #------------------------------------------------------------------------
461             # keep($from, $to)
462             #
463             # Splits the profile into two parts, keeping the section of nodes from
464             # $from to $to and discarding the rest. The section $from - $to becomes
465             # the new profile.
466             #------------------------------------------------------------------------
467              
468             sub keep {
469 0     0 1 0 my ($self, $from, $to) = @_;
470              
471 0 0       0 if ($from > $to) {
472 0         0 my $tmp = $from;
473 0         0 $from = $to;
474 0         0 $to = $tmp;
475             }
476              
477 0 0       0 return $self->error("lower limit is out of range ($from)")
478             if $from < 0;
479             return $self->error("upper limit is out of range ($to)")
480 0 0       0 if $to >= scalar @{ $self->{ X } };
  0         0  
481              
482 0 0       0 print STDERR "keep($from, $to)\n" if $DEBUG;
483            
484             # perl's splice() expects ARRAY, OFFSET, LENGTH
485 0         0 $to = ++$to - $from;
486 0         0 $self->{ X } = [ splice(@{ $self->{ X } }, $from, $to) ];
  0         0  
487 0         0 $self->{ Y } = [ splice(@{ $self->{ Y } }, $from, $to) ];
  0         0  
488              
489 0         0 return 1;
490             }
491              
492              
493             #------------------------------------------------------------------------
494             # closed()
495             # close()
496             #
497             # closed() returns true if the profile is closed. That is, if the last
498             # node has the same X, Y values as the first. close() duplicates the
499             # first node at the end of the list, if necessary, to ensure that the
500             # profile is closed.
501             #------------------------------------------------------------------------
502              
503             sub closed {
504 1     1 1 1 my $self = shift;
505 1         3 my $last = $self->n_nodes - 1;
506             return ( $self->{ X }->[ $last ] == $self->{ X }->[0]
507 1   33     6 && $self->{ Y }->[ $last ] == $self->{ Y }->[0] );
508             }
509              
510             sub close {
511 1     1 1 2 my $self = shift;
512              
513 1 50       4 print STDERR "close()\n" if $DEBUG;
514              
515 1 50       3 unless ($self->closed()) {
516 1         2 push(@{ $self->{ X } }, $self->{ X }->[0]);
  1         3  
517 1         1 push(@{ $self->{ Y } }, $self->{ Y }->[0]);
  1         3  
518 1         2 $self->_changed_size(); # clear memoised size values
519             }
520             }
521              
522              
523             #------------------------------------------------------------------------
524             # a_at_b(\@a, \@b, $b)
525             # y_at_x($x)
526             # x_at_y($y)
527             #
528             # Returns a reference to an ordered list of Y/X values where the profile
529             # crosses the specified point on the X/Y axis. The profile must be closed
530             # to ensure that an even number of crossing points are returned.
531             #------------------------------------------------------------------------
532              
533             sub a_at_b {
534 0     0 0 0 my ($self, $aval, $bval, $b) = @_;
535 0         0 my ($a, $a1, $a2, $da, $b1, $b2, $db, $ratio, $tmp);
536 0         0 my $last = $#$aval;
537 0         0 my @aset = ();
538              
539 0 0       0 return $self->error("profile must be closed")
540             unless $self->closed();
541              
542 0         0 for (my $i = 0; $i < $last; $i++) {
543 0         0 ($a1, $a2) = @$aval[ $i, $i+1 ];
544 0         0 ($b1, $b2) = @$bval[ $i, $i+1 ];
545              
546             # swap values if necessary to ensure $b1 < $b2
547 0 0       0 if ($b1 > $b2) {
548 0         0 $tmp = $a1; $a1 = $a2; $a2 = $tmp;
  0         0  
  0         0  
549 0         0 $tmp = $b1; $b1 = $b2; $b2 = $tmp;
  0         0  
  0         0  
550             }
551              
552 0 0 0     0 if ($b >= $b1 && $b < $b2) {
553 0         0 $da = $a2 - $a1;
554 0         0 $db = $b2 - $b1;
555 0         0 $ratio = ($b - $b1) / $db;
556 0         0 $a = $a1 + $da * $ratio;
557 0 0       0 print STDERR "$b in range [ $b1 -> $b2 ], [ $a1 -> $a2 ] => $a\n"
558             if $DEBUG;
559 0         0 push(@aset, $a);
560             }
561             }
562 0         0 return \@aset;
563             }
564              
565             sub y_at_x {
566 0     0 1 0 my ($self, $x) = @_;
567 0 0       0 print STDERR "y_at_x($x)\n" if $DEBUG;
568 0         0 $self->a_at_b($self->{ Y }, $self->{ X }, $x);
569             }
570              
571             sub x_at_y {
572 0     0 1 0 my ($self, $y) = @_;
573 0 0       0 print STDERR "x_at_y($y)\n" if $DEBUG;
574 0         0 $self->a_at_b($self->{ X }, $self->{ Y }, $y);
575             }
576              
577            
578            
579              
580             #------------------------------------------------------------------------
581             # about()
582             #
583             # Returns a string containing information about the profile.
584             #------------------------------------------------------------------------
585              
586             sub about {
587 0     0 0 0 my $self = shift;
588 0         0 my $debug = $self->{ DEBUG };
589 0         0 local $" = ', ';
590              
591 0         0 my $output = "Profile $self->{ NAME } ($self->{ FILENAME })\n";
592 0         0 $output .= sprintf("length: %8.3f height: %8.3f\n",
593             $self->length(), $self->height());
594              
595 0         0 my $n = scalar @{ $self->{ X } };
  0         0  
596              
597 0         0 $output .= "$n co-ordinate pairs:\n"
598             . " n X Y\n"
599             . '-' x 38 . "\n";
600              
601 0         0 foreach (my $i = 0; $i < $n; $i++) {
602             $output .= sprintf(" %3d: %14.8f %14.8f\n", $i,
603             $self->{ X }->[ $i ],
604 0         0 $self->{ Y }->[ $i ]);
605             }
606              
607 0         0 return $output;
608             }
609              
610              
611             #------------------------------------------------------------------------
612             # output()
613             #
614             # Returns a text string representing the profile definition in the form:
615             #
616             # Profile Name
617             # x1 y1
618             # x2 y2
619             # ...
620             # xn yn
621             #
622             #------------------------------------------------------------------------
623              
624             sub output {
625 0     0 0 0 my $self = shift;
626 0         0 my $text = $self->{ NAME } . "\n";
627 0         0 my ($x, $y) = @$self{ qw( X Y ) };
628 0         0 my $n = scalar @$x;
629              
630 0         0 for(my $i = 0; $i < $n; $i++) {
631 0         0 $text .= sprintf("%9.7f %9.7f\n", $x->[$i], $y->[$i]);
632             }
633 0         0 return $text;
634             }
635              
636             sub postscript {
637 0     0 0 0 my $self = shift;
638 0   0     0 my $vars = shift || { };
639            
640 0         0 require Kite::PScript::Defs;
641 0         0 require Template;
642              
643 0         0 my $doc = $self->ps_template();
644 0         0 my $template = Template->new( POST_CHOMP => 1);
645 0         0 $vars->{ defs } = Kite::PScript::Defs->new();
646 0         0 $vars->{ self } = $self;
647 0 0       0 $vars->{ border } = 5 unless defined $vars->{ border };
648 0   0     0 $vars->{ rotate } ||= 0;
649 0   0     0 $vars->{ translate } ||= [0,0];
650              
651 0         0 my $out = '';
652 0 0       0 $template->process(\$doc, $vars, \$out)
653             || return $self->error($template->error());
654 0         0 return $out;
655             }
656              
657             #------------------------------------------------------------------------
658             # _changed_size()
659             #
660             # Private method called when the profile size changes. Clears any
661             # memoised values for LENGTH HEIGHT MINX MAXX MINY and MAXY
662             #------------------------------------------------------------------------
663              
664             sub _changed_size {
665 3     3   5 my $self = shift;
666 3         12 @$self{ qw( SIZE LENGTH HEIGHT MINX MAXX MINY MAXY ) } = (undef) x 7;
667             }
668            
669              
670             #------------------------------------------------------------------------
671              
672             sub ps_template {
673 0     0 0   return <<'EOF';
674             [% USE fix = format('%.2f') -%]
675             %!PS-Adobe-3.0
676             [% IF name %]
677             %%Title: [% name %]
678             [% END %]
679             %%EndComments
680              
681             [% defs.mm %]
682             [% defs.lines %]
683             [% defs.cross %]
684             [% defs.dot %]
685             [% defs.circle %]
686             [% defs.crop %]
687             [% defs.outline %]
688              
689             /border [% border %] mm def
690             [% defs.clip +%]
691             [% regmarks ? defs.reg : defs.noreg +%]
692             [% defs.tiles +%]
693             [% defs.tilemap +%]
694              
695             /Times-Roman findfont dup dup
696             24 scalefont /big-text exch def
697             14 scalefont /mid-text exch def
698             10 scalefont /min-text exch def
699              
700             % define profile
701             /tileimage {
702             gsave
703             [% IF outline %]
704             tilepath [% outline %] mm outline
705             [% END %]
706              
707             [% rotate %] rotate
708             [% translate.0 %] mm [% translate.1 %] mm translate
709              
710             newpath linedashed
711             [% self.min_x - 5 %] mm 0 mm moveto
712             [% self.max_x + 5 %] mm 0 lineto
713             [% self.min_x %] mm -5 mm moveto [% self.min_x %] mm 5 mm lineto
714             [% self.max_x %] mm -5 mm moveto [% self.max_x %] mm 5 mm lineto
715             stroke
716              
717             [% x = self.x
718             y = self.y
719             %]
720             newpath linenormal
721             [% FOREACH i = [0 .. x.max ] %]
722             [% fix(x.$i) %] mm [% fix(y.$i) %] mm
723             [%- loop.first ? ' moveto' : ' lineto' +%]
724             [% END %]
725             stroke
726             grestore
727             } def
728              
729             /tilepath {
730             0 0 translate
731             [% rotate %] rotate
732             [% translate.0 %] mm [% translate.1 %] mm translate
733             newpath
734             [% x = self.x
735             y = self.y
736             %]
737             [% FOREACH i = [0 .. x.max ] %]
738             [% fix(x.$i) %] mm [% fix(y.$i) %] mm
739             [%- loop.first ? ' moveto' : ' lineto' +%]
740             [% END %]
741             [% translate.0 %] neg mm [% translate.1 %] mm neg translate
742             [% rotate %] neg rotate
743             } def
744              
745              
746             /tilepage {
747             regmarks
748             /x border 3 mm add def
749             /y border 3 mm add def
750             [% IF map %]
751             tilemap
752             [% END %]
753             } def
754              
755             tilepath tiles
756             [% defs.dotiles %]
757              
758             EOF
759             }
760              
761              
762              
763             1;
764              
765             __END__