Initial Commit

This commit is contained in:
Riley Schneider
2025-12-03 16:38:10 +01:00
parent c5e26bf594
commit b732d8d4b5
17680 changed files with 5977495 additions and 2 deletions

View File

@@ -0,0 +1,282 @@
package Imager::Font::BBox;
use 5.006;
use strict;
our $VERSION = "1.007";
=head1 NAME
Imager::Font::BBox - objects representing the bounding box of a string.
=head1 SYNOPSIS
use Imager::Font;
# get the object
my $font = Imager::Font->new(...);
my $bbox = $font->bounding_box(string=>$text, size=>$size);
# methods
my $start = $bbox->start_offset;
my $left_bearing = $bbox->left_bearing;
my $right_bearing = $bbox->right_bearing;
my $end = $bbox->end_offset;
my $gdescent = $box->global_descent;
my $gascent = $bbox->global_ascent;
my $ascent = $bbox->ascent;
my $decent = $bbox->descent;
my $total_width = $bbox->total_width;
my $fheight = $bbox->font_height;
my $theight = $bbox->text_height;
my $display_width = $bbox->display_width;
=head1 DESCRIPTION
Objects of this class are returned by the Imager::Font bounding_box()
method when it is called in scalar context.
This will hopefully make the information from this method more
accessible.
=head1 METHODS
=over
=item start_offset()
=item neg_width
=item left_bearing
Returns the horizontal offset from the selected drawing location to
the left edge of the first character drawn. If this is positive, the
first glyph is to the right of the drawing location.
The alias neg_width() is present to match the bounding_box()
documentation for list context.
The alias left_bearing() is present to match font terminology.
=cut
sub start_offset {
return $_[0][0];
}
sub neg_width {
return $_[0][0];
}
sub left_bearing {
return $_[0][0];
}
=item advance_width()
The advance width of the string, if the driver supports that,
otherwise the same as end_offset.
=cut
sub advance_width {
my $self = shift;
@$self > 6 ? $self->[6] : $self->[2];
}
=item right_bearing
The distance from the right of the last glyph to the end of the advance
point.
If the glyph overflows the right side of the advance width this value
is negative.
=cut
sub right_bearing {
my $self = shift;
@$self >= 8 && return $self->[7]; # driver gives it to us
# otherwise the closest we have is the difference between the
# end_pos and advance_width
return $self->advance_width - $self->pos_width;
}
=item display_width
The distance from the left-most pixel of the left-most glyph to the
right-most pixel of the right-most glyph.
Equals advance_width - left_bearing - right_bearing (and implemented
that way.)
=cut
sub display_width {
my ($self) = @_;
$self->advance_width - $self->left_bearing - $self->right_bearing;
}
=item global_descent()
The lowest position relative to the font baseline that any character
in the font reaches in the character cell. Normally negative.
At least one font we've seen has reported a positive number for this.
=cut
sub global_descent {
return $_[0][1];
}
=item global_ascent()
The highest position relative to the font baseline that any character
in the font reaches in the character cell. Normally positive.
=cut
sub global_ascent {
return $_[0][3];
}
=item descent()
The lowest position relative to the font baseline that any character
in the supplied string reaches. Negative when any character's glyph
reaches below the baseline.
=cut
sub descent {
return $_[0][4];
}
=item ascent()
The highest position relative to the font baseline that any character
in the supplied string reaches. Positive if any character's glyph
reaches above the baseline.
=cut
sub ascent {
return $_[0][5];
}
=item font_height()
The maximum displayed height of any string using this font.
=cut
sub font_height {
my $self = shift;
$self->global_ascent - $self->global_descent;
}
=item text_height()
The displayed height of the supplied string.
=cut
sub text_height {
my $self = shift;
$self->ascent - $self->descent;
}
=back
=head1 OBSOLETE METHODS
These methods include bugs kept for backwards compatibility and
shouldn't be used in new code.
=over
=item total_width()
The total displayed width of the string.
New code should use display_width().
This depends on end_offset(), and is limited by it's backward
compatibility.
=cut
sub total_width {
my $self = shift;
$self->end_offset - $self->start_offset;
}
=item end_offset
=item pos_width
The offset from the selected drawing location to the right edge of the
last character drawn. Should always be positive.
You can use the alias pos_width() if you are used to the
bounding_box() documentation for list context.
For backwards compatibility this method returns the maximum of the
advance width and the offset of the right edge of the last glyph.
=cut
sub end_offset {
return $_[0][2];
}
sub pos_width {
return $_[0][2];
}
=back
=head1 INTERNAL FUNCTIONS
=over
=item new(...)
Called by Imager::Font->bounding_box() to create the object.
=cut
sub new {
my $class = shift;
return bless [ @_ ], $class;
}
=back
=head1 BUGS
Doesn't reproduce the functionality that you get using the x and y
parameters to Imager::Font->bounding_box(). I considered:
my ($left, $top, $right, $bottom) = $box->offset(x=>$x, y=>$y)
but this is about as clumsy as the original.
=head1 AUTHOR
Tony Cook <tony@develop-help.com>
=head1 SEE ALSO
Imager(3), Imager::Font(3)
=cut
1;

View File

