File Coverage

blib/lib/Text/Graphics.pm
Criterion Covered Total %
statement 209 269 77.7
branch 23 50 46.0
condition 12 28 42.8
subroutine 38 51 74.5
pod 0 2 0.0
total 282 400 70.5


line stmt bran cond sub pod time code
1             ## ====================================================================
2             ## Copyright (C) 1998 Stephen Farrell
3             ##
4             ## All rights reserved. This program is free software; you can
5             ## redistribute it and/or modify it under the same terms as Perl
6             ## itself.
7             ##
8             ## ====================================================================
9             ##
10             ## Authors: Stephen Farrell & Jeremy Mayes (c) 1998
11             ## Description: A Text::Graphics rendering toolkit
12             ## RCS $Id: Graphics.pm,v 1.11 1998/06/23 01:00:16 sfarrell Exp $
13             ##
14              
15             package Text::Graphics;
16             $Text::Graphics::VERSION = 1.0001;
17              
18 1     1   1000 use Exporter;
  1         2  
  1         128  
19             @ISA = qw(Exporter);
20             @EXPORT = qw(max min);
21              
22             sub max {
23 8     8 0 11 my $a = shift; my $b = shift;
  8         9  
24 8 50       19 return ($a > $b) ? $a : $b;
25             }
26              
27             sub min {
28 12     12 0 14 my $a = shift; my $b = shift;
  12         11  
29 12 50       44 return ($a < $b) ? $a : $b;
30             }
31              
32              
33              
34             ## ====================================================================
35 1     1   6 use strict;
  1         2  
  1         746  
