fix a fence post error validating a combing mode number
[imager.git] / FT2 / FT2.pm
1 package Imager::Font::FT2;
2 use strict;
3 use Imager;
4 use Scalar::Util ();
5 use vars qw($VERSION @ISA);
6 @ISA = qw(Imager::Font);
7
8 BEGIN {
9   $VERSION = "0.95";
10
11   require XSLoader;
12   XSLoader::load('Imager::Font::FT2', $VERSION);
13 }
14
15 *_first = \&Imager::Font::_first;
16
17 sub new {
18   my $class = shift;
19   my %hsh=(color=>Imager::Color->new(255,0,0,255),
20            size=>15,
21            @_);
22
23   unless ($hsh{file}) {
24     $Imager::ERRSTR = "No font file specified";
25     return;
26   }
27   unless (-e $hsh{file}) {
28     $Imager::ERRSTR = "Font file $hsh{file} not found";
29     return;
30   }
31   unless ($Imager::formats{ft2}) {
32     $Imager::ERRSTR = "Freetype2 not supported in this build";
33     return;
34   }
35   my $id = i_ft2_new($hsh{file}, $hsh{'index'} || 0);
36   unless ($id) { # the low-level code may miss some error handling
37     $Imager::ERRSTR = Imager::_error_as_msg();
38     return;
39   }
40   return bless {
41                 id       => $id,
42                 aa       => $hsh{aa} || 0,
43                 file     => $hsh{file},
44                 type     => 't1',
45                 size     => $hsh{size},
46                 color    => $hsh{color},
47                 utf8     => $hsh{utf8},
48                 vlayout  => $hsh{vlayout},
49                }, $class;
50 }
51
52 sub _draw {
53   my $self = shift;
54
55   $self->_valid
56     or return;
57
58   my %input = @_;
59   if (exists $input{channel}) {
60     i_ft2_cp($self->{id}, $input{image}{IMG}, $input{'x'}, $input{'y'},
61              $input{channel}, $input{size}, $input{sizew} || 0,
62              $input{string}, , $input{align}, $input{aa}, $input{vlayout},
63              $input{utf8});
64   } else {
65     i_ft2_text($self->{id}, $input{image}{IMG},
66                $input{'x'}, $input{'y'},
67                $input{color}, $input{size}, $input{sizew} || 0,
68                $input{string}, $input{align}, $input{aa}, $input{vlayout},
69                $input{utf8});
70   }
71 }
72
73 sub _bounding_box {
74   my $self = shift;
75   my %input = @_;
76
77   $self->_valid
78     or return;
79
80   my @result =  i_ft2_bbox($self->{id}, $input{size}, $input{sizew},
81                            $input{string}, $input{utf8});
82   unless (@result) {
83     Imager->_set_error(Imager->_error_as_msg);
84     return;
85   }
86
87   return @result;
88 }
89
90 sub dpi {
91   my $self = shift;
92
93   $self->_valid
94     or return;
95
96   my @old = i_ft2_getdpi($self->{id});
97   if (@_) {
98     my %hsh = @_;
99     my $result;
100     unless ($hsh{xdpi} && $hsh{ydpi}) {
101       if ($hsh{dpi}) {
102         $hsh{xdpi} = $hsh{ydpi} = $hsh{dpi};
103       }
104       else {
105         $Imager::ERRSTR = "dpi method requires xdpi and ydpi or just dpi";
106         return;
107       }
108       i_ft2_setdpi($self->{id}, $hsh{xdpi}, $hsh{ydpi}) or return;
109     }
110   }
111   
112   return @old;
113 }
114
115 sub hinting {
116   my ($self, %opts) = @_;
117
118   $self->_valid
119     or return;
120
121   i_ft2_sethinting($self->{id}, $opts{hinting} || 0);
122 }
123
124 sub _transform {
125   my $self = shift;
126
127   $self->_valid
128     or return;
129
130   my %hsh = @_;
131   my $matrix = $hsh{matrix} or return undef;
132
133   return i_ft2_settransform($self->{id}, $matrix)
134 }
135
136 sub utf8 {
137   return 1;
138 }
139
140 # check if the font has the characters in the given string
141 sub has_chars {
142   my ($self, %hsh) = @_;
143
144   $self->_valid
145     or return;
146
147   unless (defined $hsh{string} && length $hsh{string}) {
148     $Imager::ERRSTR = "No string supplied to \$font->has_chars()";
149     return;
150   }
151   if (wantarray) {
152     my @result =  i_ft2_has_chars($self->{id}, $hsh{string}, 
153                                   _first($hsh{'utf8'}, $self->{utf8}, 0));
154     unless (@result) {
155       Imager->_set_error(Imager->_error_as_msg);
156       return;
157     }
158
159     return @result;
160   }
161   else {
162     my $result =  i_ft2_has_chars($self->{id}, $hsh{string}, 
163                                   _first($hsh{'utf8'}, $self->{utf8}, 0));
164     unless (defined $result) {
165       Imager->_set_error(Imager->_error_as_msg);
166       return;
167     }
168     return $result;
169   }
170 }
171
172 sub face_name {
173   my ($self) = @_;
174
175   $self->_valid
176     or return;
177
178   i_ft2_face_name($self->{id});
179 }
180
181 sub can_glyph_names {
182   my ($self) = @_;
183
184   i_ft2_can_do_glyph_names()
185     or return;
186
187   if (ref $self) {
188     $self->_valid
189       or return;
190
191     i_ft2_face_has_glyph_names($self->{id})
192       or return;
193   }
194
195   return 1;
196 }
197
198 sub glyph_names {
199   my ($self, %input) = @_;
200
201   $self->_valid
202     or return;
203
204   my $string = $input{string};
205   defined $string
206     or return Imager->_set_error("no string parameter passed to glyph_names");
207   my $utf8 = _first($input{utf8}, 0);
208   my $reliable_only = _first($input{reliable_only}, 1);
209
210   my @names = i_ft2_glyph_name($self->{id}, $string, $utf8, $reliable_only);
211   @names or return Imager->_set_error(Imager->_error_as_msg);
212
213   return @names if wantarray;
214   return pop @names;
215 }
216
217 sub is_mm {
218   my ($self) = @_;
219
220   $self->_valid
221     or return;
222
223   i_ft2_is_multiple_master($self->{id});
224 }
225
226 sub mm_axes {
227   my ($self) = @_;
228
229   $self->_valid
230     or return;
231
232   my ($num_axis, $num_design, @axes) =
233     i_ft2_get_multiple_masters($self->{id})
234       or return Imager->_set_error(Imager->_error_as_msg);
235
236   return @axes;
237 }
238
239 sub set_mm_coords {
240   my ($self, %opts) = @_;
241
242   $self->_valid
243     or return;
244
245   $opts{coords}
246     or return Imager->_set_error("Missing coords parameter");
247   ref($opts{coords}) && $opts{coords} =~ /ARRAY\(0x[\da-f]+\)$/
248     or return Imager->_set_error("coords parameter must be an ARRAY ref");
249
250   i_ft2_set_mm_coords($self->{id}, @{$opts{coords}})
251     or return Imager->_set_error(Imager->_error_as_msg);
252
253   return 1;
254 }
255
256 # objects may be invalidated on thread creation (or Win32 fork emulation)
257 sub _valid {
258   my $self = shift;
259
260   unless ($self->{id} && Scalar::Util::blessed($self->{id})) {
261     Imager->_set_error("font object was created in another thread");
262     return;
263   }
264
265   return 1;
266 }
267
268 1;
269
270 __END__
271
272 =head1 NAME
273
274 Imager::Font::FT2 - font support using FreeType 2
275
276 =head1 SYNOPSIS
277
278   use Imager;
279
280   my $img = Imager->new;
281   my $font = Imager::Font->new(file => "foo.ttf", type => "ft2");
282
283   $img->string(... font => $font);
284
285 =head1 DESCRIPTION
286
287 This provides font support on FreeType 2.
288
289 =head1 CAVEATS
290
291 Unfortunately, older versions of Imager would install
292 C<Imager::Font::FreeType2> even if FreeType 2 wasn't available, and if
293 no font was created would succeed in loading the module.  This means
294 that an existing C<FreeType2.pm> could cause a probe success for
295 supported font files, so I've renamed it.
296
297 =head1 AUTHOR
298
299 Tony Cook <tonyc@cpan.org>
300
301 =head1 SEE ALSO
302
303 Imager, Imager::Font.
304
305 =cut