@@ -0,0 +1,307 @@
package Imager::Font::FT2;
use 5.006;
use strict;
use Imager;
use Scalar::Util ();
our @ISA = qw(Imager::Font);
our $VERSION;
BEGIN {
$VERSION = "0.98";
require XSLoader;
XSLoader::load('Imager::Font::FT2', $VERSION);
}
*_first = \&Imager::Font::_first;
sub new {
my $class = shift;
my %hsh=(color=>Imager::Color->new(255,0,0,255),
size=>15,
@_);
unless ($hsh{file}) {
$Imager::ERRSTR = "No font file specified";
return;
}
unless (-e $hsh{file}) {
$Imager::ERRSTR = "Font file $hsh{file} not found";
return;
}
unless ($Imager::formats{ft2}) {
$Imager::ERRSTR = "Freetype2 not supported in this build";
return;
}
my $id = i_ft2_new($hsh{file}, $hsh{'index'} || 0);
unless ($id) { # the low-level code may miss some error handling
$Imager::ERRSTR = Imager::_error_as_msg();
return;
}
return bless {
id => $id,
aa => $hsh{aa} || 0,
file => $hsh{file},
type => 't1',
size => $hsh{size},
color => $hsh{color},
utf8 => $hsh{utf8},
vlayout => $hsh{vlayout},
}, $class;
}
sub _draw {
my $self = shift;
$self->_valid
or return;
my %input = @_;
if (exists $input{channel}) {
i_ft2_cp($self->{id}, $input{image}{IMG}, $input{'x'}, $input{'y'},
$input{channel}, $input{size}, $input{sizew} || 0,
$input{string}, , $input{align}, $input{aa}, $input{vlayout},
$input{utf8});
} else {
i_ft2_text($self->{id}, $input{image}{IMG},
$input{'x'}, $input{'y'},
$input{color}, $input{size}, $input{sizew} || 0,
$input{string}, $input{align}, $input{aa}, $input{vlayout},
$input{utf8});
}
}
sub _bounding_box {
my $self = shift;
my %input = @_;
$self->_valid
or return;
my @result = i_ft2_bbox($self->{id}, $input{size}, $input{sizew},
$input{string}, $input{utf8});
unless (@result) {
Imager->_set_error(Imager->_error_as_msg);
return;
}
return @result;
}
sub dpi {
my $self = shift;
$self->_valid
or return;
my @old = i_ft2_getdpi($self->{id});
if (@_) {
my %hsh = @_;
my $result;
unless ($hsh{xdpi} && $hsh{ydpi}) {
if ($hsh{dpi}) {
$hsh{xdpi} = $hsh{ydpi} = $hsh{dpi};
}
else {
$Imager::ERRSTR = "dpi method requires xdpi and ydpi or just dpi";
return;
}
i_ft2_setdpi($self->{id}, $hsh{xdpi}, $hsh{ydpi}) or return;
}
}
return @old;
}
sub hinting {
my ($self, %opts) = @_;
$self->_valid
or return;
i_ft2_sethinting($self->{id}, $opts{hinting} || 0);
}
sub _transform {
my $self = shift;
$self->_valid
or return;
my %hsh = @_;
my $matrix = $hsh{matrix} or return undef;
return i_ft2_settransform($self->{id}, $matrix)
}
sub utf8 {
return 1;
}
# check if the font has the characters in the given string
sub has_chars {
my ($self, %hsh) = @_;
$self->_valid
or return;
unless (defined $hsh{string} && length $hsh{string}) {
$Imager::ERRSTR = "No string supplied to \$font->has_chars()";
return;
}
if (wantarray) {
my @result = i_ft2_has_chars($self->{id}, $hsh{string},
_first($hsh{'utf8'}, $self->{utf8}, 0));
unless (@result) {
Imager->_set_error(Imager->_error_as_msg);
return;
}
return @result;
}
else {
my $result = i_ft2_has_chars($self->{id}, $hsh{string},
_first($hsh{'utf8'}, $self->{utf8}, 0));
unless (defined $result) {
Imager->_set_error(Imager->_error_as_msg);
return;
}
return $result;
}
}
sub face_name {
my ($self) = @_;
$self->_valid
or return;
i_ft2_face_name($self->{id});
}
sub can_glyph_names {
my ($self) = @_;
i_ft2_can_do_glyph_names()
or return;
if (ref $self) {
$self->_valid
or return;
i_ft2_face_has_glyph_names($self->{id})
or return;
}
return 1;
}
sub glyph_names {
my ($self, %input) = @_;
$self->_valid
or return;
my $string = $input{string};
defined $string
or return Imager->_set_error("no string parameter passed to glyph_names");
my $utf8 = _first($input{utf8}, 0);
my $reliable_only = _first($input{reliable_only}, 1);
my @names = i_ft2_glyph_name($self->{id}, $string, $utf8, $reliable_only);
@names or return Imager->_set_error(Imager->_error_as_msg);
return @names if wantarray;
return pop @names;
}
sub is_mm {
my ($self) = @_;
$self->_valid
or return;
i_ft2_is_multiple_master($self->{id});
}
sub mm_axes {
my ($self) = @_;
$self->_valid
or return;
my ($num_axis, $num_design, @axes) =
i_ft2_get_multiple_masters($self->{id})
or return Imager->_set_error(Imager->_error_as_msg);
return @axes;
}
sub set_mm_coords {
my ($self, %opts) = @_;
$self->_valid
or return;
$opts{coords}
or return Imager->_set_error("Missing coords parameter");
ref($opts{coords}) && $opts{coords} =~ /ARRAY\(0x[\da-f]+\)$/
or return Imager->_set_error("coords parameter must be an ARRAY ref");
i_ft2_set_mm_coords($self->{id}, @{$opts{coords}})
or return Imager->_set_error(Imager->_error_as_msg);
return 1;
}
# objects may be invalidated on thread creation (or Win32 fork emulation)
sub _valid {
my $self = shift;
unless ($self->{id} && Scalar::Util::blessed($self->{id})) {
Imager->_set_error("font object was created in another thread");
return;
}
return 1;
}
1;
__END__
=head1 NAME
Imager::Font::FT2 - font support using FreeType 2
=head1 SYNOPSIS
use Imager;
my $img = Imager->new;
my $font = Imager::Font->new(file => "foo.ttf", type => "ft2");
$img->string(... font => $font);
=head1 DESCRIPTION
This provides font support on FreeType 2.
=head1 CAVEATS
Unfortunately, older versions of Imager would install
C<Imager::Font::FreeType2> even if FreeType 2 wasn't available, and if
no font was created would succeed in loading the module. This means
that an existing C<FreeType2.pm> could cause a probe success for
supported font files, so I've renamed it.
=head1 AUTHOR
Tony Cook <tonyc@cpan.org>
=head1 SEE ALSO
Imager, Imager::Font.
=cut

