| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Acme::TextLayout; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 2 |  |  | 2 |  | 61359 | use warnings; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 72 |  | 
| 4 | 2 |  |  | 2 |  | 12 | use strict; | 
|  | 2 |  |  |  |  | 6 |  | 
|  | 2 |  |  |  |  | 70 |  | 
| 5 | 2 |  |  | 2 |  | 1761 | use Perl6::Attributes; | 
|  | 2 |  |  |  |  | 74337 |  | 
|  | 2 |  |  |  |  | 11 |  | 
| 6 | 2 |  |  | 2 |  | 12284 | use FileHandle; | 
|  | 2 |  |  |  |  | 8823 |  | 
|  | 2 |  |  |  |  | 12 |  | 
| 7 | 2 |  |  | 2 |  | 2022 | use Data::Dumper; | 
|  | 2 |  |  |  |  | 8553 |  | 
|  | 2 |  |  |  |  | 7009 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | =head1 NAME | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | Acme::TextLayout - Layout things in a grid, as described textually | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | =head1 VERSION | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | Version 0.01 | 
| 17 |  |  |  |  |  |  |  | 
| 18 |  |  |  |  |  |  | =cut | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | our $VERSION = '0.01'; | 
| 21 |  |  |  |  |  |  |  | 
| 22 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | $tl = Acme::TextLayout->new; | 
| 25 |  |  |  |  |  |  | $tl->instantiate(text => $pattern); | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | For a GUI, controlling layout (especially on resize) can be | 
| 30 |  |  |  |  |  |  | difficult, especially if your layout is complex. When looking | 
| 31 |  |  |  |  |  |  | at a GUI, I came to the realization that I could express the | 
| 32 |  |  |  |  |  |  | layout nicely like this: | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | AAAAAAAAAAAAAAAA | 
| 35 |  |  |  |  |  |  | BBBBxxxxxxxxxxxx | 
| 36 |  |  |  |  |  |  | BBBBxxxxxxxxxxxx | 
| 37 |  |  |  |  |  |  | DDDDDDDDDDDDDDDD | 
| 38 |  |  |  |  |  |  | DDDDDDDDDDDDDDDD | 
| 39 |  |  |  |  |  |  | DDDDDDDDDDDDDDDD | 
| 40 |  |  |  |  |  |  | %%%%%%%%%%%%%GGG | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | Where each group of contiguous, like characters specifies a screen | 
| 43 |  |  |  |  |  |  | region. | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | B: space is not legal. Nor should you use "-", trust | 
| 46 |  |  |  |  |  |  | me. A space (" ") will cause you to die, but a "-" is accepted, | 
| 47 |  |  |  |  |  |  | but is used by other modules for other things. BEWARE! | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | To me, this gives an easy-to-grasp pictorial of the GUI | 
| 50 |  |  |  |  |  |  | layout, as long as one notes WTF the letters and symbols represent. | 
| 51 |  |  |  |  |  |  | The only caveat is that the collection of like characters/symbols | 
| 52 |  |  |  |  |  |  | making the pattern must be adjacent, and must be rectangular. And | 
| 53 |  |  |  |  |  |  | the overall pattern must be rectangular. | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | Note that this textual arrangement can be as big as you want. | 
| 56 |  |  |  |  |  |  | It's all relative. Although it might not look like it on | 
| 57 |  |  |  |  |  |  | the screen in your editor of choice, all spacing is assummed to | 
| 58 |  |  |  |  |  |  | be the same in X and Y. Thus, the aspect ratio of the above | 
| 59 |  |  |  |  |  |  | pattern is 16/7 (width/height). | 
| 60 |  |  |  |  |  |  |  | 
| 61 |  |  |  |  |  |  | To be useful for a GUI, one must be able to map this goofy space | 
| 62 |  |  |  |  |  |  | into screen coordinates. That's what the B function is | 
| 63 |  |  |  |  |  |  | for (see below). | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | Now, I know what you must be thinking: is this guy nuts? Why not | 
| 66 |  |  |  |  |  |  | use brand-X fancy GUI layout tool? Well, the fact is that those | 
| 67 |  |  |  |  |  |  | are nice and easy for the initial layout, but they generally generate | 
| 68 |  |  |  |  |  |  | code with precise XY coordinates in them, which makes resizing almost | 
| 69 |  |  |  |  |  |  | impossible. | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | The idea here is that we use the above textual layout to specify | 
| 72 |  |  |  |  |  |  | all the relative positions of things, then map this to a real | 
| 73 |  |  |  |  |  |  | coordinate system, preserving the spatial relativity and size | 
| 74 |  |  |  |  |  |  | associations. | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | I wrote this for use in a GUI application, but figured it might have | 
| 77 |  |  |  |  |  |  | use elsewhere. Hence, this class. If you find a novel use for it, | 
| 78 |  |  |  |  |  |  | please let me know what it is (email address in this document). | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | =head1 METHODS | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | =cut | 
| 84 |  |  |  |  |  |  |  | 
| 85 |  |  |  |  |  |  | =head2 B | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | $tl = Acme::TextLayout->new([%opts]); | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | Create an instance of this class. See B to do anything useful. | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | =cut | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | sub new { | 
| 94 | 9 |  |  | 9 | 1 | 1402 | my $class = shift; | 
| 95 | 9 |  |  |  |  | 23 | my %opts = @_; | 
| 96 | 9 |  |  |  |  | 20 | my $self = \%opts; | 
| 97 | 9 |  |  |  |  | 32 | bless $self, $class; | 
| 98 | 9 |  |  |  |  | 31 | $.Class = $class; | 
| 99 | 9 |  |  |  |  | 38 | return $self; | 
| 100 |  |  |  |  |  |  | } | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | =head2 B | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | $tl->instantiate(text => ??); | 
| 105 |  |  |  |  |  |  | -or- | 
| 106 |  |  |  |  |  |  | $tl->instantiate(file => ??); | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | Specify the textual layout pattern we are interested in, either | 
| 109 |  |  |  |  |  |  | from a text string or a file. | 
| 110 |  |  |  |  |  |  |  | 
| 111 |  |  |  |  |  |  | Returns undef if something wrong with your input. | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | =cut | 
| 114 |  |  |  |  |  |  |  | 
| 115 |  |  |  |  |  |  | sub instantiate { | 
| 116 | 12 |  |  | 12 | 1 | 1005 | my ($self, %opts) = @_; | 
| 117 | 12 |  |  |  |  | 30 | my $file = $opts{file}; | 
| 118 | 12 |  |  |  |  | 25 | my $text = $opts{text}; | 
| 119 |  |  |  |  |  |  |  | 
| 120 |  |  |  |  |  |  | # reset state on new instantiation | 
| 121 | 12 |  |  |  |  | 33 | $.textRef = []; | 
| 122 | 12 |  |  |  |  | 30 | $.Ranges  = {}; | 
| 123 | 12 |  |  |  |  | 34 | $.widest = undef; | 
| 124 | 12 |  |  |  |  | 25 | $.chars  = {}; | 
| 125 | 12 |  |  |  |  | 54 | $.Above = $.Below = $.Left = $.Right = undef; | 
| 126 |  |  |  |  |  |  |  | 
| 127 | 12 | 100 |  |  |  | 52 | if (defined $file) { | 
|  |  | 50 |  |  |  |  |  | 
| 128 | 1 |  |  |  |  | 11 | my $fh = FileHandle->new($file); | 
| 129 | 1 | 50 |  |  |  | 111 | return unless defined $fh; | 
| 130 | 1 |  |  |  |  | 35 | my @text = <$fh>; | 
| 131 | 1 |  |  |  |  | 7 | $fh->close; | 
| 132 | 1 |  |  |  |  | 42 | chomp foreach @text; | 
| 133 | 1 |  |  |  |  | 7 | s/^\s+// foreach @text; | 
| 134 | 1 |  |  |  |  | 4 | $text = [ @text ]; | 
| 135 | 1 |  |  |  |  | 3 | ./_widest(\@text); | 
| 136 |  |  |  |  |  |  | } | 
| 137 |  |  |  |  |  |  | elsif (defined $text) { | 
| 138 | 11 |  |  |  |  | 60 | my @text = split(/\n{1}/, $text); | 
| 139 | 11 |  |  |  |  | 89 | s/^\s+// foreach @text; | 
| 140 | 11 |  |  |  |  | 33 | $text = [ @text ]; | 
| 141 | 11 |  |  |  |  | 32 | ./_widest(\@text); | 
| 142 |  |  |  |  |  |  | } | 
| 143 |  |  |  |  |  |  | else { | 
| 144 | 0 |  |  |  |  | 0 | return undef; | 
| 145 |  |  |  |  |  |  | } | 
| 146 |  |  |  |  |  |  |  | 
| 147 | 12 |  |  |  |  | 43 | ./_whats_in_there($text); | 
| 148 | 12 |  |  |  |  | 70 | ./_widest($text); | 
| 149 | 12 |  |  |  |  | 38 | $.textRef = $text; | 
| 150 | 33 | 100 |  |  |  | 132 | map { | 
| 151 | 12 |  |  |  |  | 52 | return undef unless length($_) == $.widest; | 
| 152 | 12 |  |  |  |  | 27 | } @{$.textRef}; | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 11 |  |  |  |  | 24 | my %Ranges; | 
| 155 | 11 |  |  |  |  | 19 | my %chars = %.chars; | 
|  | 11 |  |  |  |  | 104 |  | 
| 156 | 38 |  |  |  |  | 85 | map { | 
| 157 | 11 |  |  |  |  | 37 | my $C = $_; | 
| 158 | 38 |  |  |  |  | 140 | my @d = ./range($C); | 
| 159 | 37 |  |  |  |  | 215 | $Ranges{$C} = \@d; | 
| 160 |  |  |  |  |  |  | } keys(%chars); | 
| 161 |  |  |  |  |  |  |  | 
| 162 | 10 |  |  |  |  | 35 | $.Ranges = \%Ranges; | 
| 163 | 10 | 100 |  |  |  | 44 | print STDERR "Pattern appears disjoint\n" if ./_disjoint(); | 
| 164 | 10 | 100 |  |  |  | 36 | return undef if ./_disjoint(); | 
| 165 |  |  |  |  |  |  | # signify OK if we got here | 
| 166 | 8 |  |  |  |  | 89 | return 1; | 
| 167 |  |  |  |  |  |  | } | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | # not a complete test, but tests for the obvious | 
| 170 |  |  |  |  |  |  | sub _disjoint { | 
| 171 | 20 |  |  | 20 |  | 35 | my ($self) = @_; | 
| 172 | 20 |  |  |  |  | 29 | my @text = @{$.textRef}; | 
|  | 20 |  |  |  |  | 71 |  | 
| 173 | 20 |  |  |  |  | 56 | my @chars = ./characters(); | 
| 174 | 20 |  |  |  |  | 42 | my $ok = 1; | 
| 175 | 58 |  |  |  |  | 77 | map { | 
| 176 | 20 |  |  |  |  | 32 | my $line = $_; | 
| 177 | 288 |  |  |  |  | 1291 | map { | 
| 178 | 58 |  |  |  |  | 87 | my $n = 0; | 
| 179 | 288 |  |  |  |  | 1678 | my $t = $line; | 
| 180 | 288 |  |  |  |  | 7064 | $n++ while $t =~ s/$_{1,}//; | 
| 181 | 288 | 100 |  |  |  | 1844 | $ok = 0 if $n > 1; | 
| 182 |  |  |  |  |  |  | } @chars; | 
| 183 |  |  |  |  |  |  | } @text; | 
| 184 | 20 |  |  |  |  | 88 | my $width = ./width(); | 
| 185 | 20 |  |  |  |  | 67 | for (my $i=0; $i < $width; $i++) { | 
| 186 | 136 |  |  |  |  | 159 | my @new; | 
| 187 | 136 |  |  |  |  | 1053 | push(@new, substr($_, $i, 1)) foreach @text; | 
| 188 | 136 |  |  |  |  | 447 | my $line = join('', @new); | 
| 189 | 704 |  |  |  |  | 793 | map { | 
| 190 | 136 |  |  |  |  | 200 | my $n = 0; | 
| 191 | 704 |  |  |  |  | 1231 | my $t = $line; | 
| 192 | 704 |  |  |  |  | 11842 | $n++ while $t =~ s/$_{1,}//; | 
| 193 | 704 | 100 |  |  |  | 2994 | $ok = 0 if $n > 1; | 
| 194 |  |  |  |  |  |  | } @chars; | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  |  | 
| 197 | 20 | 100 |  |  |  | 226 | return $ok ? 0 : 1; | 
| 198 |  |  |  |  |  |  | } | 
| 199 |  |  |  |  |  |  |  | 
| 200 |  |  |  |  |  |  | sub _widest { | 
| 201 | 76 |  |  | 76 |  | 130 | my ($self, $textRef) = @_; | 
| 202 | 76 |  |  |  |  | 2608 | my @text = @$textRef; | 
| 203 | 76 |  |  |  |  | 141 | my $widest = length($text[0]); | 
| 204 | 257 |  |  |  |  | 336 | map { | 
| 205 | 76 |  |  |  |  | 123 | my $len = length($_); | 
| 206 | 257 | 50 |  |  |  | 978 | $widest = $len if $len > $widest; | 
| 207 |  |  |  |  |  |  | } @text; | 
| 208 | 76 |  |  |  |  | 4384 | $.widest = $widest; | 
| 209 |  |  |  |  |  |  | } | 
| 210 |  |  |  |  |  |  |  | 
| 211 |  |  |  |  |  |  | sub _height { | 
| 212 | 19 |  |  | 19 |  | 32 | my ($self, $textRef) = @_; | 
| 213 | 19 |  |  |  |  | 45 | my @text = @$textRef; | 
| 214 | 19 |  |  |  |  | 43 | return scalar(@text); | 
| 215 |  |  |  |  |  |  | } | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | # figure out all characters in our pattern | 
| 218 |  |  |  |  |  |  | sub _whats_in_there { | 
| 219 | 12 |  |  | 12 |  | 21 | my ($self, $aref) = @_; | 
| 220 | 12 |  |  |  |  | 34 | my @text = @$aref; | 
| 221 |  |  |  |  |  |  | #print "@text", "\n"; | 
| 222 | 12 |  |  |  |  | 18 | my %chars; | 
| 223 | 3060 |  |  |  |  | 4248 | map { | 
| 224 | 12 |  |  |  |  | 39 | my $c = $_; | 
| 225 | 3060 |  |  |  |  | 5117 | my $C = chr($c); | 
| 226 | 8415 |  |  |  |  | 10410 | map { | 
| 227 | 3060 |  |  |  |  | 6424 | my $n; | 
| 228 | 8415 | 100 |  |  |  | 42033 | $chars{$C} = 1 if $_ =~ /\Q$C\E/; | 
| 229 | 8415 | 50 | 66 |  |  | 34857 | die "$.Class - space unacceptable in pattern\n" | 
|  |  |  | 33 |  |  |  |  | 
| 230 |  |  |  |  |  |  | if $C eq " " && defined $chars{$C} && $chars{$C} == 1; | 
| 231 |  |  |  |  |  |  | } @text; | 
| 232 |  |  |  |  |  |  | } 1 .. 255; | 
| 233 |  |  |  |  |  |  |  | 
| 234 |  |  |  |  |  |  | # preserve our character set | 
| 235 | 12 |  |  |  |  | 85 | $.chars = \%chars; | 
| 236 |  |  |  |  |  |  | } | 
| 237 |  |  |  |  |  |  |  | 
| 238 |  |  |  |  |  |  | sub _right { | 
| 239 | 0 |  |  | 0 |  | 0 | my ($self, $text, $char) = @_; | 
| 240 | 0 |  |  |  |  | 0 | my @text = split(//, $text); | 
| 241 | 0 |  |  |  |  | 0 | my $first; | 
| 242 |  |  |  |  |  |  | my $last; | 
| 243 | 0 | 0 |  |  |  | 0 | if ($text =~ /$char/) { | 
| 244 | 0 |  |  |  |  | 0 | $first = pos($text); | 
| 245 | 0 |  |  |  |  | 0 | $last = rindex $text, $char; | 
| 246 |  |  |  |  |  |  | } | 
| 247 | 0 |  |  |  |  | 0 | return ($first, $last); | 
| 248 |  |  |  |  |  |  | } | 
| 249 |  |  |  |  |  |  |  | 
| 250 |  |  |  |  |  |  | # determine vertical range of a specific character in our pattern | 
| 251 |  |  |  |  |  |  | sub _vrange { | 
| 252 | 48 |  |  | 48 |  | 70 | my ($self, $textRef, $char) = @_; | 
| 253 | 48 |  |  |  |  | 60 | my $top; | 
| 254 |  |  |  |  |  |  | my $bottom; | 
| 255 | 48 |  |  |  |  | 55 | my $n = 0; | 
| 256 | 182 | 100 | 100 |  |  | 1176 | map { | 
| 257 | 48 |  |  |  |  | 87 | $top    = $n if $_ =~ /$char/ && !defined $top; | 
| 258 | 182 | 100 |  |  |  | 989 | $bottom = $n if $_ =~ /$char/; | 
| 259 | 182 |  |  |  |  | 387 | $n++; | 
| 260 |  |  |  |  |  |  | } @$textRef; | 
| 261 | 48 |  |  |  |  | 121 | return ($top, $bottom); | 
| 262 |  |  |  |  |  |  | } | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | sub _first { | 
| 265 | 48 |  |  | 48 |  | 86 | my ($self, $textRef, $char) = @_; | 
| 266 | 48 |  |  |  |  | 119 | my @text = @$textRef; | 
| 267 | 48 |  |  |  |  | 91 | my $first; | 
| 268 | 182 |  |  |  |  | 311 | map { | 
| 269 | 48 |  |  |  |  | 66 | my $n = index $_, $char; | 
| 270 | 182 | 100 |  |  |  | 546 | unless (defined $first) { | 
| 271 | 94 | 100 |  |  |  | 207 | $first = $n if $n >= 0; | 
| 272 |  |  |  |  |  |  | } | 
| 273 | 182 | 100 | 100 |  |  | 925 | if (defined $first && $n >= 0) { | 
| 274 | 80 | 50 |  |  |  | 389 | die "$.Class - char $char appears misaligned\n" | 
| 275 |  |  |  |  |  |  | if $n < $first; | 
| 276 |  |  |  |  |  |  | } | 
| 277 |  |  |  |  |  |  | } @text; | 
| 278 | 48 |  |  |  |  | 132 | return $first; | 
| 279 |  |  |  |  |  |  | } | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | sub _last { | 
| 282 | 48 |  |  | 48 |  | 72 | my ($self, $textRef, $char) = @_; | 
| 283 | 48 |  |  |  |  | 115 | my @text = @$textRef; | 
| 284 | 48 |  |  |  |  | 85 | my $last; | 
| 285 | 182 |  |  |  |  | 303 | map { | 
| 286 | 48 |  |  |  |  | 70 | my $n = rindex $_, $char; | 
| 287 | 182 | 100 |  |  |  | 383 | unless (defined $last) { | 
| 288 | 94 | 100 |  |  |  | 236 | $last = $n if $n >= 0; | 
| 289 |  |  |  |  |  |  | } | 
| 290 | 182 | 100 | 100 |  |  | 803 | if (defined $last && $n >= 0) { | 
| 291 | 80 | 100 |  |  |  | 439 | die "$.Class - char $char appears misaligned\n" | 
| 292 |  |  |  |  |  |  | if $n > $last; | 
| 293 |  |  |  |  |  |  | } | 
| 294 |  |  |  |  |  |  | } @text; | 
| 295 | 47 |  |  |  |  | 144 | return $last; | 
| 296 |  |  |  |  |  |  | } | 
| 297 |  |  |  |  |  |  |  | 
| 298 |  |  |  |  |  |  | sub _range { | 
| 299 | 48 |  |  | 48 |  | 88 | my ($self, $textRef, $char) = @_; | 
| 300 | 48 |  |  |  |  | 114 | my ($top, $bottom) = ./_vrange($textRef, $char); | 
| 301 | 48 |  |  |  |  | 150 | my $left  = ./_first($textRef, $char); | 
| 302 | 48 |  |  |  |  | 106 | my $right = ./_last($textRef, $char); | 
| 303 | 47 |  |  |  |  | 194 | return ($top, $bottom, $left, $right); | 
| 304 |  |  |  |  |  |  | } | 
| 305 |  |  |  |  |  |  |  | 
| 306 |  |  |  |  |  |  | # simple equation to map char ranges to something else | 
| 307 |  |  |  |  |  |  | sub _stretch_offset { | 
| 308 | 12 |  |  | 12 |  | 52 | my ($self, $i1, $i2, $o1, $o2) = @_; | 
| 309 |  |  |  |  |  |  | # handle single characters | 
| 310 | 12 | 50 |  |  |  | 24 | $i2 = $i1 + 1 if $i1 == $i2; | 
| 311 | 12 |  |  |  |  | 20 | my $stretch = ($o2-$o1)/($i2-$i1); | 
| 312 | 12 |  |  |  |  | 17 | my $offset = $o1-($i1*$stretch); | 
| 313 | 12 |  |  |  |  | 36 | return ($stretch, $offset); | 
| 314 |  |  |  |  |  |  | } | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  | =head2 B | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | ($ymin, $ymax, $xmin, $xmax) = $tl->range($char); | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | The range of positions for the specified character. B | 
| 321 |  |  |  |  |  |  | order of arguments> returned. | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | =cut | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | sub range { | 
| 326 | 48 |  |  | 48 | 1 | 765 | my ($self, $char) = @_; | 
| 327 |  |  |  |  |  |  | #return () unless defined $.Ranges{$char}; | 
| 328 | 48 |  |  |  |  | 177 | return ./_range($.textRef, $char); | 
| 329 |  |  |  |  |  |  | } | 
| 330 |  |  |  |  |  |  |  | 
| 331 |  |  |  |  |  |  | =head2 B | 
| 332 |  |  |  |  |  |  |  | 
| 333 |  |  |  |  |  |  | @chars = $tl->characters(); | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | Return list of all of the unique characters in our pattern. | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | =cut | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | sub characters { | 
| 340 | 21 |  |  | 21 | 1 | 329 | my ($self) = @_; | 
| 341 | 21 |  |  |  |  | 25 | return sort keys %.Ranges; | 
|  | 21 |  |  |  |  | 234 |  | 
| 342 |  |  |  |  |  |  | } | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | =head2 B | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | ($width, $height) = $tl->text_size(); | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | Find width & height of our pattern in character units. This may | 
| 349 |  |  |  |  |  |  | be important since the user of a GUI is free to resize in a way | 
| 350 |  |  |  |  |  |  | that messes up the relative aspect ratio as you defined in the | 
| 351 |  |  |  |  |  |  | pattern. And you may want to correct this awful situation. | 
| 352 |  |  |  |  |  |  |  | 
| 353 |  |  |  |  |  |  | =cut | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | sub text_size { | 
| 356 | 0 |  |  | 0 | 1 | 0 | my ($self) = @_; | 
| 357 | 0 |  |  |  |  | 0 | my $h = ./_height($.textRef); | 
| 358 | 0 |  |  |  |  | 0 | my $w = ./_widest($.textRef); | 
| 359 | 0 |  |  |  |  | 0 | return ($w, $h); | 
| 360 |  |  |  |  |  |  | } | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | =head2 B | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | $tl->width(); | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | Return width of our pattern (in # characters). | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | =cut | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | sub width { | 
| 371 | 46 |  |  | 46 | 1 | 76 | my ($self) = @_; | 
| 372 | 46 |  |  |  |  | 128 | my $w = ./_widest($.textRef); | 
| 373 | 46 |  |  |  |  | 136 | return $w; | 
| 374 |  |  |  |  |  |  | } | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | =head2 B | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | $tl->height(); | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | Return height of our pattern (in # characters). | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | =cut | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | sub height { | 
| 385 | 13 |  |  | 13 | 1 | 291 | my ($self) = @_; | 
| 386 | 13 |  |  |  |  | 44 | my $h = ./_height($.textRef); | 
| 387 | 13 |  |  |  |  | 52 | return $h; | 
| 388 |  |  |  |  |  |  | } | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | =head2 B | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | @bbox = $tl->map_range($width, $height, $char); | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | Map the relative position and size of the indicated character ($char) | 
| 395 |  |  |  |  |  |  | region in our pattern to a real XY coordinate space. | 
| 396 |  |  |  |  |  |  |  | 
| 397 |  |  |  |  |  |  | @bbox is the bounding box, returned as ($x1, $y1, $x2, $y2), where | 
| 398 |  |  |  |  |  |  | $x1, $y1 is the upper left corner, and $x2, $y2 is the lower right. | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | Because this was written (primarily) to interface to a GUI, | 
| 401 |  |  |  |  |  |  | the origin is assumed | 
| 402 |  |  |  |  |  |  | to be 0,0 in the upper left corner, with x bigger to the right, and | 
| 403 |  |  |  |  |  |  | y bigger down. Adjust as necessary to fit your problem domain. | 
| 404 |  |  |  |  |  |  |  | 
| 405 |  |  |  |  |  |  | =cut | 
| 406 |  |  |  |  |  |  |  | 
| 407 |  |  |  |  |  |  | sub map_range { | 
| 408 | 6 |  |  | 6 | 1 | 3217 | my ($self, $width, $height, $char) = @_; | 
| 409 | 6 |  |  |  |  | 57 | my @r = @{$.Ranges{$char}}; | 
|  | 6 |  |  |  |  | 20 |  | 
| 410 | 6 |  |  |  |  | 18 | my $h = ./_height($.textRef); | 
| 411 | 6 |  |  |  |  | 16 | my $w = ./_widest($.textRef); | 
| 412 | 6 |  |  |  |  | 20 | my ($xs, $xo) = ./_stretch_offset(0, $w, 0, $width); | 
| 413 | 6 |  |  |  |  | 16 | my ($ys, $yo) = ./_stretch_offset(0, $h, 0, $height); | 
| 414 | 6 |  |  | 12 |  | 25 | my $xEqn = sub { my ($x) = @_; my $y = $xs*$x + $xo; return $y; }; | 
|  | 12 |  |  |  |  | 23 |  | 
|  | 12 |  |  |  |  | 23 |  | 
|  | 12 |  |  |  |  | 17 |  | 
| 415 | 6 |  |  | 12 |  | 23 | my $yEqn = sub { my ($y) = @_; my $x = $ys*$y + $yo; return $x; }; | 
|  | 12 |  |  |  |  | 13 |  | 
|  | 12 |  |  |  |  | 18 |  | 
|  | 12 |  |  |  |  | 23 |  | 
| 416 | 6 |  |  |  |  | 16 | my $xmin = $xEqn->($r[2]); | 
| 417 | 6 |  |  |  |  | 16 | my $ymin = $yEqn->($r[0]), | 
| 418 |  |  |  |  |  |  | my $xmax = $xEqn->($r[3]-$r[2]+1)+$xmin; | 
| 419 | 6 |  |  |  |  | 16 | my $ymax = $yEqn->($r[1]-$r[0]+1)+$ymin; | 
| 420 | 6 |  |  |  |  | 14 | my @bbox = ($xmin, $ymin, $xmax-1, $ymax-1); | 
| 421 | 6 |  |  |  |  | 51 | return @bbox; | 
| 422 |  |  |  |  |  |  | } | 
| 423 |  |  |  |  |  |  |  | 
| 424 |  |  |  |  |  |  | # find out if there is overlap; $c0 and $c1 are array references | 
| 425 |  |  |  |  |  |  | sub _check_overlap { | 
| 426 | 224 |  |  | 224 |  | 338 | my ($self, $c0, $c1) = @_; | 
| 427 | 224 |  |  |  |  | 221 | my %x; | 
| 428 | 224 |  |  |  |  | 436 | my @x0 = @$c0; | 
| 429 | 224 |  |  |  |  | 318 | my @x1 = @$c1; | 
| 430 | 224 |  |  |  |  | 1443 | $x{$_}  = 1 foreach $x0[0] .. $x0[1]; | 
| 431 | 224 |  |  |  |  | 1349 | $x{$_} += 1 foreach $x1[0] .. $x1[1]; | 
| 432 | 224 |  |  |  |  | 304 | my $status; | 
| 433 | 1315 | 100 |  |  |  | 4304 | map { | 
| 434 | 224 |  |  |  |  | 586 | $status = 1 if $x{$_} > 1; | 
| 435 |  |  |  |  |  |  | } keys(%x); | 
| 436 | 224 | 100 |  |  |  | 2489 | return defined $status ? 1 : 0; | 
| 437 |  |  |  |  |  |  | } | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | # are they in same x range? | 
| 440 |  |  |  |  |  |  | sub _in_x { | 
| 441 | 112 |  |  | 112 |  | 173 | my ($self, $me, $other) = @_; | 
| 442 | 112 |  |  |  |  | 229 | my @x = ($me->[2], $me->[3]); | 
| 443 | 112 |  |  |  |  | 200 | my @xo = ($other->[2], $other->[3]); | 
| 444 | 112 |  |  |  |  | 256 | return ./_check_overlap(\@x, \@xo); | 
| 445 |  |  |  |  |  |  | } | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | # are they in same y range? | 
| 448 |  |  |  |  |  |  | sub _in_y { | 
| 449 | 112 |  |  | 112 |  | 201 | my ($self, $me, $other) = @_; | 
| 450 | 112 |  |  |  |  | 228 | my @y = ($me->[0], $me->[1]); | 
| 451 | 112 |  |  |  |  | 191 | my @yo = ($other->[0], $other->[1]); | 
| 452 | 112 |  |  |  |  | 256 | return ./_check_overlap(\@y, \@yo); | 
| 453 |  |  |  |  |  |  | } | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | =head2 B | 
| 456 |  |  |  |  |  |  |  | 
| 457 |  |  |  |  |  |  | @r = $tl->above($char); | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | Return a list (possibly empty) of each of the characters | 
| 460 |  |  |  |  |  |  | above (and adjacent) to the specified character. | 
| 461 |  |  |  |  |  |  |  | 
| 462 |  |  |  |  |  |  | =cut | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | sub above { | 
| 465 | 8 |  |  | 8 | 1 | 3005 | my ($self, $char) = @_; | 
| 466 | 8 |  |  |  |  | 11 | my @r = @{$.Ranges{$char}}; | 
|  | 8 |  |  |  |  | 27 |  | 
| 467 | 8 | 100 |  |  |  | 36 | return () if $r[0] == 0; | 
| 468 | 5 | 50 |  |  |  | 24 | return @{$.Above{$char}} if defined $.Above{$char}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 469 | 5 |  |  |  |  | 5 | my @keys = keys(%.Ranges); | 
|  | 5 |  |  |  |  | 26 |  | 
| 470 | 5 |  |  |  |  | 11 | my @d; | 
| 471 |  |  |  |  |  |  | map { | 
| 472 | 5 | 100 |  |  |  | 9 | if ($_ ne $char) { | 
|  | 45 |  |  |  |  | 116 |  | 
| 473 |  |  |  |  |  |  | #print "Comparing $_ "; | 
| 474 | 40 |  |  |  |  | 46 | my @other = @{$.Ranges{$_}}; | 
|  | 40 |  |  |  |  | 125 |  | 
| 475 | 40 | 100 | 66 |  |  | 98 | push(@d, $_) if ./_in_x(\@r, \@other) && | 
|  |  |  | 66 |  |  |  |  | 
| 476 |  |  |  |  |  |  | ($other[0] == ($r[0]-1) || $other[1] == ($r[0]-1)); | 
| 477 |  |  |  |  |  |  | } | 
| 478 |  |  |  |  |  |  | } @keys; | 
| 479 | 5 |  |  |  |  | 14 | $.Above{$char} = \@d; | 
| 480 |  |  |  |  |  |  | #print "Above $char @d\n"; | 
| 481 | 5 |  |  |  |  | 32 | return @d; | 
| 482 |  |  |  |  |  |  | } | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | =head2 B | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | @r = $tl->below($char); | 
| 487 |  |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | Return a list (possibly empty) of each of the characters | 
| 489 |  |  |  |  |  |  | below (and adjacent) to the specified character. | 
| 490 |  |  |  |  |  |  |  | 
| 491 |  |  |  |  |  |  | =cut | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | sub below { | 
| 494 | 9 |  |  | 9 | 1 | 7276 | my ($self, $char) = @_; | 
| 495 | 9 |  |  |  |  | 15 | my @r = @{$.Ranges{$char}}; | 
|  | 9 |  |  |  |  | 35 |  | 
| 496 | 9 | 50 |  |  |  | 31 | return () if $r[1] == ./width(); | 
| 497 | 9 | 50 |  |  |  | 42 | return @{$.Below{$char}} if defined $.Below{$char}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 498 | 9 |  |  |  |  | 14 | my @keys = keys(%.Ranges); | 
|  | 9 |  |  |  |  | 53 |  | 
| 499 | 9 |  |  |  |  | 20 | my @d; | 
| 500 |  |  |  |  |  |  | map { | 
| 501 | 9 | 100 |  |  |  | 16 | if ($_ ne $char) { | 
|  | 81 |  |  |  |  | 205 |  | 
| 502 | 72 |  |  |  |  | 79 | my @other = @{$.Ranges{$_}}; | 
|  | 72 |  |  |  |  | 221 |  | 
| 503 | 72 | 100 | 66 |  |  | 193 | push(@d, $_) if ./_in_x(\@r, \@other) && | 
|  |  |  | 66 |  |  |  |  | 
| 504 |  |  |  |  |  |  | ($other[0] == ($r[0]+1) || $other[1] == ($r[0]+1)); | 
| 505 |  |  |  |  |  |  | } | 
| 506 |  |  |  |  |  |  | } @keys; | 
| 507 | 9 |  |  |  |  | 28 | $.Below{$char} = \@d; | 
| 508 | 9 |  |  |  |  | 59 | return @d; | 
| 509 |  |  |  |  |  |  | } | 
| 510 |  |  |  |  |  |  |  | 
| 511 |  |  |  |  |  |  | =head2 B | 
| 512 |  |  |  |  |  |  |  | 
| 513 |  |  |  |  |  |  | @r = $tl->left($char); | 
| 514 |  |  |  |  |  |  |  | 
| 515 |  |  |  |  |  |  | Return a list (possibly empty) of each of the characters to | 
| 516 |  |  |  |  |  |  | the left (and adjacent) to the specified character. | 
| 517 |  |  |  |  |  |  |  | 
| 518 |  |  |  |  |  |  | =cut | 
| 519 |  |  |  |  |  |  |  | 
| 520 |  |  |  |  |  |  | sub left { | 
| 521 | 9 |  |  | 9 | 1 | 2533 | my ($self, $char) = @_; | 
| 522 | 9 |  |  |  |  | 15 | my @r = @{$.Ranges{$char}}; | 
|  | 9 |  |  |  |  | 33 |  | 
| 523 | 9 | 100 |  |  |  | 84 | return () if $r[2] == 0; | 
| 524 | 6 | 50 |  |  |  | 21 | return @{$.Left{$char}} if defined $.Left{$char}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 525 | 6 |  |  |  |  | 7 | my @keys = keys(%.Ranges); | 
|  | 6 |  |  |  |  | 28 |  | 
| 526 | 6 |  |  |  |  | 14 | my @d; | 
| 527 |  |  |  |  |  |  | map { | 
| 528 | 6 | 100 |  |  |  | 8 | if ($_ ne $char) { | 
|  | 54 |  |  |  |  | 131 |  | 
| 529 | 48 |  |  |  |  | 54 | my @other = @{$.Ranges{$_}}; | 
|  | 48 |  |  |  |  | 149 |  | 
| 530 | 48 | 100 | 100 |  |  | 114 | push(@d, $_) if ./_in_y(\@r, \@other) && | 
| 531 |  |  |  |  |  |  | ($other[3] == ($r[2]-1)); | 
| 532 |  |  |  |  |  |  | } | 
| 533 |  |  |  |  |  |  | } @keys; | 
| 534 | 6 |  |  |  |  | 15 | $.Left{$char} = \@d; | 
| 535 | 6 |  |  |  |  | 37 | return @d; | 
| 536 |  |  |  |  |  |  | } | 
| 537 |  |  |  |  |  |  |  | 
| 538 |  |  |  |  |  |  | =head2 B | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | @r = $tl->right($char); | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  | Return a list (possibly empty) of each of the characters to | 
| 543 |  |  |  |  |  |  | the right (and adjacent) to the specified character. | 
| 544 |  |  |  |  |  |  |  | 
| 545 |  |  |  |  |  |  | =cut | 
| 546 |  |  |  |  |  |  |  | 
| 547 |  |  |  |  |  |  | sub right { | 
| 548 | 8 |  |  | 8 | 1 | 2508 | my ($self, $char) = @_; | 
| 549 | 8 |  |  |  |  | 11 | my @r = @{$.Ranges{$char}}; | 
|  | 8 |  |  |  |  | 27 |  | 
| 550 | 8 | 50 |  |  |  | 35 | return () if $r[2] == ./width(); | 
| 551 | 8 | 50 |  |  |  | 24 | return @{$.Right{$char}} if defined $.Right{$char}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 552 | 8 |  |  |  |  | 9 | my @keys = keys(%.Ranges); | 
|  | 8 |  |  |  |  | 39 |  | 
| 553 | 8 |  |  |  |  | 15 | my @d; | 
| 554 |  |  |  |  |  |  | map { | 
| 555 | 8 | 100 |  |  |  | 10 | if ($_ ne $char) { | 
|  | 72 |  |  |  |  | 191 |  | 
| 556 | 64 |  |  |  |  | 69 | my @other = @{$.Ranges{$_}}; | 
|  | 64 |  |  |  |  | 183 |  | 
| 557 | 64 | 100 | 100 |  |  | 143 | push(@d, $_) if ./_in_y(\@r, \@other) && | 
| 558 |  |  |  |  |  |  | ($other[2] == ($r[3]+1)); | 
| 559 |  |  |  |  |  |  | } | 
| 560 |  |  |  |  |  |  | } @keys; | 
| 561 | 8 |  |  |  |  | 23 | $.Right{$char} = \@d; | 
| 562 | 8 |  |  |  |  | 44 | return @d; | 
| 563 |  |  |  |  |  |  | } | 
| 564 |  |  |  |  |  |  |  | 
| 565 |  |  |  |  |  |  | =head2 B | 
| 566 |  |  |  |  |  |  |  | 
| 567 |  |  |  |  |  |  | ($xpercent, $ypercent) = $tl->range_as_percent($char); | 
| 568 |  |  |  |  |  |  |  | 
| 569 |  |  |  |  |  |  | Returns the percentage of x and y that this character consumes | 
| 570 |  |  |  |  |  |  | in the I. Number returned for each is <= 1.0. | 
| 571 |  |  |  |  |  |  |  | 
| 572 |  |  |  |  |  |  | =cut | 
| 573 |  |  |  |  |  |  |  | 
| 574 |  |  |  |  |  |  | sub range_as_percent { | 
| 575 | 6 |  |  | 6 | 1 | 1318 | my ($self, $char) = @_; | 
| 576 | 6 |  |  |  |  | 18 | my ($ymin, $ymax, $xmin, $xmax) = ./range($char); | 
| 577 | 6 |  |  |  |  | 18 | my $width  = ./width(); | 
| 578 | 6 |  |  |  |  | 18 | my $height = ./height(); | 
| 579 | 6 |  |  |  |  | 76 | return (($xmax-$xmin+1)/$width, ($ymax-$ymin+1)/$height); | 
| 580 |  |  |  |  |  |  | } | 
| 581 |  |  |  |  |  |  |  | 
| 582 |  |  |  |  |  |  | =head2 B | 
| 583 |  |  |  |  |  |  |  | 
| 584 |  |  |  |  |  |  | @chars = $tl->order([$line]); | 
| 585 |  |  |  |  |  |  |  | 
| 586 |  |  |  |  |  |  | Return the order of the characters encountered on line $line | 
| 587 |  |  |  |  |  |  | (zero-based). $line defaults to zero if not specified. | 
| 588 |  |  |  |  |  |  |  | 
| 589 |  |  |  |  |  |  | =cut | 
| 590 |  |  |  |  |  |  |  | 
| 591 |  |  |  |  |  |  | sub order { | 
| 592 | 4 |  |  | 4 | 1 | 633 | my ($self, $line) = @_; | 
| 593 | 4 | 100 |  |  |  | 27 | $line = 0 unless defined $line; | 
| 594 | 4 | 100 |  |  |  | 15 | die "$.Class - in order, line $line is too big!\n" | 
| 595 |  |  |  |  |  |  | unless $line < ./height(); | 
| 596 | 3 |  |  |  |  | 6 | my $text = $.textRef[$line]; | 
| 597 | 3 | 50 |  |  |  | 12 | return unless defined $text; | 
| 598 | 3 |  |  |  |  | 43 | my %Chars; | 
| 599 |  |  |  |  |  |  | my @Chars; | 
| 600 | 3 |  |  |  |  | 18 | my @chars = split('', $text); | 
| 601 |  |  |  |  |  |  | map { | 
| 602 | 3 | 100 |  |  |  | 6 | unless (defined $Chars{$_}) { | 
|  | 19 |  |  |  |  | 58 |  | 
| 603 | 6 |  |  |  |  | 12 | push(@Chars, $_); | 
| 604 | 6 |  |  |  |  | 24 | $Chars{$_} = 1; | 
| 605 |  |  |  |  |  |  | } | 
| 606 |  |  |  |  |  |  | } @chars; | 
| 607 | 3 |  |  |  |  | 23 | return @Chars; | 
| 608 |  |  |  |  |  |  | } | 
| 609 |  |  |  |  |  |  |  | 
| 610 |  |  |  |  |  |  | =head2 B | 
| 611 |  |  |  |  |  |  |  | 
| 612 |  |  |  |  |  |  | $stat = $tl->only_one(); | 
| 613 |  |  |  |  |  |  |  | 
| 614 |  |  |  |  |  |  | Returns 1 if there is only a single character in your pattern, | 
| 615 |  |  |  |  |  |  | 0 if there are more. | 
| 616 |  |  |  |  |  |  |  | 
| 617 |  |  |  |  |  |  | =cut | 
| 618 |  |  |  |  |  |  |  | 
| 619 |  |  |  |  |  |  | sub only_one { | 
| 620 | 2 |  |  | 2 | 1 | 5 | my ($self) = @_; | 
| 621 | 2 |  |  |  |  | 9 | return ./order() == 1; | 
| 622 |  |  |  |  |  |  | } | 
| 623 |  |  |  |  |  |  |  | 
| 624 |  |  |  |  |  |  | =head1 AUTHOR | 
| 625 |  |  |  |  |  |  |  | 
| 626 |  |  |  |  |  |  | X Cramps, C<<  >> | 
| 627 |  |  |  |  |  |  |  | 
| 628 |  |  |  |  |  |  | =head1 BUGS | 
| 629 |  |  |  |  |  |  |  | 
| 630 |  |  |  |  |  |  | There shouldn't be any. But I am a human, and do mess up sometimes. | 
| 631 |  |  |  |  |  |  |  | 
| 632 |  |  |  |  |  |  | Please report any bugs or feature requests to C | 
| 633 |  |  |  |  |  |  | at rt.cpan.org>, or through | 
| 634 |  |  |  |  |  |  | the web interface at | 
| 635 |  |  |  |  |  |  | L. | 
| 636 |  |  |  |  |  |  | I will be notified, and then you'll | 
| 637 |  |  |  |  |  |  | automatically be notified of progress on your bug as I make changes. | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | =head1 SUPPORT | 
| 640 |  |  |  |  |  |  |  | 
| 641 |  |  |  |  |  |  | You can find documentation for this module with the perldoc command. | 
| 642 |  |  |  |  |  |  |  | 
| 643 |  |  |  |  |  |  | perldoc Acme::TextLayout | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  | You can also look for information at: | 
| 646 |  |  |  |  |  |  |  | 
| 647 |  |  |  |  |  |  | =over 4 | 
| 648 |  |  |  |  |  |  |  | 
| 649 |  |  |  |  |  |  | =item * RT: CPAN's request tracker | 
| 650 |  |  |  |  |  |  |  | 
| 651 |  |  |  |  |  |  | L | 
| 652 |  |  |  |  |  |  |  | 
| 653 |  |  |  |  |  |  | =item * AnnoCPAN: Annotated CPAN documentation | 
| 654 |  |  |  |  |  |  |  | 
| 655 |  |  |  |  |  |  | L | 
| 656 |  |  |  |  |  |  |  | 
| 657 |  |  |  |  |  |  | =item * CPAN Ratings | 
| 658 |  |  |  |  |  |  |  | 
| 659 |  |  |  |  |  |  | L | 
| 660 |  |  |  |  |  |  |  | 
| 661 |  |  |  |  |  |  | =item * Search CPAN | 
| 662 |  |  |  |  |  |  |  | 
| 663 |  |  |  |  |  |  | L | 
| 664 |  |  |  |  |  |  |  | 
| 665 |  |  |  |  |  |  | =back | 
| 666 |  |  |  |  |  |  |  | 
| 667 |  |  |  |  |  |  | =head1 ACKNOWLEDGEMENTS | 
| 668 |  |  |  |  |  |  |  | 
| 669 |  |  |  |  |  |  | Captain Beefheart and Ella Guru. So there. | 
| 670 |  |  |  |  |  |  |  | 
| 671 |  |  |  |  |  |  | =head1 COPYRIGHT & LICENSE | 
| 672 |  |  |  |  |  |  |  | 
| 673 |  |  |  |  |  |  | Copyright 2009 X Cramps, all rights reserved. | 
| 674 |  |  |  |  |  |  |  | 
| 675 |  |  |  |  |  |  | This program is free software; you can redistribute it and/or modify it | 
| 676 |  |  |  |  |  |  | under the same terms as Perl itself. | 
| 677 |  |  |  |  |  |  |  | 
| 678 |  |  |  |  |  |  |  | 
| 679 |  |  |  |  |  |  | =cut | 
| 680 |  |  |  |  |  |  |  | 
| 681 |  |  |  |  |  |  | 1; # End of Acme::TextLayout |