36             ##
37             ## virtual baseclass (no constructor)
38             ##
39             package Text::Graphics::Component;
40             Text::Graphics->import();
41              
42             ##
43             ## add ( panel, offx, offy )
44             ##
45             ## add a child panel at the given offsets.
46             ##
47             sub add {
48 2     2   38 my $this = shift;
49 2         5 my $child = shift;
50 2         4 my $offx = shift;
51 2         4 my $offy = shift;
52 2 100       996 $child->{offx} = ($offx > 0) ? $offx : 0;
53 2 100       10 $child->{offy} = ($offy > 0) ? $offy : 0;
54 2         5 $child->{parent} = $this;
55            
56 2         3 push @{ $this->{children} }, $child;
  2         4  
57 2         5 return $this;
58             }
59              
60             ##
61             ## setBackground ( char )
62             ##
63             ## set the background to the specificied char. this works with
64             ## _drawBackground() in base class.... if you override _drawBackground
65             ## then you probably want to override this as well. a background of
66             ## undef (or "") creates a transparent panel. if you want opaque,
67             ## set the background to " ".
68             ##
69 2     2   20 sub setBackground { shift->{bg} = pop }
70              
71             ##
72             ## _drawBackground ( gc )
73             ##
74             ## draw the background. subclasses can override this, but probably
75             ## won't need to.
76             ##
77             sub _drawBackground {
78 3     3   4 my $this = shift;
79 3         13 my $gc = shift;
80            
81 3 100       12 if ($this->{bg} ne undef) {
82 2         9 $gc->fillRect($this->{bg}, 0, 0, $this->{width}, $this->{height});
83             }
84             }
85              
86             ##
87             ## _drawSelf ( gc )
88             ##
89             ## override this method when subclassing to do whatever it is your
90             ## subclass does. when you override, make sure to call
91             ## $this->SUPER::_drawSelf() to make sure that your children are drawn
92             ## as well.
93             ##
94             sub _drawSelf {
95 3     3   5 my $this = shift;
96 3         4 my $gc = shift;
97            
98 3         4 foreach my $child (@{ $this->{children} }) {
  3         9  
99 2         81 my $gss = Text::Graphics::GraphicsContextSaveState->new($gc);
100 2         17 $gc->setClippingRegion($child->_getBoundaries());
101 2         24 $child->_drawBackground($gc);
102 2         7 $child->_drawSelf($gc);
103             }
104             }
105              
106              
107              
108             ##
109             ## _getBoundaries()
110             ##
111             ## calculate the boundaries so as to be contained in parent's
112             ## boundaries. this is called during rendering to set the clipping
113             ## region for the gc.
114             ##
115             sub _getBoundaries {
116 2     2   3 my $this = shift;
117            
118 2         3 my $p = $this->{parent};
119 2 50       6 ($this->{offx} < 0) and $this->{offx} = 0;
120 2 50       6 ($this->{offy} < 0) and $this->{offy} = 0;
121            
122 2         8 return ($this->{offx},
123             $this->{offy},
124             min ($this->{offx} + $this->{width}, $p->{width}),
125             min ($this->{offy} + $this->{height}, $p->{height}));
126             }
127              
128              
129              
130             ##
131             ## getSize ()
132             ##
133             ## this is a hook for a layout engine to get information about the
134             ## size that the panel wants to be.
135             ##
136             sub getSize {
137 0     0   0 my $this = shift;
138 0         0 return ($this->{width}, $this->{height});
139             }
140              
141             ##
142             ## getOffset ()
143             ##
144             ## this is a hook for a layout engine to get information about the
145             ## offset of the panel
146             ##
147             sub getOffset {
148 0     0   0 my $this = shift;
149 0         0 return ($this->{offx}, $this->{offy});
150             }
151              
152             ##
153             ## setOffset ( offx, offy )
154             ##
155             ## hook for layout engine to redo the offset after the panel has been
156             ## created. NOTE that you can pass in either value as null and it
157             ## will be unchanged
158             ##
159             sub setOffset {
160 0     0   0 my $this = shift;
161 0         0 my $offx = shift;
162 0         0 my $offy = shift;
163 0 0       0 if (defined $offx) {
164 0         0 $this->{offx} = $offx;
165             }
166 0 0       0 if (defined $offy) {
167 0         0 $this->{offy} = $offy;
168             }
169             }
170              
171             ##
172             ## setSize ( width, height )
173             ##
174             ## hook for layout engine to redo the size after the panel has been
175             ## created. NOTE that you can pass in either value as null and it will
176             ## be unchanged
177             ##
178             sub setSize {
179 0     0   0 my $this = shift;
180 0         0 my $width = shift;
181 0         0 my $height = shift;
182 0 0       0 if (defined $width) {
183 0         0 $this->{width} = $width;
184             }
185 0 0       0 if (defined $height) {
186 0         0 $this->{height} = $height;
187             }
188             }
189              
190              
191             ##
192             ## getChildren ()
193             ##
194             ## this is a hook for a layout engine to access children of this panel
195             ##
196             sub getChildren {
197 0     0   0 my $this = shift;
198 0 0       0 return (@{ $this->{children} } > 0) ? @{ $this->{children} } : undef;
  0         0  
  0         0  
199             }
200              
201              
202             ## ====================================================================
203             package Text::Graphics::Page;
204 1     1   7 use vars qw ( @ISA );
  1         10  
  1         388  
