CtrlO-PDF

 view release on metacpan or  search on metacpan

lib/CtrlO/PDF.pm  view on Meta::CPAN

        if !$rc;

    my $pdf = PDF::Builder->new;

    $pdf->pass_start_state(1, 1, $self->state)
        if $self->has_state;

    $pdf->add_font_path('/usr/share/fonts');
    # Retained for backwards compatibility and moved from being built in the
    # font() and fontbold() properties
    $pdf->add_font(
        face  => 'liberation-sans',
        type  => 'ttf',
        style => 'sans-serif',
        width => 'proportional',
        file  => {
            'roman'       => 'truetype/liberation/LiberationSans-Regular.ttf',
            'italic'      => 'truetype/liberation/LiberationSans-Italic.ttf',
            'bold'        => 'truetype/liberation/LiberationSans-Bold.ttf',
            'bold-italic' => 'truetype/liberation/LiberationSans-BoldItalic.ttf'
        },
    );
    $pdf->font_settings(font => 'liberation-sans');

    $pdf;
}

=head2 page

Returns the current PDF page.

=cut

# Current page
has page => (
    is      => 'rwp',
    lazy    => 1,
    builder => sub { $_[0]->add_page },
);

=head2 add_page

Adds a PDF page and returns it.

Note that when a PDF page is added (either via this method or automatically)
the is_new_page flag records that a new page is in use with no content. See
that method for more details.

=cut

has page_count => (
    is      => 'rw',
    default => 0,
);

sub add_page
{   my $self = shift;
    my $page = $self->pdf->page;
    $page->mediabox(0, 0, $self->width, $self->height);
    $self->_set_page($page);
    $self->_set__y($self->_y_start_default); # Reset y cursor
    # Flag that we have just started a new page. Because text is positioned from
    # its bottom-left corner, we will need to move the cursor down further to
    # account for the font size of the text, but we don't know that yet.
    $self->_set_is_new_page(1);
    $self->page_count($self->page_count + 1);
    return $page;
}

=head2 is_new_page

Whether the current page is new with no content. When the heading or text
methods are called and this is true, additional top margin is added to account
for the height of the text being added. Any other content manually added will
not include this margin and will leave the internal new page flag as true.

=cut

has is_new_page => (
    is      => 'rwp',
    isa     => Bool,
    default => 1,
);

=head2 clear_new_page

Manually clears the is_new_page flag.

=cut

sub clear_new_page
{   my $self = shift;
    $self->_set_is_new_page(0);
}

=head2 orientation

Sets or returns the page orientation (portrait or landscape). The default is
Portrait (taller than wide).

=cut

has orientation => (
    is      => 'ro',
    isa     => Str,
    default => 'portrait',
);

=head2 PDFlib

Sets or returns the PDF-building library in use. The choices are "PDF::Builder"
and "PDF::API2" (case-insensitive). "PDF::Builder" is the default, indicating
that PDF::Builder will be used I<unless> it is not found, in which case
PDF::API2 will be used. If neither is found, CtrlO::PDF will fail.

=cut

has PDFlib => (
    is      => 'ro',
    isa     => Str,
    default => 'PDF::Builder',
);

lib/CtrlO/PDF.pm  view on Meta::CPAN

    is => 'lazy',
);

sub _build_logo_width
{   my $self = shift;
    return 0 if !$self->_logo_info;
    $self->_logo_info->{width} * $self->logo_scaling;
}

has _logo_info => (
    is => 'lazy',
);

sub _build__logo_info
{   my $self = shift;
    return if !$self->logo;
    image_info($self->logo);
}

has _x => (
    is      => 'rwp',
    lazy    => 1,
    builder => sub { $_[0]->margin },
);

sub _down
{   my ($self, $points) = @_;
    my $y = $self->_y;
    $self->_set__y($y - $points);
}

=head2 y_position

Returns the current y position on the page. This value updates as the page is
written to, and is the location that content will be positioned at the next
write. Note that the value is measured from the bottom of the page.

=cut

sub y_position
{   my $self = shift;
    $self->_y;
}

=head2 set_y_position($pixels)

Sets the current Y position. See L</y_position>.

=cut

sub set_y_position
{   my ($self, $y) = @_;
    $y && $y =~ /^-?[0-9]+(\.[0-9]+)?$/
        or croak "Invalid y value for set_y_position: $y";
    $self->_set__y($y);
}

=head2 move_y_position($pixels)

Moves the current Y position, relative to its current value. Positive values
will move the cursor up the page, negative values down. See L</y_position>.

=cut

sub move_y_position
{   my ($self, $y) = @_;
    $y && $y =~ /^-?[0-9]+(\.[0-9]+)?$/
        or croak "Invalid y value for move_y_position: $y";
    $self->_set__y($self->_y + $y);
}

has _y => (
    is      => 'rwp',
    lazy    => 1,
    builder => sub { $_[0]->_y_start_default },
);

sub _y_start_default
{   my $self = shift;
    return $self->height - $self->margin_top;
}

=head2 heading($text, %options)

Add a heading. If called on a new page, will automatically move the cursor down
to account for the heading's height (based on the assumption that one pixel
equals one point). Options available are:

=over

=item size I<n>

C<n> is the font size in points, B<default 16>

=item indent I<n>

C<n> is the amount (in points) to indent the text, B<default 0>

=item topmargin I<n>

