File Coverage

blib/lib/SDLx/Text.pm
Criterion Covered Total %
statement 116 205 56.5
branch 34 94 36.1
condition 10 23 43.4
subroutine 26 35 74.2
pod 0 22 0.0
total 186 379 49.0


line stmt bran cond sub pod time code
1             package SDLx::Text;
2 1     1   756 use strict;
  1         3  
  1         35  
3 1     1   7 use warnings;
  1         4  
  1         35  
4 1     1   7 use SDL;
  1         2  
  1         100  
5 1     1   10 use SDL::Video;
  1         2  
  1         278  
6 1     1   10 use SDL::Config;
  1         3  
  1         31  
7 1     1   480 use SDL::TTF;
  1         3  
  1         69  
8 1     1   480 use SDL::TTF::Font;
  1         3  
  1         54  
9 1     1   6 use SDLx::Validate;
  1         2  
  1         29  
10 1     1   4 use List::Util qw(max sum);
  1         2  
  1         70  
11              
12 1     1   6 use Carp ();
  1         2  
  1         1889  
13              
14             our $VERSION = 2.548;
15              
16             sub new {
17 1     1 0 459 my ($class, %options) = @_;
18 1 50       6 unless ( SDL::Config->has('SDL_ttf') ) {
19 0         0 Carp::cluck("SDL_ttf support has not been compiled");
20             }
21 1         1 my $file = $options{'font'};
22 1 50       4 if (!$file) {
23 0         0 require File::ShareDir;
24 0         0 $file = File::ShareDir::dist_file('SDL', 'GenBasR.ttf');
25             }
26              
27 1 50       4 my $color = defined $options{'color'} ? $options{'color'} : [255, 255, 255];
28              
29 1   50     5 my $size = $options{'size'} || 24;
30              
31 1   50     4 my $shadow = $options{'shadow'} || 0;
32 1   50     3 my $shadow_offset = $options{'shadow_offset'} || 1;
33              
34             my $shadow_color = defined $options{'shadow_color'}
35 1 50       3 ? $options{'shadow_color'}
36             : [0, 0, 0]
37             ;
38              
39 1   33     4 my $self = bless {}, ref($class) || $class;
40              
41 1   50     5 $self->{x} = $options{'x'} || 0;
42 1   50     12 $self->{y} = $options{'y'} || 0;
43              
44 1   50     7 $self->{h_align} = $options{'h_align'} || 'left';
45             # TODO: validate
46             # TODO: v_align
47 1 50       4 unless ( SDL::TTF::was_init() ) {
48 1 50       128 Carp::cluck ("Cannot init TTF: " . SDL::get_error() )
49             unless SDL::TTF::init() == 0;
50             }
51              
52 1         7 $self->size($size);
53 1         3 $self->font($file);
54 1         93 $self->color($color);
55 1         3 $self->shadow($shadow);
56 1         3 $self->shadow_color($shadow_color);
57 1         3 $self->shadow_offset($shadow_offset);
58              
59 1 50       2 $self->bold($options{'bold'}) if exists $options{'bold'};
60 1 50       2 $self->italic($options{'italic'}) if exists $options{'italic'};
61 1 50       2 $self->underline($options{'underline'}) if exists $options{'underline'};
62 1 50       2 $self->strikethrough($options{'strikethrough'}) if exists $options{'strikethrough'};
63              
64             # word wrapping
65 1   50     3 $self->{word_wrap} = $options{'word_wrap'} || 0;
66              
67 1 50       3 $self->text( $options{'text'} ) if exists $options{'text'};
68              
69 1         3 return $self;
70             }
71              
72             sub font {
73 3     3 0 5 my ($self, $font_filename) = @_;
74              
75 3 100       9 if ($font_filename) {
76 1         2 my $size = $self->size;
77              
78 1 50       397 $self->{_font} = SDL::TTF::open_font($font_filename, $size)
79             or Carp::cluck "Error opening font '$font_filename': " . SDL::get_error;
80              
81 1         3 $self->{_font_filename} = $font_filename;
82 1         2 $self->{_update_surfaces} = 1;
83             }
84              
85 3         14 return $self->{_font};
86             }
87              
88             sub font_filename {
89 1     1 0 4 return $_[0]->{_font_filename};
90             }
91              
92             sub color {
93 2     2 0 4 my ($self, $color) = @_;
94              
95 2 100       5 if (defined $color) {
96 1         4 $self->{_color} = SDLx::Validate::color($color);
97 1         2 $self->{_update_surfaces} = 1;
98             }
99              
100 2         4 return $self->{_color};
101             }
102              
103             sub size {
104 3     3 0 7 my ($self, $size) = @_;
105              
106 3 100       5 if ($size) {
107 1         3 $self->{_size} = $size;
108              
109             # reload the font using new size.
110             # No need to set "_update_surfaces"
111             # since font() already does it.
112 1         3 $self->font( $self->font_filename );
113             }
114              
115 3         8 return $self->{_size};
116             }
117              
118             sub _style {
119 0     0   0 my ($self, $flag, $enable) = @_;
120              
121 0         0 my $styles = SDL::TTF::get_font_style( $self->font );
122              
123             # do we have an enable flag?
124 0 0       0 if (@_ > 2) {
125              
126             # we do! setup flags if we're enabling or disabling
127 0 0       0 if ($enable) {
128 0         0 $styles |= $flag;
129             }
130             else {
131 0 0       0 $styles ^= $flag if $flag & $styles;
132             }
133              
134 0         0 SDL::TTF::set_font_style( $self->font, $styles );
135              
136             # another run, returning true if value was properly set.
137 0         0 return SDL::TTF::get_font_style( $self->font ) & $flag;
138             }
139             # no enable flag present, just return
140             # whether the style is enabled/disabled
141             else {
142 0         0 return $styles & $flag;
143             }
144             }
145              
146 0     0 0 0 sub normal { my $self = shift; $self->_style( TTF_STYLE_NORMAL, @_ ) }
  0         0  
