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 arrayref 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 paragraph. # wordwrap=> 1 -> wraps at wordboundries instead of characters # margin => 1 -> adds a 6mm margin on the left and 10mm on the bottom # force => 1 -> Disregards height and continues adding lines until 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 defined"); } if (!defined $hr_options->{y}) { Carp::confess("No starting y defined"); } if (!defined $hr_options->{width}) { Carp::confess("No width defined"); } if (!defined $hr_options->{height}) { Carp::confess("No height defined"); } # 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} = "left"; } 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=top # 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} - ($texthandler->{' fontsize'}/pt / 2)); my $ar_resultdata = []; while (@paragraphs) { my @Current_Paragraph; if ($hr_options->{wordwrap}) { @Current_Paragraph = split(' ', shift(@paragraphs)); # Create array of words of the first entry in @paragraphs. } else { @Current_Paragraph = split('', shift(@paragraphs)); # Create 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 space. 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 allotted 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/character in $_ again. unshift(@Current_Paragraph, $Current_Item); redo; } # If there is data in @line, there was an unfinished line being 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}); } } } }