File Coverage

lib/Postscript/HTML/Map.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             package Postscript::HTML::Map;
2              
3             # Assumes bounding box
4              
5 2     2   3101 use strict;
  2         4  
  2         82  
6 2     2   13 use warnings;
  2         5  
  2         219  
7              
8 2     2   24 use base qw/Class::Accessor::Fast/;
  2         3  
  2         2419  
9              
10             use Data::Dumper;
11             use HTML::Element;
12             use Math::Bezier;
13              
14             __PACKAGE__->mk_accessors(qw/postscript map width height current_x current_y args_stack polygon
15             comments html_handler scale_x scale_y/);
16              
17             our $VERSION = '1.0001';
18              
19             my %handlers = (
20             BeginEPSF => undef,
21             stroke => undef,
22             arc => 'add_arc',
23             newpath => 'create_newpath',
24             moveto => 'perform_move',
25             lineto => 'add_line',
26             curveto => 'add_curve',
27             closepath => 'perform_closepath', # Will need to do more later
28             scale => 'adjust_scaling', # Will need to do more later
29             );
30             my $handler_regex = join '|', keys %handlers;
31              
32             sub render {
33             my ($self) = @_;
34              
35             unless ($self->postscript){
36             print STDERR "No postscript provided\n";
37             return 0;
38             }
39              
40             unless (-r $self->postscript){
41             printf STDERR "Postscript file %s not readable\n", $self->postscript;
42             return 0;
43             }
44              
45             # Ensure we have fresh html output
46             $self->map(HTML::Element->new(
47             'map'
48             ));
49              
50             my @postscript;
51             do {
52             open(my $ps_file, "<".$self->postscript);
53             chomp(@postscript = <$ps_file>);
54             close $ps_file;
55              
56             # Find the Comment that states the bounding box.
57             # Working this out from the postscript would be extremely difficult,
58             # Due to needing to process all the curves first.
59             my ($bounding_definition) = map {
60             /^%.*BoundingBox:\s*((?:\d+\s*){4})/ ? $1 : ()
61             } @postscript;
62             my (undef, undef, $width, $height) = split /\s+/, $bounding_definition;
63             $self->width($width);
64             $self->height($height);
65             };
66              
67             $self->current_coords(0, 0);
68             $self->args_stack([]);
69             $self->polygon([]);
70             $self->comments([]);
71             $self->scale(1,1);
72              
73             while (@postscript){
74             if ($postscript[0] =~ /^\s*%/){
75             # A comment
76             my ($comment) = (shift @postscript) =~ /^\s*%+(.*)/;
77            
78             $self->comment($comment);
79              
80             next;
81             }
82             unless ($postscript[0] =~ s#^\s*(.*?)\s*($handler_regex)##){
83             # Nothing left on this line that interests us
84             shift @postscript;
85             next;
86             }
87             my ($args, $cmd) = ($1,$2);
88              
89             my $method_name = $handlers{$cmd};
90             next unless $method_name;
91              
92             my $method = $self->can($method_name);
93             next unless $method;
94             $self->$method($cmd, $args);
95             }
96              
97             return $self->map;
98             }
99              
100             sub add_arc {
101             my ($self, $cmd, $args) = @_;
102              
103             my ($x, $y, $radius, $start_angle, $end_angle) = split / +/, $args;
104             if (($start_angle == 0 && $end_angle == 360) ||
105             ($start_angle == 360 && $end_angle == 0)){
106             # Complete circle, use the circle operator
107              
108             $x *= $self->scale_x;
109             $y *= $self->scale_y;
110              
111             # Radius handling goes crazy if scaling isn't equal, so we assume x
112             $radius *= $self->scale_x;
113              
114             my $element = HTML::Element->new('area',
115             shape => 'circle',
116             coords => join(', ', map $_.'px', $x, $self->height - $y, $radius),
117             );
118              
119             if (ref $self->html_handler){
120             $self->html_handler->($self, $element);
121             }
122              
123             $self->map->push_content($element);
124             }
125            
126             return $self;
127             }
128              
129             sub create_newpath {
130             my ($self, $cmd, $args) = @_;
131              
132             if ($args){
133             # Sometimes we get arguments to this, but they're really intended for the next command
134             $self->args_stack_push($args);
135             }
136              
137             $self->polygon([]);
138              
139             return;
140             }
141              
142             sub perform_move {
143             my ($self, $cmd, $args) = @_;
144              
145             if (!$args){
146             # Perhaps there's something in the stack for us?
147             $args = $self->args_stack_pop;
148             }
149              
150             die "moveto without args" unless $args;
151              
152             my ($x, $y) = split /\s+/, $args;
153              
154             $x *= $self->scale_x;
155             $y *= $self->scale_y;
156              
157             $self->current_coords($x, $y);
158              
159             return;
160             }
161              
162             sub perform_closepath {
163             my ($self) = @_;
164              
165             if (@{$self->polygon}){
166              
167             $self->map->push_content(['area', {
168             shape => 'poly',
169             coords => join(", ", map $_.'px', @{$self->polygon}),
170             href => 'javascript:alert("'.$self->comment.'");',
171             }]);
172              
173             $self->polygon([]);
174             }
175              
176             return;
177             }
178              
179             sub add_curve {
180             my ($self, $cmd, $args) = @_;
181              
182             die "No args to curveto" unless $args;
183              
184             # Bezier Curve
185             my ($control1_x, $control1_y,
186             $control2_x, $control2_y,
187             $end_x, $end_y) = split /\s+/, $args;
188              
189             $control1_x *= $self->scale_x;
190             $control1_y *= $self->scale_y;
191             $control2_x *= $self->scale_x;
192             $control2_y *= $self->scale_y;
193             $end_x *= $self->scale_x;
194             $end_y *= $self->scale_y;
195              
196             my $bezier = Math::Bezier->new(
197             $self->current_coords,
198             $control1_x, $control1_y,
199             $control2_x, $control2_y,
200             $end_x, $end_y
201             );
202              
203             $self->add_to_polygon(
204             $bezier->curve(20)
205             );
206              
207             $self->current_coords($end_x, $end_y);
208              
209             return;
210             }
211              
212             sub add_line {
213             my ($self, $cmd, $args) = @_;
214              
215             die "No co-ordinates for line" unless $args;
216              
217             # Firstly, generate the line in the polygon
218             $self->add_to_polygon($self->current_coords);
219              
220             my ($x, $y) = split /\s+/, $args;
221              
222             $x *= $self->scale_x;
223             $y *= $self->scale_y;
224              
225             $self->add_to_polygon($x, $y);
226            
227             $self->current_coords($x, $y);
228              
229             return;
230             }
231              
232             sub adjust_scaling {
233             my ($self, $cmd, $args) = @_;
234              
235             die "No scaling provided" unless $args;
236              
237             my ($x, $y) = split /\s+/, $args;
238              
239             $self->scale($x, $y);
240              
241             $self->height( $self->height * $self->scale_y );
242             $self->width( $self->width * $self->scale_x );
243              
244             return;
245             }
246              
247             sub current_coords {
248             my ($self, $x, $y) = @_;
249              
250             if ($x && $y){
251             $self->current_x($x);
252             $self->current_y($y);
253             }
254              
255             return ($self->current_x, $self->current_y);
256             }
257              
258             sub args_stack_push {
259             my ($self, $args) = @_;
260              
261             push @{$self->args_stack}, $args;
262              
263             return;
264             }
265              
266             sub args_stack_pop {
267             my ($self) = @_;
268              
269             return pop @{$self->args_stack};
270             }
271              
272             sub add_to_polygon {
273             my ($self, @coords) = @_;
274              
275             die "Uneven number of co-ordinates adding to polygon" if scalar(@coords) % 2;
276              
277             push @{$self->polygon}, @coords;
278              
279             return;
280             }
281              
282             sub comment {
283             my ($self, $comment) = @_;
284              
285             if ($comment){
286             push @{$self->comments}, $comment;
287             }
288            
289             return $self->comments->[-1];
290             }
291              
292             sub scale {
293             my ($self, $x, $y) = @_;
294              
295             if ($x && $y){
296             $self->scale_x($x);
297             $self->scale_y($y);
298             }
299              
300             return ($self->scale_x, $self->scale_y);
301             }
302              
303             1;
304              
305             __END__