This is what I've wrote to be used at work:
use constant mm => 25.4 / 72;
use constant pt => 1;
# TextBlock($texthandler, $text, $hr_options);
# $texthandler can be a single PDF:API2::Content::Text-object, or an a
+rrayref of these. Text will be added on all.
# Options must contain:
# x => x-position for the first row of text in pt
# y => y-position of the baseline of the bottom row of text
+in pt
# width => width of the textblock in pt
# height => height of the textblock in pt
# Options can also contain:
# lead => vertical distance between lines in pt
# align => horizontal alignment of text (left, right, center)
# valign => vertical alignment of text (top, bottom, center)
# parspace=> Extra height to be left white at the start of each par
+agraph.
# wordwrap=> 1 -> wraps at wordboundries instead of characters
# margin => 1 -> adds a 6mm margin on the left and 10mm on the b
+ottom
# force => 1 -> Disregards height and continues adding lines unti
+l everything's processed. Only works with valign=top (the default)
# Paragraphs are extracted from $text delimited by \n
sub TextBlock {
my ($ar_texthandlers, $text, $hr_original_options) = @_;
my $texthandler;
# Make a copy of the given options
$hr_options = { %{$hr_original_options} };
# Check for non-optional parameters
if (!defined $hr_options->{x}) { Carp::confess("No starting x defi
+ned"); }
if (!defined $hr_options->{y}) { Carp::confess("No starting y defi
+ned"); }
if (!defined $hr_options->{width}) { Carp::confess("No width defin
+ed"); }
if (!defined $hr_options->{height}) { Carp::confess("No height def
+ined"); }
# Add small margin to the left and bottom
if (defined $hr_options->{margin}) {
$hr_options->{x} += 6/mm;
$hr_options->{y} += 10/mm;
}
given (ref $ar_texthandlers) {
when ($_ eq "ARRAY") {
$texthandler = $ar_texthandlers->[0];
}
when ($_ eq "PDF::API2::Content::Text") {
$texthandler = $ar_texthandlers;
}
}
# Adjust y-position to be the top instead of the bottom
$hr_options->{y} += $hr_options->{height};
# Defaults for optional parameters
if (not defined $hr_options->{lead}) { $hr_options->{lead} = 0; }
if (not defined $hr_options->{align}) { $hr_options->{align} = "le
+ft"; }
if (not defined $hr_options->{valign}) { $hr_options->{valign} = "
+top"; }
if (defined $hr_options->{force} and $hr_options->{valign} ne 'top
+') { delete $hr_options->{force}; } # Only allow force with valign=to
+p
# Split text into paragraphs
my @paragraphs = split( /\n/, $text );
# calculate width of all characters
my @chars = split( "", $text );
my %width = ();
$space_width = $texthandler->advancewidth(' ');
foreach (@chars) {
if (exists $width{$_}) { next; }
$width{$_} = $texthandler->advancewidth($_);
}
my ($xpos, $ypos) = ($hr_options->{x}, $hr_options->{y} - ($textha
+ndler->{' fontsize'}/pt / 2));
my $ar_resultdata = [];
while (@paragraphs) {
my @Current_Paragraph;
if ($hr_options->{wordwrap}) {
@Current_Paragraph = split(' ', shift(@paragraphs)); # Cre
+ate array of words of the first entry in @paragraphs.
} else {
@Current_Paragraph = split('', shift(@paragraphs)); # Crea
+te array of characters of the first entry in @paragraphs.
}
my $line_in_paragraph = 0;
if ($hr_options->{parspace}) { $ypos -= $hr_options->{parspace
+}; }
if ($ypos < $hr_options->{y} - $hr_options->{height}) { last;
+}
my $xpos = $hr_options->{x}; # New alinea starts again at $hr_
+options->{x}
# while there's room on the line, add another word
my @line = ();
my $line_width = 0;
while (@Current_Paragraph) {
my $Current_Item = shift(@Current_Paragraph);
if ($hr_options->{wordwrap}) {
my $wordwidth = $width{' '};
foreach my $character (split('', $Current_Item)) {
$wordwidth += $width{$character};
}
if ($line_width + $wordwidth < $hr_options->{width}) {
$line_width += $wordwidth;
if (@line) { push(@line, ' '); }
push(@line, $Current_Item);
next;
} elsif ($wordwidth > $hr_options->{width}) {
# Current word is wider than the total allotted sp
+ace. Skip this word.
next;
}
} else {
if ($line_width + $width{$Current_Item} < $hr_options-
+>{width}) {
$line_width += $width{$Current_Item};
push(@line, $Current_Item);
next;
} elsif ($width{$Current_Item} > $hr_options->{width})
+ {
Carp::carp("Single character doesn't fit in allott
+ed width. Stopping.");
@paragraphs = ();
last;
}
}
# Current word/character in $_ doesn't fit on the current
+line.
# Perform horizontal alignment
given ($hr_options->{align}) {
when ($_ eq 'right') {
$xpos = $hr_options->{x} + $hr_options->{width} -
+$line_width;
}
when ($_ eq 'center') {
$xpos = $hr_options->{x} + $hr_options->{width} /
+2 - $line_width / 2;
}
when ($_ eq 'justify') {
# multiply spaces so that the full width is used
# Not implemented yet
}
}
# Save current line
push(@{$ar_resultdata}, { x => $xpos, y => $ypos, text =>
+join('', @line) });
# Clear and reset current line and line_width
@line = ();
$line_width = 0;
# Advance to the next line
$ypos -= $hr_options->{lead};
if (!defined $hr_options->{force} and $ypos < $hr_options-
+>{y} - $hr_options->{height}) {
# No more room for another line
Carp::carp("No room for the next line at ypos [$ypos],
+ ybegin [$hr_options->{y}], height [$hr_options->{height}]");
@paragraphs = (); # Cancel all remaining text
last; # Stop processing
}
# There is room for another line. Try the current word/cha
+racter in $_ again.
unshift(@Current_Paragraph, $Current_Item);
redo;
}
# If there is data in @line, there was an unfinished line bein
+g processed. Finish and save it.
if (@line) {
# Perform horizontal alignment
given ($hr_options->{align}) {
when ($_ eq 'right') {
$xpos = $hr_options->{x} + $hr_options->{width} -
+$line_width;
}
when ($_ eq 'center') {
$xpos = $hr_options->{x} + $hr_options->{width} /
+2 - $line_width / 2;
}
when ($_ eq 'justify') {
# multiply spaces so that the full width is used
# Not implemented yet
}
}
push(@{$ar_resultdata}, { x => $xpos, y => $ypos, width =>
+ $line_width, text => join('', @line) });
@line = ();
$line_width = 0;
if (@paragraphs) { $ypos -= $hr_options->{lead}; } # Only
+go to the new line if there's more text to be processed
}
}
# Perform vertical alignment
my $text_height = ($hr_options->{y} - $texthandler->{' fontsize'}/
+pt /2) - $ypos;
given ($hr_options->{valign}) {
when ($_ eq 'bottom') {
foreach my $hr_line (@{$ar_resultdata}) {
$hr_line->{y} = $hr_line->{y} - $hr_options->{height}
++ $text_height + ($texthandler->{' fontsize'}/pt /2);
}
}
when ($_ eq 'center') {
foreach my $hr_line (@{$ar_resultdata}) {
$hr_line->{y} = $hr_line->{y} - ($hr_options->{height}
+ / 2) + ($text_height / 2) + ($texthandler->{' fontsize'}/pt / 4);
}
}
}
# render the lines
given (ref $ar_texthandlers) {
when ($_ eq "ARRAY") {
foreach my $texthandler (@{$ar_texthandlers}) {
foreach my $hr_line (@{$ar_resultdata}) {
$texthandler->translate($hr_line->{x}, $hr_line->{
+y});
$texthandler->text($hr_line->{text});
}
}
}
when ($_ eq "PDF::API2::Content::Text") {
foreach my $hr_line (@{$ar_resultdata}) {
$texthandler->translate($hr_line->{x}, $hr_line->{y});
$texthandler->text($hr_line->{text});
}
}
}
}