147 0     0 0 0 sub bold { my $self = shift; $self->_style( TTF_STYLE_BOLD, @_ ) }
  0         0  
148 0     0 0 0 sub italic { my $self = shift; $self->_style( TTF_STYLE_ITALIC, @_ ) }
  0         0  
149 0     0 0 0 sub underline { my $self = shift; $self->_style( TTF_STYLE_UNDERLINE, @_ ) }
  0         0  
150 0     0 0 0 sub strikethrough { my $self = shift; $self->_style( TTF_STYLE_STRIKETHROUGH, @_ ) }
  0         0  
151              
152              
153             sub h_align {
154 1     1 0 2 my ($self, $align) = @_;
155              
156 1 50       4 if ($align) {
157 0         0 $self->{h_align} = $align;
158 0         0 $self->{_update_surfaces} = 1;
159             }
160              
161 1         3 return $self->{h_align};
162             }
163              
164             sub shadow {
165 1     1 0 1 my ($self, $shadow) = @_;
166              
167 1 50       58 if ($shadow) {
168 0         0 $self->{shadow} = $shadow;
169 0         0 $self->{_update_surfaces} = 1;
170             }
171              
172 1         5 return $self->{shadow};
173             }
174              
175             sub shadow_color {
176 1     1 0 1 my ($self, $shadow_color) = @_;
177              
178 1 50       3 if (defined $shadow_color) {
179 1         3 $self->{shadow_color} = SDLx::Validate::color($shadow_color);
180 1         2 $self->{_update_surfaces} = 1;
181             }
182              
183 1         1 return $self->{shadow_color};
184             }
185              
186              
187             sub shadow_offset {
188 1     1 0 2 my ($self, $shadow_offset) = @_;
189              
190 1 50       2 if ($shadow_offset) {
191 1         1 $self->{shadow_offset} = $shadow_offset;
192 1         1 $self->{_update_surfaces} = 1;
193             }
194              
195 1         2 return $self->{shadow_offset};
196             }
197              
198             sub w {
199 2     2 0 5 my $surface = $_[0]->{surface};
200 2 50 33     17 return $surface->w unless $surface and ref $surface eq 'ARRAY';
201              
202 0 0       0 return max map { $_ ? $_->w() : 0 } @$surface;
  0         0  
203             }
204              
205             sub h {
206 1     1 0 3 my $surface = $_[0]->{surface};
207 1 50 33     9 return $surface->h unless $surface and ref $surface eq 'ARRAY';
208              
209 0 0       0 return sum map { $_ ? $_->h() : 0 } @$surface;
  0         0  
210             }
211              
212             sub x {
213 1     1 0 355 my ($self, $x) = @_;
214              
215 1 50       3 if (defined $x) {
216 0         0 $self->{x} = $x;
217             }
218 1         5 return $self->{x};
219             }
220              
221             sub y {
222 1     1 0 2 my ($self, $y) = @_;
223              
224 1 50       3 if (defined $y) {
225 0         0 $self->{y} = $y;
226             }
227 1         4 return $self->{y};
228             }
229              
230             sub text {
231 3     3 0 6 my ($self, $text) = @_;
232              
233 3 100       11 return $self->{text} if scalar @_ == 1;
234              
235 2 100       5 if ( defined $text ) {
236 1 50       3 $text = $self->_word_wrap($text) if $self->{word_wrap};
237 1         2 my $font = $self->{_font};
238             my $surface = _get_surfaces_for($font, $text, $self->{_color} )
239 1 50       2 or Carp::croak 'TTF rendering error: ' . SDL::get_error;
240              
241 1 50       6 if ($self->{shadow}) {
242             my $shadow_surface = _get_surfaces_for($font, $text, $self->{shadow_color})
243 0 0       0 or Carp::croak 'TTF shadow rendering error: ' . SDL::get_error;
244              
245 0 0       0 $shadow_surface = [ $shadow_surface ] unless ref $shadow_surface eq 'ARRAY';
246              
247 0         0 $self->{_shadow_surface} = $shadow_surface;
248             }
249              
250 1         2 $self->{surface} = $surface;
251 1         2 $self->{text} = $text;
252             }
253             else {
254 1         5 $self->{surface} = undef;
255             }
256              
257              
258 2         5 return $self;
259             }
260              
261             # Returns the TTF surface for the given text.
262             # If the text contains linebreaks, we split into
263             # several surfaces (since SDL can't render '\n').
264             sub _get_surfaces_for {
265 1     1   2 my ($font, $text, $color) = @_;
266              
267 1 50       1292 return SDL::TTF::render_utf8_blended($font, $text, $color)
268             if index($text, "\n") == -1;
269              
270 0         0 my @surfaces = ();
271 0         0 my @paragraphs = split /\n/ => $text;
272 0         0 foreach my $paragraph (@paragraphs) {
273 0         0 push @surfaces, SDL::TTF::render_utf8_blended($font, $paragraph, $color);
274             }
275 0         0 return \@surfaces;
276             }
277              
278             sub _word_wrap {
279 0     0   0 my ($self, $text) = @_;
280              
281 0         0 my $maxlen = $self->{word_wrap};
282 0         0 my $font = $self->{_font};
283              
284             # code heavily based on Text::Flow::Wrap
285 0         0 my @paragraphs = split /\n/ => $text;
286 0         0 my @output;
287              
288 0         0 foreach my $paragraph (@paragraphs) {
289 0         0 my @paragraph_output = ('');
290 0         0 my @words = split /\s+/ => $paragraph;
291              
292 0         0 foreach my $word (@words) {
293 0         0 my $padded = $word . q[ ];
294 0         0 my $candidate = $paragraph_output[-1] . $padded;
295 0         0 my ($w) = @{ SDL::TTF::size_utf8($font, $candidate) };
  0         0  
296 0 0       0 if ($w < $maxlen) {
297 0         0 $paragraph_output[-1] = $candidate;
298             }
299             else {
300 0         0 push @paragraph_output, $padded;
301             }
302             }
303 0 0       0 chop $paragraph_output[-1] if substr( $paragraph_output[-1], -1, 1 ) eq q[ ];
304              
305 0         0 push @output, \@paragraph_output;
306              
307             }
308              
309             return join "\n" => map {
310 0         0 join "\n" => @$_
  0         0  
311             } @output;
312             }
313              
314             sub surface {
315 1     1 0 4 return $_[0]->{surface};
316             }
317              
318             sub write_to {
319 0     0 0   my ($self, $target, $text) = @_;
320              
321 0 0         if (@_ > 2) {
322 0           $self->text($text);
323 0           $self->{_update_surfaces} = 0;
324             }
325 0           $self->write_xy($target, $self->{x}, $self->{y});
326             }
327              
328             sub write_xy {
329 0     0 0   my ($self, $target, $x, $y, $text) = @_;
330              
331 0 0         if (@_ > 4) {
    0          
332 0           $self->text($text);
333 0           $self->{_update_surfaces} = 0;
334             }
335             elsif ($self->{_update_surfaces}) {
336 0           $self->text( $self->text );
337 0           $self->{_update_surfaces} = 0;
338             }
339              
340 0 0         if ( my $surfaces = $self->{surface} ) {
341              
342 0 0         $surfaces = [ $surfaces ] unless ref $surfaces eq 'ARRAY';
343 0           my $linebreaks = 0;
344              
345 0           foreach my $i ( 0 .. $#{$surfaces}) {
  0            
346 0 0         if (my $surface = $surfaces->[$i]) {
347 0           $y += ($linebreaks * $surface->h);
348 0           $linebreaks = 0;
349              
350 0 0         if ($self->{h_align} eq 'center' ) {
    0          
351             # $x = ($target->w / 2) - ($surface->w / 2);
352 0           $x -= $surface->w / 2;
353             }
354             elsif ($self->{h_align} eq 'right' ) {
355             # $x = $target->w - $surface->w;
356 0           $x -= $surface->w;
357             }
358              
359             # blit the shadow
360 0 0         if ($self->{shadow}) {
361 0           my $shadow = $self->{_shadow_surface}->[$i];
362 0           my $offset = $self->{shadow_offset};
363              
364 0           SDL::Video::blit_surface(
365             $shadow, SDL::Rect->new(0,0,$shadow->w, $shadow->h),
366             $target, SDL::Rect->new($x + $offset, $y + $offset, 0, 0)
367             );
368             }
369              
370             # blit the text
371             SDL::Video::blit_surface(
372 0           $surface, SDL::Rect->new(0,0,$surface->w, $surface->h),
373             $target, SDL::Rect->new($x, $y, 0, 0)
374             );
375             }
376 0           $linebreaks++;
377             }
378              
379             }
380 0           return;
381             }
382              
383             1;