File Coverage

blib/lib/Imager/DTP/Textbox.pm
Criterion Covered Total %
statement 12 275 4.3
branch 0 104 0.0
condition 0 48 0.0
subroutine 4 37 10.8
pod 16 16 100.0
total 32 480 6.6


line stmt bran cond sub pod time code
1             package Imager::DTP::Textbox;
2 2     2   12 use strict;
  2         3  
  2         66  
3 2     2   10 use Carp;
  2         10  
  2         185  
4 2     2   5198 use Imager;
  2         107971  
  2         19  
5 2     2   158 use vars qw($VERSION);
  2         5  
  2         7530  
6              
7             $VERSION = '0.04';
8              
9             sub new {
10 0     0 1   my $self = shift;
11 0           my %o = @_;
12             # define properties
13 0           my $p = {
14             lines => [],
15             leading => 150,
16             width => 0,
17             height => 0,
18             halign => '',
19             valign => '',
20             wrapWidth => 0,
21             wrapHeight => 0,
22             isUpdated => 0, # check flag for _calcWidthHeight needs
23             };
24 0           $self = bless($p,$self);
25             # set properties
26 0           $self->setLeading(percent=>$o{leading});
27 0           $self->setAlign(valign=>$o{valign},halign=>$o{halign});
28 0           $self->setWrap(width=>$o{wrapWidth},height=>$o{wrapHeight});
29 0 0         if(defined($o{text})){
30 0           $self->setText(text=>$o{text},font=>$o{font});
31 0           $self->setWspace(pixel=>$o{wspace});
32             }
33 0 0 0       if($o{xscale} || $o{yscale}){
34 0           $self->setLetterScale(x=>$o{xscale},y=>$o{yscale});
35             }
36 0           return $self;
37             }
38              
39             sub draw {
40 0     0 1   my $self = shift;
41 0           my %o = @_;
42             # validation
43 0 0 0       if($o{target} && ref($o{target}) !~ /^Imager(::.+)?$/){
44 0           confess "target: must be an Imager Object";
45             }
46 0 0         $o{x} = 0 if(!$o{x});
47 0 0         $o{y} = 0 if(!$o{y});
48             # calculate width and height
49 0           $self->_calcWidthHeight();
50             # calculate text wrap
51 0           $self->_calcWrap();
52             # draw directly to target image
53 0 0         if($o{target}){
54 0           my($x,$y) = $self->_draw_getStartPos(x=>$o{x},y=>$o{y});
55 0           $self->_draw_drawLines(target=>$o{target},x=>$x,y=>$y,debug=>$o{debug},others=>$o{others});
56 0           return 1;
57             # or return drawn Imager object
58             }else{
59 0 0         my $tmp = Imager->new(xsize=>$self->_getWidth(),
60             ysize=>$self->_getHeight(),channels=>3) or die $Imager::ERRSTR;
61 0 0         $o{bgcolor} = '$FFFFFF' if(!$o{bgcolor});
62 0           $tmp->box(filled=>1,color=>$o{bgcolor});
63 0           $self->_draw_drawLines(target=>$tmp,debug=>$o{debug},others=>$o{others});
64 0           return $tmp;
65             }
66             }
67              
68             sub _draw_drawLines {
69 0     0     confess "_draw_drawLines - this is an abstract method";
70             }
71              
72             sub _draw_getAlignPos {
73 0     0     confess "_draw_getAlignPos - this is an abstract method";
74             }
75              
76             sub _draw_getStartPos {
77 0     0     confess "_draw_getStartPos - this is an abstract method";
78             }
79              
80             sub _calcWidthHeight {
81 0     0     confess "_calcWidthHeight - this is an abstract method";
82             }
83              
84             sub _calcWrap {
85 0     0     my $self = shift;
86             # letter wrapping
87 0           $self->_calcWrap_LetterStack();
88             # line truncating
89 0           $self->_calcWrap_LineStack();
90 0           return 1;
91             }
92              
93             sub _calcWrap_LetterStack {
94 0     0     my $self = shift;
95 0           my $wrapMax = $self->_calcWrap_LetterStack_getWrapMax();
96 0 0         return undef if(!$wrapMax);
97 0           my $lines = $self->getLines();
98 0           my $li = 0;
99 0           foreach my $line (@{$lines}){
  0            
100 0           my $lineMax = $self->_calcWrap_LetterStack_getLineMax(line=>$line);
101 0 0 0       if($wrapMax && $wrapMax < $lineMax){
102 0           my $wi = 0;
103 0           my $nowMax = 0;
104 0           my $exceeded = 0;
105 0           my $letters = $line->getLetters();
106             # check exceedance
107 0           while (1){
108 0           my $ltr = $letters->[$wi];
109 0           $nowMax += $self->_calcWrap_LetterStack_getLetterSize(letter=>$ltr);
110 0 0         if($nowMax > $wrapMax){
111 0           $exceeded = 1;
112 0           last;
113             }
114 0 0         last if($wi == $#{$letters});
  0            
115 0 0         $nowMax += $line->getWspace() if($wi < $#{$letters});
  0            
116 0           $wi++;
117             }
118             # cut off exceeded letters
119 0 0         if($exceeded){
120 0           my @exceed = ();
121             # line contains more than 1 letter & $wrapMax is less than 1 letter size
122 0 0 0       if($wi == 0 && $#{$letters} > 0){
  0 0 0        
  0            
123 0           @exceed = @{$letters}[1 .. $#{$letters}];
  0            
  0            
124             # line contains only 1 letter & $wrapMax is less than 1 letter size
125             }elsif($wi == 0 && $#{$letters} == 0){
126 0           @exceed = ();
127             # other than above (usual case)
128             }else{
129             # grab some more letters if it's in a middle of an alphabet word
130 0           $wi = $self->_calcWrap_LetterStack_eExceed(wi=>$wi,
131             letters=>$letters);
132 0           @exceed = @{$letters}[$wi .. $#{$letters}];
  0            
  0            
133             }
134             # delete space at beginning of exceed letters
135 0           $self->_calcWrap_LetterStack_CutFrontSpace(exceed=>\@exceed);
136             # cut off exceeded letters
137 0 0         my $to = ($wi > 0)? $wi-1 : 0;
138 0           @{$letters} = @{$letters}[0 .. $to];
  0            
  0            
139             # create new line if needed
140 0 0         if($li == $#{$lines}){
  0            
141 0           my $newLine = $self->_getNewLineInstance(wspace=>$line->getWspace(),isWrap=>1);
142 0           $newLine->{letters} = \@exceed;
143 0           $newLine->_calcWidthHeight();
144 0           push(@{$lines},$newLine);
  0            
145             # un-shift exeeded letters to the later line
146             }else{
147 0           my $laterLine = $lines->[$li+1];
148             # if the later line was a line created during calcWrap()
149 0 0         if($laterLine->{isWrap} == 1){
150 0           unshift(@{$laterLine->{letters}},@exceed);
  0            
151 0           $laterLine->{isUpdated} = 0; # force re-calculation
152 0           $laterLine->_calcWidthHeight();
153             # or else add new line in between this and later line
154             }else{
155 0           my $newLine = $self->_getNewLineInstance(wspace=>$line->getWspace(),isWrap=>1);
156 0           $newLine->{letters} = \@exceed;
157 0           $newLine->_calcWidthHeight();
158 0           @{$lines} = (@{$lines}[0 .. $li], $newLine, @{$lines}[$li+1 .. $#{$lines}]);
  0            
  0            
  0            
  0            
159             }
160             }
161             # re-calculate width and height of the current line
162 0           $line->{isUpdated} = 0; # register for re-calculation
163             }
164             }
165 0           $li++;
166             }
167             # re-calculate width and height
168 0           $self->{isUpdated} = 0;
169 0           $self->_calcWidthHeight();
170 0           return 1;
171             }
172              
173             sub _calcWrap_LetterStack_getWrapMax {
174 0     0     confess "_calcWrap_LetterStack_getWrapMax - this is an abstract method";
175             }
176             sub _calcWrap_LetterStack_getLineMax {
177 0     0     confess "_calcWrap_LetterStack_getLineMax - this is an abstract method";
178             }
179             sub _calcWrap_LetterStack_getLetterSize {
180 0     0     confess "_calcWrap_LetterStack_getLetterSize - this is an abstract method";
181             }
182              
183             sub _calcWrap_LetterStack_eExceed {
184 0     0     my $self = shift;
185 0           my %o = @_;
186 0           my $pattern = qr/[a-zA-Z0-9'!?%$(),.]/;
187             # return if exceeded letter was not a single-byte character
188 0 0         return $o{wi} if($o{letters}->[$o{wi}]->getText() !~ /$pattern/);
189 0           my $i;
190 0           for($i=$o{wi};$i>=0;$i--){
191 0           my $t = $o{letters}->[$i]->getText();
192             # find a word breaking spot
193 0 0 0       last if($t =~ /\s/ || $t !~ /$pattern/);
194             }
195             # if $wi spot is at the beggining of line, don't count as exceed,
196             # infact, find the end of the word and count that as exceed point.
197 0 0         if($i+1 == 0){
198 0           my $c;
199 0           for($c=$o{wi};$c<=$#{$o{letters}};$c++){
  0            
200 0           my $t = $o{letters}->[$c]->getText();
201             # find a word breaking spot
202 0 0 0       last if($t =~ /\s/ || $t !~ /$pattern/);
203             }
204 0           return $c;
205             # else return the spot where the word starts
206             }else{
207 0           return $i+1;
208             }
209             }
210              
211             sub _calcWrap_LetterStack_CutFrontSpace {
212 0     0     my $self = shift;
213 0           my %o = @_;
214 0 0         return undef if(@{$o{exceed}} == 0);
  0            
215 0           my $firstLetter = $o{exceed}->[0];
216 0 0         if($firstLetter->getText() =~ /\s/){
217 0           shift(@{$o{exceed}});
  0            
218 0           return 1;
219             }
220 0           return undef;
221             }
222              
223             sub _calcWrap_LineStack {
224 0     0     my $self = shift;
225 0           my $wrapMax = $self->_calcWrap_LineStack_getWrapMax();
226 0 0         return undef if(!$wrapMax);
227 0           my $lines = $self->getLines();
228 0           my $li = 0;
229 0           my $nowMax = 0;
230 0           my $lineMax = $self->_getMaxLetterSize();
231 0           my $lineSpace = $self->_calcLineSpace();
232 0           my $exceed = 0;
233 0           foreach my $line (@{$lines}){
  0            
234 0           $nowMax += $lineMax;
235 0 0         if($nowMax > $wrapMax){
236 0           $exceed = 1;
237 0           last;
238             }
239 0 0         $nowMax += $lineSpace if($li < $#{$lines});
  0            
240 0           $li++;
241             }
242             # cut off exceeded lines
243 0 0         if($exceed){
244 0 0         my $to = ($li == 0)? 0 : $li-1;
245 0           @{$lines} = @{$lines}[0 .. $to];
  0            
  0            
246             # re-calculate width and height if needed
247 0           $self->{isUpdated} = 0;
248 0           $self->_calcWidthHeight();
249             }
250 0           return 1;
251             }
252              
253             sub _calcWrap_LineStack_getWrapMax {
254 0     0     confess "_calcWrap_LineStack_getWrapMax - this is an abstract method";
255             }
256              
257             sub _calcLineSpace {
258 0     0     my $self = shift;
259 0           my $base = $self->_getMaxLetterSize();
260 0           return ($self->getLeading() / 100 - 1) * $base;
261             }
262              
263             sub setText {
264 0     0 1   my $self = shift;
265 0           my %o = @_;
266             # validation
267 0 0 0       if(!defined($o{text}) || $o{text} eq ''){
268 0           confess "text: must not be empty or null.";
269             }
270 0           $o{text} =~ s/\r\n/\n/g; # replate CR+LF to LF
271 0           $o{text} =~ s/\r/\n/g; # replate CR to LF
272             # get last line object
273 0           my $lastLine;
274 0 0         if(@{$self->{lines}} > 0){
  0            
275 0           $lastLine = pop(@{$self->{lines}});
  0            
276 0           my $letters = $lastLine->getLetters();
277 0 0         $o{wspace} = $lastLine->getWspace() if(!defined($o{wspace}));
278 0 0 0       $o{font} = $letters->[-1]{font} if(!defined($o{font}) && scalar @{$letters} > 0);
  0            
279             }
280             # validation for font object
281 0 0         if(ref($o{font}) !~ /^Imager::Font(::.+)?$/){
282 0           confess "font: must define an Imager::Font Object";
283             }
284             # clear current lines
285 0 0         $self->{lines} = [] if(!$o{add});
286             # parse by line feeds
287 0           my(@lineTexts,$t,$len);
288 0           ($t) = ($o{text} =~ /^(\n+)/); # look for pre-\n's
289 0 0         $len = ($t)? length($t) : 0;
290 0 0         if($len > 0){
291 0           push(@lineTexts,'') for (1 .. $len);
292             }
293 0           ($t) = ($o{text} =~ /(\n+)$/); # look for post-\n's
294 0 0         $len = ($t)? length($t) : 0;
295 0           @lineTexts = split(/\n/,$o{text}); # split inner text by line feeds
296 0 0         if($len > 0){
297 0           push(@lineTexts,'') for (1 .. $len);
298             }
299 0 0         @lineTexts = ('') if(scalar @lineTexts == 0); # to create a blank line
300 0           my $i=0;
301 0           foreach my $text (@lineTexts){
302 0 0 0       if($i == 0 && $lastLine && $o{add}){
      0        
303             # add new texts to the end of last line
304 0           $lastLine->setText(text=>$text,font=>$o{font},add=>1);
305 0           push(@{$self->{lines}},$lastLine);
  0            
306             }else{
307             # create new Line instance if a clean blank line is needed
308 0           my $newLine = $self->_getNewLineInstance(wspace=>$o{wspace});
309 0           $newLine->setText(text=>$text,font=>$o{font});
310 0           push(@{$self->{lines}},$newLine);
  0            
311             }
312 0           $i++;
313             }
314 0           $self->{isUpdated} = 0;
315 0           return 1;
316             }
317              
318             sub setWrap {
319 0     0 1   my $self = shift;
320 0           my %o = @_;
321             # validation
322 0 0 0       if($o{width} && $o{width} !~ /^\d+$/){
323 0           confess "width: must be an integer ($o{width})";
324             }
325 0 0 0       if($o{height} && $o{height} !~ /^\d+$/){
326 0           confess "height: must be an integer ($o{height})";
327             }
328 0 0         $self->{wrapWidth} = $o{width} if($o{width});
329 0 0         $self->{wrapHeight} = $o{height} if($o{height});
330 0           $self->{isUpdated} = 0;
331 0           return 1;
332             }
333              
334             sub setAlign {
335 0     0 1   my $self = shift;
336 0           my %o = @_;
337 0 0 0       if($o{halign} && $o{halign} !~ /^(left|center|right)$/){
338 0           confess "halign: must be either of left/center/right ($o{h})";
339             }
340 0 0 0       if($o{valign} && $o{valign} !~ /^(top|center|bottom)$/){
341 0           confess "valign: must be either of top/center/bottom ($o{v})";
342             }
343 0           $self->_setAlign_setDefault(%o);
344 0 0         $self->{halign} = $o{halign} if($o{halign});
345 0 0         $self->{valign} = $o{valign} if($o{valign});
346 0           return 1;
347             }
348              
349             sub setLeading {
350 0     0 1   my $self = shift;
351 0           my %o = @_;
352 0 0 0       if($o{percent} && $o{percent} !~ /^\d+$/){
353 0           confess "percent: must be a percentage numeral ($o{value})";
354             }
355 0 0         $self->{leading} = $o{percent} if($o{percent});
356 0           $self->{isUpdated} = 0;
357 0           return 1;
358             }
359              
360             sub setWspace {
361 0     0 1   my $self = shift;
362 0           my %o = @_;
363 0           foreach my $line (@{$self->getLines()}){
  0            
364 0           $line->setWspace(@_);
365             }
366 0           return 1;
367             }
368              
369             sub setLetterScale {
370 0     0 1   my $self = shift;
371 0           my %o = @_;
372 0           foreach my $line (@{$self->getLines()}){
  0            
373 0           $line->setLetterScale(@_);
374             }
375 0           return 1;
376             }
377              
378             sub getWidth {
379 0     0 1   my $self = shift;
380 0           $self->_calcWidthHeight();
381 0           $self->_calcWrap();
382 0           return $self->_getWidth();
383             }
384             sub getHeight {
385 0     0 1   my $self = shift;
386 0           $self->_calcWidthHeight();
387 0           $self->_calcWrap();
388 0           return $self->_getHeight();
389             }
390             sub getLines {
391 0     0 1   return shift->{lines};
392             }
393             sub getLeading {
394 0     0 1   return shift->{leading};
395             }
396             sub getHalign {
397 0     0 1   return shift->{halign};
398             }
399             sub getValign {
400 0     0 1   return shift->{valign};
401             }
402             sub getWrapWidth {
403 0     0 1   return shift->{wrapWidth};
404             }
405             sub getWrapHeight {
406 0     0 1   return shift->{wrapHeight};
407             }
408             sub _getWidth {
409 0     0     return shift->{width};
410             }
411             sub _getHeight {
412 0     0     return shift->{height};
413             }
414             sub _getMaxLetterSize {
415 0     0     confess "_getMaxLetterSize - this is an abstract method";
416             }
417              
418             1;
419             __END__