205             @ISA = qw(Text::Graphics::Component);
206              
207             ##
208             ## new(width, height)
209             ##
210             sub new {
211 1     1   81 my $this = {};
212 1         4 bless $this, shift;
213 1   50     14 $this->{width} = shift || 0;
214 1   50     5 $this->{height} = shift || 0;
215 1         3 $this->{children} = [];
216 1         4 return $this;
217             }
218              
219             sub _getBoundaries {
220 0     0   0 my $this = shift;
221 0         0 return (0, 0, $this->{width}, $this->{height});
222             }
223              
224             ##
225             ## render ([scalar_ref])
226             ##
227             ## this is what is called to cause the page to render itself, not
228             ## _drawSelf(). _drawSelf() should be thought of what is used in
229             ## subclasses to get their specific behavior. render() should be
230             ## thought of as something called externally to cause the whole
231             ## heirarchy to display.
232             ##
233             ## If a scalar ref is provided, then it renders into it; otherwise it
234             ## renders to STDOUT.
235             ##
236             sub render {
237 1     1   6 my $this = shift;
238 1         1 my $scalar_ref = shift;
239 1         9 my $gc = Text::Graphics::GraphicsContext->new($this->{width},
240             $this->{height});
241            
242 1         8 $this->_drawBackground($gc);
243 1         8 $this->_drawSelf($gc);
244            
245 1 50       4 if ($scalar_ref) {
246 1         4 $gc->renderToScalarRef($scalar_ref);
247             }
248             else {
249 0         0 $gc->renderToSTDOUT();
250             }
251             }
252              
253              
254             ## ====================================================================
255             package Text::Graphics::Panel;
256 1     1   7 use vars qw ( @ISA );
  1         2  
  1         57  
257             @ISA = qw(Text::Graphics::Component);
258 1     1   7 use Carp;
  1         1  
  1         231  
259              
260             ##
261             ## new(width, height)
262             ##
263             sub new {
264 1     1   14 my $this = {};
265 1         3 bless $this, shift;
266 1   50     10 $this->{width} = shift || 0;
267 1   50     4 $this->{height} = shift || 0;
268 1         2 $this->{children} = [];
269 1         4 return $this;
270             }
271              
272              
273              
274             ## ====================================================================
275             package Text::Graphics::BorderedPanel;
276 1     1   13 use vars qw ( @ISA );
  1         2  
  1         121  
277             @ISA = qw(Text::Graphics::Panel);
278              
279             sub _drawSelf {
280 1     1   2 my $this = shift;
281 1         2 my $gc = shift;
282            
283 1         10 $this->SUPER::_drawSelf($gc);
284            
285             ##
286             ## normally you draw self and then draw children.. however borders
287             ## are special b/c we want to draw them after the children have been
288             ## drawn. therefor, i need to reset the clipping region for the gc
289             ## explicitely
290             ##
291 1         5 $gc->drawBorder(0, 0, $this->{width}, $this->{height});
292             }
293              
294             ## ====================================================================
295             package Text::Graphics::TextPanel;
296 1     1   5 use vars qw ( @ISA );
  1         2  
  1         633  
297             @ISA = qw(Text::Graphics::Panel);
298             Text::Graphics->import();
299              
300             ##
301             ## new(text, width, height)
302             ##
303             ## defaults to opaque background. use setBackground(undef) to make
304             ## transparent
305             ##
306             sub new {
307 1     1   17 my $this = {};
308 1         3 bless $this, shift;
309 1         11 $this->{text} = shift;
310 1         3 $this->{width} = shift;
311 1         3 $this->{height} = shift;
312 1         7 return $this;
313             }
314              
315             sub getSize {
316 0     0   0 my $this = shift;
317            
318             ##
319             ## analyze text string to figure out how big it is... don't return a
320             ## width or height less than that provided
321             ##
322            
323 0         0 my $text = $this->{text};
324 0         0 my @lines = split (/\n/, $this->{text});
325 0         0 my $width = max( $this->{width}, max ( map { length($_) } @lines ) +
  0         0  
326             (2 * $this->{h_pad}));
327 0         0 my $height = max( $this->{height}, scalar ( @lines ) + (2 * $this->{v_pad}));
328            
329 0         0 return ($width, $height - 1); # there is always 1 extra padding on bottom
330             }
331              
332             ##
333             ## setPadding ( horizontal padding, vertical padding )
334             ##
335             ## set the padding around the text. this is currently used mainly
336             ## when there is a border around the panel, so that the text is not
337             ## overwritten
338             ##
339             sub setPadding {
340 1     1   3 my $this = shift;
341 1         3 $this->{h_pad}= shift;
342 1         5 $this->{v_pad} = shift;
343             }
344              
345             sub _drawSelf {
346 1     1   2 my $this = shift;
347 1         1 my $gc = shift;
348            
349 1         35 $gc->drawString($this->{text},
350             $this->{h_pad},
351             $this->{v_pad},
352             $this->{width} - (2 * $this->{h_pad}),
353             $this->{height} - (2 * $this->{v_pad}));
354            
355 1         10 $this->SUPER::_drawSelf($gc);
356             }
357              
358              
359              
360             ## ====================================================================
361             package Text::Graphics::FilledTextPanel;
362 1     1   7 use Carp;
  1         2  
  1         138  