View File

@@ -0,0 +1,71 @@
package Imager::Font::FreeType2;
use 5.006;
use strict;
use Imager::Font::FT2;
our @ISA = qw(Imager::Font::FT2);
our $VERSION = "1.021";
1;
__END__
=head1 NAME
Imager::Font::FreeType2 - low-level functions for FreeType2 text output
=head1 DESCRIPTION
Imager::Font creates a Imager::Font::FreeType2 object when asked to.
See Imager::Font to see how to use this type.
This class provides low-level functions that require the caller to
perform data validation.
This driver supports:
=over
=item transform()
=item dpi()
=item draw()
The following parameters:
=over
=item *
C<utf8>
=item *
C<vlayout>
=item *
C<sizew>
=back
=back
=head2 Special behaviors
If you call transform() to set a transformation matrix, hinting will
be switched off. This prevents sudden jumps in the size of the text
caused by the hinting when the transformation is the identity matrix.
If for some reason you want hinting enabled, use
$font->hinting(hinting=>1) to re-enable hinting. This will need to be
called after I<each> call to transform().
=head1 AUTHOR
Addi, Tony
=cut

View File

@@ -0,0 +1,168 @@
package Imager::Font::Image;
use 5.006;
use strict;
use Imager::Color;
use File::Basename;
use File::Spec;
our @ISA = qw(Imager::Font);
our $VERSION = "1.000";
sub NWIDTH () { 0 }
sub PWIDTH () { 2 }
sub GDESCENT () { 1 }
sub GASCENT () { 3 }
sub DESCENT () { 4 }
sub ASCENT () { 5 }
our %REQUIRED_FIELDS = (
Image_spec => 1,
Font_size => 1,
Global_ascent => 1,
Global_descent => 1,);
# Required fields
# Fontmetrics:
# Font global data:
# image name
# font size
# max glyph height
# max glyph width
#
# The per character data is:
# left edge (inclusive)
# right edge (exclusive)
# top edge (inclusive)
# bottom edge (exclusive)
# left adjustment
# forward shift
# baseline adjustment (from top)
#
# The left adjustment is the starting
# offset into the glyph, the forward shift
# is the actual forward movement of the
# imaginary cursor.
# To calculate the size of a string use:
# sum (forward_shift_i) + left_adjustment_0 + width_last - left_adjustment_last - forward_shift_last
# example font spec file:
# IAGRFONT
# # This is an imager font definition file. This is a comment
# Image_spec = foo.png
# Font_size = 12
# Global_ascent = 10
# Global_descent = -2
# # Per character data
# FM_65 = 20 40 30 50 3 15
# # Code for 'A' left edge = 20, right = 40, top = 30, bottom 50, leading = 3, forward = 15.
# The left adjustment is the starting
# offset into the glyph, the forward shift
# is the actual forward movement of the
# imaginary cursor.
# To calculate the size of a string use:
# sum (forward_shift_i) + left_adjustment_0 + width_last - left_adjustment_last - forward_shift_last
sub parse_fontspec_file {
my ($self, $file) = @_;
local *FH;
return unless open(FH, "<$file");
my %req = %REQUIRED_FIELDS;
while(<FH>) {
next if m/^\#/;
if (m/^\s*?(\S+?)\s*=\s*(.+?)\s*$/) {
# Check for a required field:
my $char = $1;
my $metric = $2;
if ($req{$char}) {
$self->{$char} = $metric;
delete $req{$1};
} else {
next unless $char =~ s/^FM_(\d+)$/$1/;
next unless $metric =~ m/(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/;
$self->{fm}->{$char} = [$1, $2, $3, $4, $5, $6];
}
}
}
close(FH);
return $self;
}
sub new {
my $self = bless {}, shift;
my %opts = (color=>Imager::Color->new(255, 0, 0, 0), @_);
unless ($opts{file}) {
$Imager::ERRSTR = "No font file specified";
return;
}
unless ($self->parse_fontspec_file($opts{file})) {
$Imager::ERRSTR = "Font file $opts{file} not found or bad";
return;
}
my $img = Imager->new();
my $img_filename = File::Spec->catfile( dirname($opts{'file'}),
$self->{Image_spec} );
unless ($img->open(%opts, file=>$img_filename)) {
$Imager::ERRSTR = "Font IMAGE file $img_filename not found or bad: ".
$img->errstr();
return;
}
$self->{image} = $img;
$self->{size} = $self->{Font_size};
return $self;
}
sub get_glyph_data {
my ($self, $glyph_code) = @_;
return unless exists $self->{fm}->{$glyph_code};
return @{$self->{fm}->{$glyph_code}};
}
# copy_glyph
#
# $x, $y is left, baseline for glyphs.
#
sub copy_glyph {
my ($self, $glyph_code, $target_img, $x, $y) = @_;
my @gdata = $self->get_glyph_data($glyph_code) or return;
$target_img->rubthrough(src=>$self->{image},
tx => $x + $gdata[4],
ty => $y - $self->{Global_ascent},,
src_minx => $gdata[0],
src_maxx => $gdata[1],
src_miny => $gdata[2],
src_maxy => $gdata[3]);
}
sub _draw {
my ($self, %opts) = @_;
my $x = $opts{'x'};
my $y = $opts{'y'};
my @glyphs = unpack("C*", $opts{string});
my $img = $opts{image};
my $glyph;
for $glyph (@glyphs) {
my @gmetrics = $self->get_glyph_data($glyph) or next;
$self->copy_glyph($glyph, $img, $x, $y);
$x += $gmetrics[5];
}
}

View File

@@ -0,0 +1,313 @@
package Imager::Font::T1;
use 5.006;
use strict;
use Imager::Color;
our @ISA = qw(Imager::Font);
use Scalar::Util ();
BEGIN {
our $VERSION = "1.027";
require XSLoader;
XSLoader::load('Imager::Font::T1', $VERSION);
}
*_first = \&Imager::Font::_first;
my $t1aa = 2;
sub new {
my $class = shift;
my %hsh=(color=>Imager::Color->new(255,0,0,255),
size=>15,
@_);
unless ($hsh{file}) {
$Imager::ERRSTR = "No font file specified";
return;
}
unless (-e $hsh{file}) {
$Imager::ERRSTR = "Font file $hsh{file} not found";
return;
}
unless ($Imager::formats{t1}) {
$Imager::ERRSTR = "Type 1 fonts not supported in this build";
return;
}
# we want to avoid T1Lib's file search mechanism
unless ($hsh{file} =~ m!^/!
|| $hsh{file} =~ m!^\.\/?/!
|| $^O =~ /^(MSWin32|cygwin)$/ && $hsh{file} =~ /^[a-z]:/i) {
$hsh{file} = './' . $hsh{file};
}
if($hsh{afm}) {
unless (-e $hsh{afm}) {
$Imager::ERRSTR = "Afm file $hsh{afm} not found";
return;
}
unless ($hsh{afm} =~ m!^/!
|| $hsh{afm} =~ m!^\./!
|| $^O =~ /^(MSWin32|cygwin)$/ && $hsh{file} =~ /^[a-z]:/i) {
$hsh{file} = './' . $hsh{file};
}
} else {
$hsh{afm} = 0;
}
my $font = Imager::Font::T1xs->new($hsh{file},$hsh{afm});
unless ($font) { # the low-level code may miss some error handling
Imager->_set_error(Imager->_error_as_msg);
return;
}
return bless {
t1font => $font,
aa => $hsh{aa} || 0,
file => $hsh{file},
type => 't1',
size => $hsh{size},
color => $hsh{color},
t1aa => $t1aa,
}, $class;
}
sub _draw {
my $self = shift;
$self->_valid
or return;
my %input = @_;
my $flags = '';
$flags .= 'u' if $input{underline};
$flags .= 's' if $input{strikethrough};
$flags .= 'o' if $input{overline};
my $aa = $input{aa} ? $self->{t1aa} : 0;
if (exists $input{channel}) {
$self->{t1font}->cp($input{image}{IMG}, $input{'x'}, $input{'y'},
$input{channel}, $input{size},
$input{string}, $input{align},
$input{utf8}, $flags, $aa)
or return;
} else {
$self->{t1font}->text($input{image}{IMG}, $input{'x'}, $input{'y'},
$input{color}, $input{size},
$input{string}, $input{align}, $input{utf8}, $flags, $aa)
or return;
}
return $self;
}
sub _bounding_box {
my $self = shift;
$self->_valid
or return;
my %input = @_;
my $flags = '';
$flags .= 'u' if $input{underline};
$flags .= 's' if $input{strikethrough};
$flags .= 'o' if $input{overline};
my @bbox = $self->{t1font}->bbox($input{size}, $input{string},
$input{utf8}, $flags);
unless (@bbox) {
Imager->_set_error(Imager->_error_as_msg);
return;
}
return @bbox;
}
# check if the font has the characters in the given string
sub has_chars {
my ($self, %hsh) = @_;
$self->_valid
or return;
unless (defined $hsh{string}) {
$Imager::ERRSTR = "No string supplied to \$font->has_chars()";
return;
}
if (wantarray) {
my @result = $self->{t1font}
->has_chars($hsh{string}, _first($hsh{'utf8'}, $self->{utf8}, 0));
unless (@result) {
Imager->_set_error(Imager->_error_as_msg);
return;
}
return @result;
}
else {
my $result = $self->{t1font}
->has_chars($hsh{string}, _first($hsh{'utf8'}, $self->{utf8}, 0));
unless (defined $result) {
Imager->_set_error(Imager->_error_as_msg);
return;
}
return $result;
}
}
sub utf8 {
1;
}
sub can_glyph_names {
1;
}
sub face_name {
my ($self) = @_;
$self->_valid
or return;
return $self->{t1font}->face_name();
}
sub glyph_names {
my ($self, %input) = @_;
$self->_valid
or return;
my $string = $input{string};
defined $string
or return Imager->_set_error("no string parameter passed to glyph_names");
my $utf8 = _first($input{utf8} || 0);
my @result = $self->{t1font}->glyph_names($string, $utf8);
unless (@result) {
Imager->_set_error(Imager->_error_as_msg);
return;
}
return @result;
}
sub set_aa_level {
my ($self, $new_t1aa) = @_;
if (!defined $new_t1aa ||
($new_t1aa != 1 && $new_t1aa != 2)) {
Imager->_set_error("set_aa_level: parameter must be 1 or 2");
return;
}
if (ref $self) {
$self->_valid
or return;
$self->{t1aa} = $new_t1aa;
}
else {
$t1aa = $new_t1aa;
}
return 1;
}
sub _valid {
my $self = shift;
unless ($self->{t1font} && Scalar::Util::blessed($self->{t1font})) {
Imager->_set_error("font object was created in another thread");
return;
}
return 1;
}
1;
__END__
=head1 NAME
Imager::Font::Type1 - low-level functions for Type1 fonts
=head1 DESCRIPTION
=for stopwords Freetype
Imager::Font::T1 is deprecated.
F<T1Lib> is unmaintained and has serious bugs when built on 64-bit
systems. Freetype 2 has Type 1 font support and is supported by
Imager via L<Imager::Font::FT2>.
L<Imager::Font> creates a C<Imager::Font::Type1 object> when asked to create
a font object based on a C<.pfb> file.
See Imager::Font to see how to use this type.
This class provides low-level functions that require the caller to
perform data validation
By default Imager no longer creates the F<t1lib.log> log file. You
can re-enable that by calling Imager::init() with the C<t1log> option:
Imager::init(t1log=>1);
This must be called before creating any fonts.
Currently specific to Imager::Font::Type1, you can use the following
flags when drawing text or calculating a bounding box:
=for stopwords overline strikethrough
=over
=item *
C<underline> - Draw the text with an underline.
=item *
C<overline> - Draw the text with an overline.
=item *
C<strikethrough> - Draw the text with a strikethrough.
=back
Obviously, if you're calculating the bounding box the size of the line
is included in the box, and the line isn't drawn :)
=head2 Anti-aliasing
T1Lib supports multiple levels of anti-aliasing, by default, if you
request anti-aliased output, Imager::Font::T1 will use the maximum
level.
You can override this with the set_t1_aa() method:
=over
=item set_aa_level()
Usage:
$font->set_aa_level(1);
Imager::Font::T1->set_aa_level(2);
Sets the T1Lib anti-aliasing level either for the specified font, or
for new font objects.
The only parameter must be 1 or 2.
Returns true on success.
=back
=head1 AUTHOR
Addi, Tony
=cut

View File

@@ -0,0 +1,95 @@
package Imager::Font::Test;
use 5.006;
use strict;
our $VERSION = "1.002";
use base 'Imager::Font';
sub new {
my ($class, %opts) = @_;
bless \%opts, shift;
}
sub _draw {
my ($self, %input) = @_;
my $text = $input{string};
my $ppn = int($input{size} * 0.5 + 0.5);
my $desc = int($input{size} * 0.3 + 0.5);
my $asc = $input{size} - $desc;
my $width = $ppn * length $text;
my $x = $input{x};
my $y = $input{'y'};
$input{align} and $y -= $asc;
$input{image}->box(color => $input{color}, xmin => $x, ymin => $y,
xmax => $x + $width-1, ymax => $y + $input{size} - 1);
return 1;
}
sub _bounding_box {
my ($self, %input) = @_;
my $text = $input{string};
my $ppn = int($input{size} * 0.5 + 0.5);
my $desc = int($input{size} * 0.3 + 0.5);
my $asc = $input{size} - $desc;
return ( 0, -$desc, $ppn * length $text, $asc, -$desc, $asc, $ppn * length $text, 0 );
}
sub has_chars {
my ($self, %input) = @_;
my $text = $input{string};
defined $text
or return Imager->_set_error("has_chars: No string parameter supplied");
return (1) x length $text;
}
sub face_name {
"test";
}
sub glyph_names {
my ($self, %input) = @_;
my $text = $input{string};
defined $text
or return Imager->_set_error("glyph_names: No string parameter supplied");
return (1) x length $text;
}
1;
=head1 NAME
Imager::Font::Test - font driver producing consistent output for tests.
=head1 SYNOPSIS
my $font = Imager::Font::Test->new;
# use $font where you use other fonts
=head1 DESCRIPTION
Imager::Font::Test is intended to produce consistent output without
being subject to the inconsistent output produced by different
versions of font libraries.
The output is simple box for the whole string.
=head1 AUTHOR
Tony Cook <tonyc@cpan.org>
=cut

View File

@@ -0,0 +1,155 @@
package Imager::Font::Truetype;
use 5.006;
use strict;
our @ISA = qw(Imager::Font);
our $VERSION = "1.013";
*_first = \&Imager::Font::_first;
sub new {
my $class = shift;
my %hsh=(color=>Imager::Color->new(255,0,0,255),
size=>15,
@_);
unless ($hsh{file}) {
$Imager::ERRSTR = "No font file specified";
return;
}
unless (-e $hsh{file}) {
$Imager::ERRSTR = "Font file $hsh{file} not found";
return;
}
unless ($Imager::formats{tt}) {
$Imager::ERRSTR = "Type 1 fonts not supported in this build";
return;
}
my $id = Imager::i_tt_new($hsh{file});
unless ($id) { # the low-level code may miss some error handling
$Imager::ERRSTR = Imager::_error_as_msg();
return;
}
return bless {
id => $id,
aa => $hsh{aa} || 0,
file => $hsh{file},
type => 'tt',
size => $hsh{size},
color => $hsh{color},
}, $class;
}
sub _draw {
my $self = shift;
my %input = @_;
if ( exists $input{channel} ) {
Imager::i_tt_cp($self->{id},$input{image}{IMG},
$input{'x'}, $input{'y'}, $input{channel}, $input{size},
$input{string}, $input{aa},
$input{utf8}, $input{align});
} else {
Imager::i_tt_text($self->{id}, $input{image}{IMG},
$input{'x'}, $input{'y'}, $input{color},
$input{size}, $input{string},
$input{aa}, $input{utf8},
$input{align});
}
}
sub _bounding_box {
my $self = shift;
my %input = @_;
my @result =
Imager::i_tt_bbox($self->{id}, $input{size}, $input{string}, $input{utf8});
unless (@result) {
Imager->_set_error(Imager->_error_as_msg);
return;
}
return @result;
}
sub utf8 { 1 }
# check if the font has the characters in the given string
sub has_chars {
my ($self, %hsh) = @_;
unless (defined $hsh{string}) {
$Imager::ERRSTR = "No string supplied to \$font->has_chars()";
return;
}
if (wantarray) {
my @result = Imager::i_tt_has_chars($self->{id}, $hsh{string},
_first($hsh{'utf8'}, $self->{utf8}, 0));
unless (@result) {
Imager->_set_error(Imager->_error_as_msg);
return;
}
return @result;
}
else {
my $result = Imager::i_tt_has_chars($self->{id}, $hsh{string},
_first($hsh{'utf8'}, $self->{utf8}, 0));
unless (defined $result) {
Imager->_set_error(Imager->_error_as_msg);
return;
}
return $result;
}
}
sub face_name {
my ($self) = @_;
Imager::i_tt_face_name($self->{id});
}
sub can_glyph_names {
1;
}
sub glyph_names {
my ($self, %input) = @_;
my $string = $input{string};
defined $string
or return Imager->_set_error("no string parameter passed to glyph_names");
my $utf8 = _first($input{utf8} || 0);
my @names = Imager::i_tt_glyph_name($self->{id}, $string, $utf8);
unless (@names) {
Imager->_set_error(Imager->_error_as_msg);
return;
}
return @names;
}
1;
__END__
=head1 NAME
Imager::Font::Truetype - low-level functions for Truetype fonts
=head1 DESCRIPTION
Imager::Font creates a Imager::Font::Truetype object when asked to
create a font object based on a F<.ttf> file.
See Imager::Font to see how to use this type.
This class provides low-level functions that require the caller to
perform data validation.
=head1 AUTHOR
Addi, Tony
=cut

View File

@@ -0,0 +1,27 @@
package Imager::Font::Type1;
use 5.006;
use strict;
use Imager::Font::T1;
our @ISA = qw(Imager::Font::T1);
our $VERSION = "1.013";
1;
__END__
=head1 NAME
Imager::Font::Type1 - low-level functions for T1Lib text output
=head1 DESCRIPTION
This is a simple wrapper around Imager::Font::T1 for backwards
compatibility.
=head1 AUTHOR
Tony Cook <tonyc@cpan.org>
=cut

View File

@@ -0,0 +1,103 @@
package Imager::Font::W32;
use 5.006;
use strict;
use Imager;
our @ISA = qw(Imager::Font);
BEGIN {
our $VERSION = "0.91";
require XSLoader;
XSLoader::load('Imager::Font::W32', $VERSION);
}
# called by Imager::Font::new()
# since Win32's HFONTs include the size information this
# is just a stub
sub new {
my $class = shift;
my %opts =
(
color => Imager::Color->new(255, 0, 0),
size => 15,
@_,
);
return bless \%opts, $class;
}
sub _bounding_box {
my ($self, %opts) = @_;
my @bbox = i_wf_bbox($self->{face}, $opts{size}, $opts{string}, $opts{utf8});
unless (@bbox) {
Imager->_set_error(Imager->_error_as_msg);
return;
}
return @bbox;
}
sub _draw {
my $self = shift;
my %input = @_;
if (exists $input{channel}) {
return i_wf_cp($self->{face}, $input{image}{IMG}, $input{x}, $input{'y'},
$input{channel}, $input{size},
$input{string}, $input{align}, $input{aa}, $input{utf8});
}
else {
return i_wf_text($self->{face}, $input{image}{IMG}, $input{x},
$input{'y'}, $input{color}, $input{size},
$input{string}, $input{align}, $input{aa}, $input{utf8});
}
}
sub utf8 {
return 1;
}
sub can_glyph_names {
return;
}
1;
__END__
=head1 NAME
Imager::Font::W32 - font support using C<GDI> on Win32
=head1 SYNOPSIS
use Imager;
my $img = Imager->new;
my $font = Imager::Font->new(face => "Arial", type => "w32");
$img->string(... font => $font);
=head1 DESCRIPTION
This provides font support on Win32.
=head1 CAVEATS
Unfortunately, older versions of Imager would install
Imager::Font::Win32 even if Win32 wasn't available, and if no font was
created would succeed in loading the module. This means that an
existing Win32.pm could cause a probe success for Win32 fonts, so I've
renamed it.
=head1 AUTHOR
Tony Cook <tonyc@cpan.org>
=head1 SEE ALSO
Imager, Imager::Font.
=cut

View File

@@ -0,0 +1,32 @@
package Imager::Font::Win32;
use 5.006;
use strict;
our @ISA = qw(Imager::Font::W32);
our $VERSION = "1.000";
require Imager::Font::W32;
1;
__END__
=head1 NAME
=for stopwords GDI
Imager::Font::Win32 - uses Win32 GDI services for text output
=head1 SYNOPSIS
my $font = Imager::Font->new(face=>"Arial");
=head1 DESCRIPTION
This module is obsolete.
=head1 AUTHOR
Tony Cook <tonyc@cpan.org>
=cut

View File

@@ -0,0 +1,387 @@
package Imager::Font::Wrap;
use 5.006;
use strict;
use Imager;
use Imager::Font;
our $VERSION = "1.005";
*_first = \&Imager::Font::_first;
# we can't accept the utf8 parameter, too hard at this level
# the %state contains:
# font - the font
# im - the image
# x - the left position
# w - the width
# justify - fill, left, right or center
sub _format_line {
my ($state, $spaces, $text, $fill) = @_;
$text =~ s/ +$//;
my $box = $state->{font}->bounding_box(string=>$text,
size=>$state->{size});
my $y = $state->{linepos} + $box->global_ascent;
if ($state->{bottom}
&& $state->{linepos} + $box->font_height > $state->{bottom}) {
$state->{full} = 1;
return 0;
}
if ($text =~ /\S/ && $state->{im}) {
my $justify = $fill ? $state->{justify} :
$state->{justify} eq 'fill' ? 'left' : $state->{justify};
if ($justify ne 'fill') {
my $x = $state->{x};
if ($justify eq 'right') {
$x += $state->{w} - $box->advance_width;
}
elsif ($justify eq 'center') {
$x += ($state->{w} - $box->advance_width) / 2;
}
$state->{font}->draw(image=>$state->{im}, string=>$text,
x=>$x, 'y'=>$y,
size=>$state->{size}, %{$state->{input}});
}
else {
(my $nospaces = $text) =~ tr/ //d;
my $nospace_bbox = $state->{font}->bounding_box(string=>$nospaces,
size=>$state->{size});
my $gap = $state->{w} - $nospace_bbox->advance_width;
my $x = $state->{x};
$spaces = $text =~ tr/ / /;
while (length $text) {
if ($text =~ s/^(\S+)//) {
my $word = $1;
my $bbox = $state->{font}->bounding_box(string=>$word,
size=>$state->{size});
$state->{font}->draw(image=>$state->{im}, string=>$1,
x=>$x, 'y'=>$y,
size=>$state->{size}, %{$state->{input}});
$x += $bbox->advance_width;
}
elsif ($text =~ s/^( +)//) {
my $sep = $1;
my $advance = int($gap * length($sep) / $spaces);
$spaces -= length $sep;
$gap -= $advance;
$x += $advance;
}
else {
die "This shouldn't happen\n";
}
}
}
}
$state->{linepos} += $box->font_height + $state->{linegap};
1;
}
sub wrap_text {
my $class = shift;
my %input = @_;
# try to get something useful
my $x = _first(delete $input{'x'}, 0);
my $y = _first(delete $input{'y'}, 0);
my $im = delete $input{image};
my $imerr = $im || 'Imager';
my $width = delete $input{width};
if (!defined $width) {
defined $im && $im->getwidth > $x
or return $imerr->_set_error("No width supplied and can't guess");
$width = $im->getwidth - $x;
}
my $font = delete $input{font}
or return $imerr->_set_error("No font parameter supplied");
my $size = _first(delete $input{size}, $font->{size});
defined $size
or return $imerr->_set_error("No font size supplied");
2 * $size < $width
or return $imerr->_set_error("Width too small for font size");
my $text = delete $input{string};
defined $text
or return $imerr->_set_error("No string parameter supplied");
my $justify = _first($input{justify}, "left");
my %state =
(
font => $font,
im => $im,
x => $x,
w => $width,
justify => $justify,
'y' => $y,
linepos=>$y,
size=>$size,
input => \%input,
linegap => delete $input{linegap} || 0,
);
$state{height} = delete $input{height};
if ($state{height}) {
$state{bottom} = $y + $state{height};
}
my $line = '';
my $spaces = 0;
my $charpos = 0;
my $linepos = 0;
pos($text) = 0; # avoid a warning
while (pos($text) < length($text)) {
#print pos($text), "\n";
if ($text =~ /\G( +)/gc) {
#print "spaces\n";
$line .= $1;
$spaces += length($1);
}
elsif ($text =~ /\G(?:\x0D\x0A?|\x0A\x0D?)/gc) {
#print "newline\n";
_format_line(\%state, $spaces, $line, 0)
or last;
$line = '';
$spaces = 0;
$linepos = pos($text);
}
elsif ($text =~ /\G(\S+)/gc) {
#print "word\n";
my $word = $1;
my $bbox = $font->bounding_box(string=>$line . $word, size=>$size);
if ($bbox->advance_width > $width) {
_format_line(\%state, $spaces, $line, 1)
or last;
$line = '';
$spaces = 0;
$linepos = pos($text) - length($word);
}
$line .= $word;
# check for long words
$bbox = $font->bounding_box(string=>$line, size=>$size);
while ($bbox->advance_width > $width) {
my $len = length($line) - 1;
$bbox = $font->bounding_box(string=>substr($line, 0, $len),
size=>$size);
while ($bbox->advance_width > $width) {
--$len;
$bbox = $font->bounding_box(string=>substr($line, 0, $len),
size=>$size);
}
_format_line(\%state, 0, substr($line, 0, $len), 0)
or last;
$line = substr($line, $len);
$bbox = $font->bounding_box(string=>$line, size=>$size);
$linepos = pos($text) - length($line);
}
}
elsif ($text =~ /\G\s/gc) {
# skip a single unrecognized whitespace char
#print "skip\n";
$linepos = pos($text);
}
}
if (length $line && !$state{full}) {
$linepos += length $line
if _format_line(\%state, 0, $line, 0);
}
if ($input{savepos}) {
${$input{savepos}} = $linepos;
}
return ($x, $y, $x+$width, $state{linepos});
}
1;
__END__
=head1 NAME
Imager::Font::Wrap - simple wrapped text output
=head1 SYNOPSIS
use Imager::Font::Wrap;
my $img = Imager->new(xsize=>$xsize, ysize=>$ysize);
my $font = Imager::Font->new(file=>$fontfile);
my $string = "..."; # text with or without newlines
Imager::Font::Wrap->wrap_text( image => $img,
font => $font,
string => $string,
x => $left,
y => $top,
width => $width,
.... );
=head1 DESCRIPTION
This is a simple text wrapper with options to control the layout of
text within the line.
You can control the position, width and height of the text with the
C<image>, C<x>, C<y>, C<width> and C<height> options.
You can simply calculate space usage by setting C<image> to C<undef>,
or set C<savepos> to see how much text can fit within the given
C<height>.
=over
=item wrap_text()
Draw word-wrapped text.
=over
=item *
C<x>, C<y> - The top-left corner of the rectangle the text is
formatted into. Defaults to (0, 0).
=item *
C<width> - The width of the formatted text in pixels. Defaults to the
horizontal gap between the top-left corner and the right edge of the
image. If no image is supplied then this is required.
=item *
C<height> - The maximum height of the formatted text in pixels. Not
required.
=item *
C<savepos> - The amount of text consumed (as a count of characters)
will be stored into the scalar this refers to.
my $pagenum = 1;
my $string = "...";
my $font = ...;
my $savepos;
while (length $string) {
my $img = Imager->new(xsize=>$xsize, ysize=>$ysize);
Imager::Font::Wrap->wrap_text(string=>$string, font=>$font,
image=>$img, savepos => \$savepos)
or die $img->errstr;
$savepos > 0
or die "Could not fit any text on page\n";
$string = substr($string, $savepos);
$img->write(file=>"page$pagenum.ppm");
}
=item *
C<image> - The image to render the text to. Can be supplied as
C<undef> or not provided to simply calculate the bounding box.
=item *
C<font> - The font used to render the text. Required.
=item *
C<size> - The size to render the font in. Defaults to the size stored
in the font object. Required if it isn't stored in the font object.
=item *
C<string> - The text to render. This can contain non-white-space,
blanks (ASCII 0x20), and newlines.
Newlines must match /(?:\x0A\x0D?|\x0D\x0A?)/. White-space other than
blanks and newlines are completely ignored.
=item *
C<justify>
The way text is formatted within each line. Possible values include:
=over
=item *
C<left> - left aligned against the left edge of the text box.
=item *
C<right> - right aligned against the right edge of the text box.
=item *
C<center> - centered horizontally in the text box.
=item *
fill - all but the final line of the paragraph has spaces expanded so
that the line fills from the left to the right edge of the text box.
=back
=item *
C<linegap> - Gap between lines of text in pixels. This is in addition
to the size from C<< $font->font_height >>. Can be positive or
negative. Default 0.
=back
Any other parameters are passed onto Imager::Font->draw().
Returns a list:
($left, $top, $right, $bottom)
which are the bounds of the space used to layout the text.
If C<height> is set then this is the space used within that height.
You can use this to calculate the space required to format the text
before doing it:
my ($left, $top, $right, $bottom) =
Imager::Font::Wrap->wrap_text(string => $string,
font => $font,
width => $xsize);
my $img = Imager->new(xsize=>$xsize, ysize=>$bottom);
Imager::Font::Wrap->wrap_text(string => $string,
font => $font,
width => $xsize,
image => $image);
=back
=head1 BUGS
Imager::Font can handle UTF-8 encoded text itself, but this module
doesn't support that (and probably won't). This could probably be
done with regex magic.
Currently ignores the C<sizew> parameter, if you supply one it will be
supplied to the draw() function and the text will be too short or too
long for the C<width>.
Uses a simplistic text model, which is why there's no hyphenation, and
no tabs.
=head1 AUTHOR
Tony Cook <tony@develop-help.com>
=head1 SEE ALSO
Imager(3), Imager::Font(3)
=cut