C<n> is the amount (in points) of vertical skip for the margin I<above> the
heading, B<default:> calculated automatically based on font size

=item bottommargin I<n>

C<n> is the amount (in points) of vertical skip for the margin I<below> the
heading, B<default:> calculated automatically based on the font size

=back

=cut

# Return the line height based on a font size, with optional ratio
sub _line_height {
    my ($self, $size, $ratio) = @_;
    $size * ($ratio || 1.5);
}

# Return the spacing above/below a line based on font size and line height
sub _line_spacing {
    my ($self, $size) = @_;
    my $spacing = $self->_line_height($size);
    ($spacing - $size) / 2;
}

sub heading
{   my ($self, $string, %options) = @_;

    my $page = $self->page; # Ensure that page is built and cursor adjusted for first use

    $page = $self->add_page if $self->_y < 150; # Make sure there is room for following paragraph text
    my $size = $options{size} || 16;

    if ($options{topmargin}) {
        # Always let override take precedence
        $self->_down($options{topmargin}) if $options{topmargin};
    }
    elsif ($self->is_new_page)
    {
        # If a new page then move down just enough to fit in the font size
        $self->_down($size);
        $self->_set_is_new_page(0);
    }
    else {
        # Default to top margin based on font size, with slightly higher
        # spacing ratio than normal text
        $self->_down($self->_line_height($size, 1.8));
    }

    my $text   = $page->text;
    my $grfx   = $page->gfx;
    my $x      = $self->_x + ($options{indent} || 0),
    my $height = $self->_y - $self->margin_bottom;
    $text->font($self->fontbold, $size);
    $text->translate($x, $self->_y);
    $text->text($string);

    # Unless otherwise defined, add a bottom margin relative to the font size,
    # but smaller than the top margin
    my $bottommargin = defined $options{bottommargin}
        ? $options{bottommargin}
        : $self->_line_height($size, 0.4);

    $self->_down($bottommargin);
}

=head2 text($text, %options)

Add paragraph text. This will automatically paginate. Available options are
shown below. Any unrecogised options will be passed to C<PDF::Builder>'s Column
method.

=over

=item format I<name>

C<name> is the format of the text, in accordance with available formats in
C<PDF::Builder>. At the time of writing, supported options are C<none>, C<pre>,
C<md1> and C<html>. If unspecified defaults to C<none>.

=item size I<n>

C<n> is the font size in points, B<default 10>

=item indent I<n>

C<n> is the amount (in points) to indent the paragraph first line, B<default 0>

=item top_padding I<n>

C<n> is the amount (in points) of padding above the paragraph, only applied if
not at the top of a page. Defaults to half the line height.

=item color I<name>

C<name> is the string giving the text color, B<default 'black'>

=back

=cut

sub text
{   my ($self, $string, %options) = @_;

    $string or return;

    my $size = delete $options{size} || $self->font_size;
    my $color = delete $options{color} || 'black';
    my $format = delete $options{format} || 'none';

    my $page = $self->page; # Ensure that page is built and cursor adjusted for first use

    # Add new page if already at the bottom from previous operation (e.g.
    # rendering table)
    $page = $self->add_page
        if $self->_y - $self->_line_height($size) < $self->margin_bottom;

    my $text   = $page->text;
    my $grfx   = $page->gfx;
    my $x      = $self->_x + ($options{indent} || 0),
    my $height = $self->_y - $self->margin_bottom;

    $text->font($self->font, 10); # Any size, overridden below

    # Whether this version has relative padding using percentage. If it does
    # not then fallback to previous behaviour and do not set any styling,
    # otherwise set the relative padding and the spacing between blocks of text
    # to be consistent
    my $has_advanced_style = version->parse($PDF::Builder::VERSION) >= 3.028;

    my $top_padding = defined $options{top_padding}
        ? $options{top_padding}
        # Default to a whole line height preceeding
        : $self->_line_height($size, $has_advanced_style && 2) - $size;

    # Only create spacing if below other content
    if ($self->is_new_page)
    {
        $self->_set_is_new_page(0);
    }
    else {
        $self->_down($top_padding);
    }

    # As of PDF::Builder 3.028 there is a bug which prevents setting the margin
    # on "li" elements. Instead, the margin needs to be set on the associated
    # marker instead (_marker). There is also a separate bug which means that
    # if the line spacing of the font of the marker is greater than that of the
    # item text itself, then the spacing of the bullet lines will be too large.
    # However, with the use of the liberation-sans font, the line spacing is
    # actually smaller for the marker, hence it has no effect.
    my $style = $has_advanced_style && '
        ol { margin-top: 100%; margin-bottom: 100% }
        ul { margin-top: 100%; margin-bottom: 100% }
        _marker { margin-top: 50%; margin-bottom: 0% }
        p { margin-top: 100%; margin-bottom: 100% }
    ';

    my ($rc, $next_y, $unused) = $text->column(
        $page, $text, $grfx, $format, $string,
        rect      => [$x, $self->_y, $self->_width_print, $height],
        para      => [0, $top_padding],
        state     => $self->state,
        # page is: $pass_count, $max_passes, $ppn, $extfilepath, $fpn, $LR, $bind
        page      => [ 1, 1, $self->page_count, undef, $self->page_count, undef, undef ],
        font_size => $size,
        font_info =>'-fm-',
        style     => $style,
        %options
    );



( run in 1.064 second using v1.01-cache-2.11-cpan-39bf76dae61 )