363 1     1   1012 use Text::Wrapper;
  1         1316  
  1         31  
364 1     1   6 use vars qw ( @ISA );
  1         2  
  1         708  
365             @ISA = qw(Text::Graphics::TextPanel);
366             Text::Graphics->import();
367              
368             sub doWrap {
369 2     2   3 my $this = shift;
370 2   66     10 my $width = shift || $this->{width};
371            
372             ## warn "doWrap($width) $this->{text}\n";
373            
374 2 100       8 unless ($this->{text_was_wrapped}) {
375             ##
376             ## don't bother with wrap if no spaces ;-)--this is not so much an
377             ## optimization as much as a work-around for the buggy wrapper.
378             ##
379 1 50 33     11 if ($width and $this->{text} =~ /\s/) {
380 1         9 my $w = Text::Wrapper->new(columns=>$width);
381 1         790 $this->{text} =~ s/\n/ /g;
382 1         7 $this->{text} = $w->wrap($this->{text});
383             }
384            
385 1         484 $this->{text_was_wrapped} = 1;
386             }
387             }
388              
389              
390              
391             ##
392             ## ONLY run this if the width is non-zero
393             ##
394             sub setPadding {
395 1     1   2 my $this = shift;
396 1         2 my $h_pad = shift;
397 1         1 my $v_pad = shift;
398            
399 1 50       4 if ($this->{width}) {
400 1         8 $this->doWrap($this->{width} - (2 * $h_pad));
401             }
402            
403 1         2033 $this->SUPER::setPadding($h_pad, $v_pad);
404             }
405              
406              
407             sub setSize {
408 0     0   0 my $this = shift;
409 0         0 my $width = shift;
410 0         0 my $height = shift;
411            
412             ##
413             ## we need to re-wrap iff the width changes
414             ##
415            
416 0 0 0     0 if (($width ne undef) and ($this->{width} != $width)) {
417 0         0 delete $this->{text_was_wrapped};
418             }
419            
420 0         0 $this->SUPER::setSize($width, $height);
421             }
422              
423             sub getSize {
424 0     0   0 my $this = shift;
425            
426 0 0       0 if ($this->{width}) {
427 0         0 $this->doWrap();
428             }
429            
430 0         0 return $this->SUPER::getSize();
431             }
432              
433             sub _drawSelf {
434 1     1   2 my $this = shift;
435 1         2 my $gc = shift;
436            
437 1 50       5 if ($this->{width}) {
438 1         4 $this->doWrap();
439             }
440            
441 1         7 $this->SUPER::_drawSelf($gc);
442             }
443              
444              
445              
446             ## ====================================================================
447             package Text::Graphics::BorderedTextPanel;
448 1     1   7 use vars qw ( @ISA );
  1         1  
  1         183  
449             @ISA = qw(Text::Graphics::TextPanel);
450              
451             sub getSize {
452 0     0   0 my $this = shift;
453            
454 0         0 my ($width, $height) = $this->SUPER::getSize();
455             ##
456             ## for filled text, the deal is that we give a width and ask later
457             ## for the height. so when getSize returns, it, of course, returns
458             ## the width first asked for, but the height is affected by wrapping
459             ## and padding for the border (the 2). for unfilled text, the width
460             ## can also change from what was requested.
461             ##
462 0         0 return ($width, $height + 2 * $this->{h_pad});
463             }
464              
465             sub _drawSelf {
466 0     0   0 my $this = shift;
467 0         0 my $gc = shift;
468            
469 0         0 $this->setPadding(1,1);
470 0         0 $this->SUPER::_drawSelf($gc);
471            
472 0         0 $gc->drawBorder(0, 0, $this->{width}, $this->{height});
473             }
474              
475             ## ====================================================================
476             package Text::Graphics::FilledBorderedTextPanel;
477 1     1   5 use vars qw ( @ISA );
  1         2  
  1         1909  
478             @ISA = qw(Text::Graphics::FilledTextPanel);
479              
480             sub getSize {
481 0     0   0 my $this = shift;
482            
483 0         0 my ($width, $height) = $this->SUPER::getSize();
484             ##
485             ## for filled text, the deal is that we give a width and ask later
486             ## for the height. so when getSize returns, it, of course, returns
487             ## the width first asked for, but the height is affected by wrapping
488             ## and padding for the border (the 2). for unfilled text, the width
489             ## can also change from what was requested.
490             ##
491 0         0 return ($width, $height + 2 * $this->{h_pad});
492             }
493              
494             sub _drawSelf {
495 1     1   3 my $this = shift;
496 1         2 my $gc = shift;
497            
498 1         9 $this->setPadding(1, 1); # FIXME--I don't think I want this hardcoded
499 1         10 $this->SUPER::_drawSelf($gc);
500            
501 1         6 $gc->drawBorder(0, 0, $this->{width}, $this->{height});
502             }
503              
504              
505             ## ====================================================================
506             package Text::Graphics::GraphicsContext;
507             Text::Graphics->import();
508              
509             ##
510             ## new(width, height)
511             ##
512             sub new {
513 1     1   2 my $this = {};
514 1         2 bless $this, shift;
515 1         7 $this->{width} = shift;
516 1         2 $this->{height} = shift;
517 1         3 $this->{charmap} = [];
518 1         2 return $this;
519             }
520              
521              
522             ##
523             ## drawBorder (x, y, width, height)
524             ##
525             sub drawBorder {
526 2     2   4 my $this = shift;
527 2         4 my $startx = $this->{x0} + shift;
528 2         3 my $starty = $this->{y0} + shift;
529 2         3 my $endx = $startx + shift;
530 2         3 my $endy = $starty + shift;
531            
532 2         5 $startx = max ($startx, $this->{x0});
533 2         5 $starty = max ($starty, $this->{y0});
534 2         6 $endx = min ($endx, $this->{x1});
535 2         4 $endy = min ($endy, $this->{y1});
536            
537 2 50 33     19 return if $startx >= $endx or $starty >= $endy;
538            
539 2         5 $this->{charmap}->[$starty]->[$startx] = "+";
540 2         4 $this->{charmap}->[$starty]->[$endx] = "+";
541 2         4 $this->{charmap}->[$endy]->[$startx] = "+";
542 2         4 $this->{charmap}->[$endy]->[$endx] = "+";
543            
544 2         6 foreach my $x ($startx + 1 .. $endx - 1) {
545 33         48 $this->{charmap}->[$starty]->[$x] = "-";
546 33         47 $this->{charmap}->[$endy]->[$x] = "-";
547             }
548 2         5 foreach my $y ($starty + 1 .. $endy - 1) {
549 16         18 $this->{charmap}->[$y]->[$startx] = "|";
550 16         34 $this->{charmap}->[$y]->[$endx] = "|";
551             }
552             }
553              
554             ##
555             ## fillRect (char, x, y, width, height)
556             ##
557             sub fillRect {
558 2     2   8 my $this = shift;
559 2   50     9 my $char = substr(shift, 0, 1) || return;
560 2         4 my $startx = $this->{x0} + shift;
561 2         4 my $starty = $this->{y0} + shift;
562 2         3 my $endx = $startx + shift;
563 2         2 my $endy = $starty + shift;
564            
565 2         6 $startx = max ($startx, $this->{x0});
566 2         4 $starty = max ($starty, $this->{y0});
567 2         4 $endx = min ($endx, $this->{x1});
568 2         4 $endy = min ($endy, $this->{y1});
569            
570 2 50 33     11 return if $startx >= $endx or $starty >= $endy;
571            
572 2         4 foreach my $y ($starty .. $endy) {
573 20         27 foreach my $x ($startx .. $endx) {
574 375         723 $this->{charmap}->[$y]->[$x] = $char;
575             }
576             }
577             }
578              
579              
580             ##
581             ## drawString (text, x, y)
582             ##
583             sub drawString {
584 1     1   2 my $this = shift;
585 1         2 my $text = shift;
586 1         2 my $startx = $this->{x0} + shift;
587 1         3 my $starty = $this->{y0} + shift;
588            
589 1         3 $text =~ s/\t/ /gm; # untabify
590            
591 1         53 my @text_array = split(//, $text);
592            
593            
594 1         8 my ($x, $y) = ($startx, $starty);
595 1         2 CHAR: foreach my $c (@text_array) {
596 105 100       156 if ($c eq "\n") {
597 6         8 $y++;
598 6         20 $x = $startx;
599             }
600             else {
601 99 100 66     546 next CHAR if ($x > $this->{x1}) or ($y > $this->{y1});
602 83         234 $this->{charmap}->[$y]->[$x++] = $c
603             }
604             }
605             }
606              
607              
608             sub setClippingRegion {
609 2     2   4 my $this = shift;
610 2         4 $this->{x0} += shift;
611 2         3 $this->{y0} += shift;
612 2         3 $this->{x1} += shift;
613 2         4 $this->{y1} += shift;
614             }
615              
616             sub renderToSTDOUT {
617 0     0   0 my $this = shift;
618 0         0 my $c;
619 0         0 foreach my $y (0 .. $this->{height}) {
620 0         0 foreach my $x (0 .. $this->{width}) {
621 0         0 $c = $this->{charmap}->[$y]->[$x];
622 0 0       0 print (defined $c ? $c : " ");
623             }
624 0         0 print "\n";
625             }
626             }
627              
628             sub renderToScalarRef {
629 1     1   2 my $this = shift;
630 1         1 my $scalar_ref = shift;
631 1         2 my $c;
632 1         3 foreach my $y (0 .. $this->{height}) {
633 11         21 foreach my $x (0 .. $this->{width}) {
634 231         518 $c = $this->{charmap}->[$y]->[$x];
635 231 50       582 $$scalar_ref .= (defined $c ? $c : " ");
636             }
637 11         46 $$scalar_ref .= "\n";
638             }
639             }
640              
641             ## ====================================================================
642             package Text::Graphics::GraphicsContextSaveState;
643              
644             sub new {
645 2     2   3 my $this = {};
646 2         6 bless $this, shift;
647 2         8 $this->{gc} = shift;
648 2         5 $this->{x0} = $this->{gc}->{x0};
649 2         4 $this->{y0} = $this->{gc}->{y0};
650 2         4 $this->{x1} = $this->{gc}->{x1};
651 2         4 $this->{y1} = $this->{gc}->{y1};
652 2         4 return $this;
653             }
654              
655             sub DESTROY {
656 2     2   3 my $this = shift;
657 2         5 $this->{gc}->{x0} = $this->{x0};
658 2         4 $this->{gc}->{y0} = $this->{y0};
659 2         3 $this->{gc}->{x1} = $this->{x1};
660 2         10 $this->{gc}->{y1} = $this->{y1};
661             }
662              
663             1;
664              
665             __END__