# --
# Modified version of the work: Copyright (C) 2006-2023 KIX Service Software GmbH, https://www.kixdesk.com
# based on the original work of:
# Copyright (C) 2001-2023 OTRS AG, https://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file LICENSE for license information (AGPL). If you
# did not receive this file, see https://www.gnu.org/licenses/agpl.txt.
# --

package Kernel::System::PDF;

use strict;
use warnings;

use PDF::API2;

our @ObjectDependencies = (
    'Kernel::Config',
    'Kernel::System::Cache',
    'Kernel::System::Log',
    'Kernel::System::Main',
    'Kernel::System::Time',
);

=head1 NAME

Kernel::System::PDF - pdf lib

=head1 SYNOPSIS

Functions for generating PDF files.

=head1 PUBLIC INTERFACE

=over 4

=item new()

create an object. Do not use it directly, instead use:

    use Kernel::System::ObjectManager;
    local $Kernel::OM = Kernel::System::ObjectManager->new();
    my $PDFObject = $Kernel::OM->Get('Kernel::System::PDF');

Please note that currently you should only create one PDF object per instance of
this class.

=cut

sub new {
    my ( $Type, %Param ) = @_;

    # allocate new hash for object
    my $Self = {};
    bless( $Self, $Type );

    # read string width cache
    $Self->{CacheStringWidth} = $Kernel::OM->Get('Kernel::System::Cache')->Get(
        Type => 'PDF',
        Key  => 'CacheStringWidth',
    ) || {};

    return $Self;
}

=item DocumentNew()

Create a new PDF Document

These font aliases are available in all methods:
        Proportional
        ProportionalBold
        ProportionalItalic
        ProportionalBoldItalic
        Monospaced
        MonospacedBold
        MonospacedItalic
        MonospacedBoldItalic

    $True = $PDFObject->DocumentNew(
        Title     => 'The Document Title',  # Title of PDF Document
        Encode    => 'utf-8',               # Charset of Document
        Testfonts => 1,                     # (optional) default 0
    );

=cut

sub DocumentNew {
    my ( $Self, %Param ) = @_;

    # check pdf object
    if ( $Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => 'Can not create new Document!',
        );
        return;
    }

    # get config object
    my $ConfigObject = $Kernel::OM->Get('Kernel::Config');

    # get Product and Version
    my $PDFCreator = '';    # set to empty value if Secure::DisableBanner is active
    if ( !$Kernel::OM->Get('Kernel::Config')->Get('Secure::DisableBanner') ) {
        $PDFCreator = $ConfigObject->Get('Product') . ' ' . $ConfigObject->Get('Version');
    }

    # set document title
    $Self->{Document}->{Title} = $Param{Title} || $PDFCreator;

    # set document encode
    $Self->{Document}->{Encode} = $Param{Encode} || 'utf-8';

    # set logo file
    $Self->{Document}->{LogoFile} = $ConfigObject->Get('PDF::LogoFile');

    # create a new document
    $Self->{PDF} = PDF::API2->new();

    # check pdf object
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => 'Can not create new Document: $!',
        );
        return;
    }

    # get time object
    my $TimeObject = $Kernel::OM->Get('Kernel::System::Time');

    # today
    my ( $NowSec, $NowMin, $NowHour, $NowDay, $NowMonth, $NowYear ) = $TimeObject->SystemTime2Date(
        SystemTime => $TimeObject->SystemTime(),
    );

    # set document infos
    $Self->{PDF}->info(
        'Author'       => $PDFCreator,
        'CreationDate' => "D:"
            . $NowYear
            . $NowMonth
            . $NowDay
            . $NowHour
            . $NowMin
            . $NowSec
            . "+01'00'",
        'Creator'  => $PDFCreator,
        'Producer' => $PDFCreator,
        'Title'    => $Self->{Document}->{Title},
        'Subject'  => $Self->{Document}->{Title},
    );

    # add font directory
    my $FontDir = $ConfigObject->Get('Home') . '/var/fonts';
    $Self->{PDF}->add_to_font_path($FontDir);

    if ( !$Param{Testfonts} ) {

        # get font config
        my %FontFiles = %{ $ConfigObject->Get('PDF::TTFontFile') };

        # set fonts
        for my $FontType ( sort keys %FontFiles ) {
            $Self->{Font}->{$FontType} = $Self->{PDF}->font(
                $FontFiles{$FontType},
                format      => 'truetype',
                -encode     => $Self->{Document}->{Encode},
                -unicodemap => 1,
            );
        }
    }
    else {

        # set testfont (only used in unitests)
        $Self->{Font}->{Testfont1} = $Self->{PDF}->font(
            'Helvetica',
            -encode     => $Self->{Document}->{Encode},
            -unicodemap => 1,
        );
        $Self->{Font}->{Testfont2} = $Self->{PDF}->font(
            'DejaVuSans.ttf',
            format      => 'truetype',
            -encode     => $Self->{Document}->{Encode},
            -unicodemap => 1,
        );

        # get font config
        my %FontFiles = %{ $ConfigObject->Get('PDF::TTFontFile') };

        # set fonts
        for my $FontType ( sort keys %FontFiles ) {
            $Self->{Font}->{$FontType} = $Self->{Font}->{Testfont1};
        }
    }

    return 1;
}

=item PageBlankNew()

Create a new, blank Page

    $True = $PDFObject->PageBlankNew(
        Width           => 200,          # (optional) default 595 (Din A4) - _ both or nothing
        Height          => 300,          # (optional) default 842 (Din A4) -
        PageOrientation => 'landscape',  # (optional) default normal (normal|landscape)
        MarginTop       => 40,           # (optional) default 0 -
        MarginRight     => 40,           # (optional) default 0  |_ all or nothing
        MarginBottom    => 40,           # (optional) default 0  |
        MarginLeft      => 40,           # (optional) default 0 -
        ShowPageNumber  => 0,            # (optional) default 1
    );

=cut

sub PageBlankNew {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Object!"
        );
        return;
    }

    # set PageOrientation
    if ( !defined( $Param{PageOrientation} ) ) {
        $Param{PageOrientation} = 'normal';
    }

    # set margins
    $Param{MarginTop}    = $Param{MarginTop}    || 0;
    $Param{MarginRight}  = $Param{MarginRight}  || 0;
    $Param{MarginBottom} = $Param{MarginBottom} || 0;
    $Param{MarginLeft}   = $Param{MarginLeft}   || 0;

    # create a new page
    $Self->{Page} = $Self->{PDF}->page();

    # if page was created
    if ( $Self->{Page} ) {

        # set new page width and height
        $Self->_CurPageDimSet( %Param, );

        # get current page dimension an set mediabox
        my %Page = $Self->_CurPageDimGet();
        $Self->{Page}->mediabox( $Page{Width}, $Page{Height}, );

        # set default value of ShowPageNumber, if no value given
        my $ShowPageNumber = 1;
        if ( defined( $Param{ShowPageNumber} ) && $Param{ShowPageNumber} eq "0" ) {
            $ShowPageNumber = 0;
        }

        # set the page numbers
        $Self->_CurPageNumberSet(
            ShowPageNumber => $ShowPageNumber,
        );

        # set printable dimension
        $Self->_CurPrintableDimSet(
            Top    => $Param{MarginTop},
            Right  => $Param{MarginRight},
            Bottom => $Param{MarginBottom},
            Left   => $Param{MarginLeft},
        );

        # set activ dimension
        $Self->DimSet(
            Dim => 'content',
        );

        return 1;
    }

    $Kernel::OM->Get('Kernel::System::Log')->Log(
        Priority => 'error',
        Message  => "Can not create new blank Page!"
    );

    return;
}

=item PageNew()

Create a new Page

    $PDFObject->PageNew(
        Width           => 200,                 # (optional) default 595 (Din A4)
        Height          => 300,                 # (optional) default 842 (Din A4)
        PageOrientation => 'landscape',         # (optional) default normal (normal|landscape)
        MarginTop       => 40,                  # (optional) default 0
        MarginRight     => 40,                  # (optional) default 0
        MarginBottom    => 40,                  # (optional) default 0
        MarginLeft      => 40,                  # (optional) default 0
        ShowPageNumber  => 0,                   # (optional) default 1
        LogoFile        => '/path/to/file.jpg', # (optional) you can use jpg, gif and png-Images
        HeaderRight     => 'Header Right Text', # (optional)
        HeadlineLeft    => 'Headline Text',     # (optional)
        HeadlineRight   => 'Headline Text',     # (optional)
        FooterLeft      => 'Footer Left Text',  # (optional)
        FooterRight     => 'Footer Right Text', # (optional)
    );

=cut

sub PageNew {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Object!"
        );
        return;
    }

    my %Data = ();

    # set new page width and height, if values are given
    if ( $Param{Width} && $Param{Height} ) {
        $Data{Width}  = $Param{Width};
        $Data{Height} = $Param{Height};
    }

    # set new margin, if values are given
    if ( $Param{MarginTop} && $Param{MarginRight} && $Param{MarginBottom} && $Param{MarginLeft} ) {
        $Data{MarginTop}    = $Param{MarginTop};
        $Data{MarginRight}  = $Param{MarginRight};
        $Data{MarginBottom} = $Param{MarginBottom};
        $Data{MarginLeft}   = $Param{MarginLeft};
    }
    if ( $Param{ShowPageNumber} ) {
        $Data{ShowPageNumber} = $Param{ShowPageNumber};
    }
    if ( $Param{PageOrientation} ) {
        $Data{PageOrientation} = $Param{PageOrientation};
    }

    # create a blank page
    $Self->PageBlankNew(%Data);
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page Object!"
        );
        return;
    }

    # set activ dimension
    $Self->DimSet(
        Dim => 'printable',
    );

    # get current printable dimension
    my %Printable = $Self->_CurPrintableDimGet();

    # get logofile
    my $LogoFile = $Self->{Document}->{LogoFile}
        || $Kernel::OM->Get('Kernel::Config')->Get('Home') . '/var/logo.png';

    if (
        defined( $Param{LogoFile} )
        && -e $Param{LogoFile}
        && (
            $Param{LogoFile} =~ /^.*\.gif$/i
            || $Param{LogoFile} =~ /^.*\.jpg$/i
            || $Param{LogoFile} =~ /^.*\.png$/i
        )
    ) {
        $LogoFile = $Param{LogoFile};
    }

    # output the logo image at header left
    $Self->Image(
        File   => $LogoFile,
        Width  => 700,
        Height => 100,
    );

    if ( $Param{HeaderRight} ) {

        # set new position
        $Self->PositionSet(
            Move => 'relativ',
            X    => 168,
            Y    => 15,
        );

        # output page header right
        $Self->Text(
            Text     => $Param{HeaderRight},
            Type     => 'Cut',
            Color    => '#404040',
            FontSize => 12,
            Height   => 12,
            Align    => 'right',
        );
    }

    # set new position
    $Self->PositionSet(
        X => 'left',
        Y => 'top',
    );

    # set new position
    $Self->PositionSet(
        Move => 'relativ',
        Y    => -29,
    );

    # output the lines in top of the page
    $Self->HLine(
        Color     => '#505050',
        LineWidth => 0.5,
    );

    if ( $Param{FooterLeft} ) {

        # set new position
        $Self->PositionSet(
            X => 'left',
            Y => 'bottom',
        );

        # set new position
        $Self->PositionSet(
            Move => 'relativ',
            Y    => 8,
        );

        # output page footer left
        $Self->Text(
            Text     => $Param{FooterLeft},
            Width    => ( $Printable{Width} / 4 * 3 ),
            Type     => 'Cut',
            Color    => '#404040',
            FontSize => 8,
            Height   => 8,
            Align    => 'left',
        );
    }

    if ( $Param{FooterRight} ) {

        # set new position
        $Self->PositionSet(
            X => 'left',
            Y => 'bottom',
        );

        # set new position
        $Self->PositionSet(
            Move => 'relativ',
            X    => ( $Printable{Width} / 4 * 3 ),
            Y    => 8,
        );

        # output page footer right
        $Self->Text(
            Text     => $Param{FooterRight},
            Type     => 'Cut',
            Color    => '#404040',
            FontSize => 8,
            Height   => 8,
            Align    => 'right',
        );
    }

    # set new position
    $Self->PositionSet(
        X => 'left',
        Y => 'bottom',
    );

    # set new position
    $Self->PositionSet(
        Move => 'relativ',
        Y    => 11,
    );

    # output the lines in bottom of the page
    $Self->HLine(
        Color     => '#505050',
        LineWidth => 0.5,
    );

    if ( $Param{HeadlineLeft} && $Param{HeadlineRight} ) {

        # set new position
        $Self->PositionSet(
            X => 'left',
            Y => 'top',
        );

        # set new position
        $Self->PositionSet(
            Move => 'relativ',
            Y    => -44,
        );
        $Self->Text(
            Text     => $Param{HeadlineLeft},
            Width    => ( $Printable{Width} / 2 ),
            Height   => 12,
            Type     => 'Cut',
            Font     => 'ProportionalBold',
            FontSize => 12,
        );
        $Self->PositionSet(
            X => 'left',
            Y => 'top',
        );
        $Self->PositionSet(
            Move => 'relativ',
            X    => ( $Printable{Width} / 2 ),
            Y    => -48,
        );
        $Self->Text(
            Text     => $Param{HeadlineRight},
            Height   => 8,
            Type     => 'Cut',
            Font     => 'Proportional',
            FontSize => 8,
            Color    => '#404040',
            Align    => 'right',
        );

        # set new content dimension
        $Self->_CurContentDimSet(
            Top    => $Printable{Top} + 64,
            Right  => $Printable{Right},
            Bottom => $Printable{Bottom} + 16,
            Left   => $Printable{Left},
        );
    }
    else {

        # set new content dimension
        $Self->_CurContentDimSet(
            Top    => $Printable{Top} + 34,
            Right  => $Printable{Right},
            Bottom => $Printable{Bottom} + 16,
            Left   => $Printable{Left},
        );
    }

    # set activ dimension
    $Self->DimSet(
        Dim => 'content',
    );

    return 1;
}

=item DocumentOutput()

Return the PDF as string

    $DocumentString = $PDFObject->DocumentOutput();

=cut

sub DocumentOutput {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Object!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    # return the document as string
    my $DocumentString = $Self->{PDF}->to_string();
    $Self->{PDF}->close();

    return $DocumentString;
}

=item Table()

Add a table.

In case of missing or misused parameters, C<undef> is returned in scalar context
and an empty list is returned in list context.

    Return
        $Return{State}
        $Return{RequiredWidth}
        $Return{RequiredHeight}
        $Return{CellData}                # (reference) complete calculated
        $Return{ColumnData}              # (reference) complete calculated

    %Return = $PDFObject->Table(
        CellData            => $CellData,    # 2D arrayref (see example)
        ColumnData          => $ColumnData,  # arrayref (see example)
        RowData             => $RowData,     # arrayref (see example)
        Type                => 'Cut',        # (optional) default ReturnLeftOver (ReturnLeftOver|ReturnLeftOverHard|Cut)
        Width               => 300,          # (optional) default maximal width
        Height              => 400,          # (optional) default minimal height
        Font                => 'Monospaced', # (optional) default Proportional (see DocumentNew())
        FontSize            => 9,            # (optional) default 11
        FontColor           => 'red',        # (optional) default black
        FontColorEven       => 'blue',       # (optional) cell font color for even rows
        FontColorOdd        => 'green',      # (optional) cell font color for odd rows
        Align               => 'right',      # (optional) default left (left|center|right)
        Lead                => 3,            # (optional) default 1
        Padding             => 18,           # (optional) default 3
        PaddingTop          => 10,           # (optional) top cell padding, overides Padding
        PaddingRight        => 30,           # (optional) right cell padding, overides Padding
        PaddingBottom       => 30,           # (optional) bottom cell padding, overides Padding
        PaddingLeft         => 10,           # (optional) left cell padding, overides Padding
        BackgroundColor     => '#101010',    # (optional) default white
        BackgroundColorEven => '#F0F0F0',    # (optional) cell background color for even rows
        BackgroundColorOdd  => '#A0A0A0',    # (optional) cell background color for odd rows
        Border              => 1,            # (optional) default 1 (values between 0 and 20)
        BorderColor         => '#FF0000',    # (optional) default black
    );

    $CellData = [
        [
            {
                Content => "Cell 1 (Row 1, Column 1)",  # (optional)
                Font => 'Monospaced',                   # (optional) (see DocumentNew())
                FontSize => 13,                         # (optional)
                FontColor => '#00FF00',                 # (optional)
                Align => 'center',                      # (optional)
                Lead => 7,                              # (optional)
                BackgroundColor => '#101010',           # (optional)
            },
            {
                Content => "Cell 2 (Row 1, Column 2)",
            },
        ],
        [
            {
                Content => "Cell 3 (Row 2, Column 1)",
            },
            {
                Content => "Cell 4 (Row 2, Column 2)",
            },
        ],
    ];

    $ColumData = [        # this array was automaticly generated, if not given
        {
            Width => 11,  # (optional)
        },
        {
            Width => 44,
        },
    ];

    $RowData = [           # this array was automaticly generated, if not given
        {
            Height => 11,  # (optional)
        },
        {
            Height => 44,
        },
    ];

=cut

sub Table {
    my ( $Self, %Param ) = @_;

    # check needed stuff
    for (qw(CellData)) {
        if ( !defined( $Param{$_} ) ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $_!"
            );
            $Param{State} = 1;
            return;
        }
    }
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        $Param{State} = 1;
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        $Param{State} = 1;
        return;
    }

    my %Dim;

    # get dimension (printable or content)
    if ( $Self->DimGet() eq 'printable' ) {
        %Dim = $Self->_CurPrintableDimGet();
    }
    else {
        %Dim = $Self->_CurContentDimGet();
    }

    # get current position
    my %Position = $Self->_CurPositionGet();

    # set default values
    $Param{ColumnData} ||= [];
    $Param{RowData}    ||= [];

    if (
        ref( $Param{CellData} ) eq 'ARRAY'
        && ref( $Param{ColumnData} ) eq 'ARRAY'
        && ref( $Param{RowData} ) eq 'ARRAY'
    ) {
        if ( !defined( $Param{OutputCount} ) ) {

            # set default values
            $Param{Type} ||= 'ReturnLeftOver';
            $Param{Font} ||= 'Proportional';
            if ( !defined( $Param{FontSize} ) || $Param{FontSize} <= 0 ) {
                $Param{FontSize} = 10;
            }
            if ( !defined( $Param{Lead} ) || $Param{Lead} < -( $Param{FontSize} ) ) {
                $Param{Lead} = int( $Param{FontSize} / 4 );
                if ( $Param{Lead} < 1 ) {
                    $Param{Lead} = 1;
                }
            }
            $Param{FontColor}     ||= 'black';
            $Param{FontColorOdd}  ||= $Param{FontColor};
            $Param{FontColorEven} ||= $Param{FontColor};

            $Param{BackgroundColor}     ||= 'NULL';
            $Param{BackgroundColorOdd}  ||= $Param{BackgroundColor};
            $Param{BackgroundColorEven} ||= $Param{BackgroundColor};

            $Param{Align} = $Param{Align} || 'left';

            if ( !defined( $Param{Border} ) || $Param{Border} < 0 ) {
                $Param{Border} = 1;
            }
            $Param{BorderColor}   ||= 'black';
            $Param{PaddingTop}    ||= $Param{Padding} || 3;
            $Param{PaddingRight}  ||= $Param{Padding} || 3;
            $Param{PaddingBottom} ||= $Param{Padding} || 3;
            $Param{PaddingLeft}   ||= $Param{Padding} || 3;

            # check given Width
            my $DefaultWidth = $Dim{Left} + $Dim{Width} - $Position{X};
            if (
                !defined( $Param{Width} )
                || (
                    $Param{Width}
                    - $Param{PaddingLeft}
                    - $Param{PaddingRight}
                    - ( 2 * $Param{Border} )
                ) < 0
                || $Param{Width} > $DefaultWidth
            ) {
                $Param{Width} = $DefaultWidth;
            }

            # set output count
            $Param{OutputCount} = 0;

            # set state
            $Param{State} = 0;

            # calculate required table attributes
            $Self->_TableCalculate( %Param, );
        }

        # check given Height
        my $DefaultHeight = $Position{Y} - $Dim{Bottom};
        if (
            !defined( $Param{Height} )
            || (
                $Param{Height}
                - $Param{PaddingTop}
                - $Param{PaddingBottom}
                - ( 2 * $Param{Border} )
            ) < 0
            || $Param{Height} > $DefaultHeight
        ) {
            $Param{Height} = $DefaultHeight;
        }

        # get maximum number of pages
        my $MaxPages = $Kernel::OM->Get('Kernel::Config')->Get('PDF::MaxPages');

        if ( !$MaxPages || $MaxPages < 1 || $MaxPages > 1000 ) {
            $MaxPages = 100;
        }

        # infinite loop protection
        if ( $Param{OutputCount} < $MaxPages ) {
            my %Block = $Self->_TableBlockNextCalculate(
                CellData   => $Param{CellData},
                ColumnData => $Param{ColumnData},
            );

            # if active cells found
            if ( $Block{State} ) {

                # start row output
                my $Row        = $Block{ReturnRowStart};
                my $RowCounter = 0;
                my $RowLoop    = 1;
                my $LastBlock  = $Param{ColumnData}->[ $#{ $Param{ColumnData} } ]->{Block};
                my $LastRow    = $#{ $Param{RowData} };

                while ($RowLoop) {

                    # stop loop, if last row
                    if ( $Row <= $LastRow ) {

                        # calculate row height, if block is 0
                        if ( !$Block{ReturnBlock} ) {
                            $Self->_TableRowCalculate(
                                Row => $Row,
                                %Param,
                            );
                        }

                        # save old position
                        my %PositionOld = %Position;
                        if (
                            $Param{RowData}->[$Row]->{OutputHeight}
                            && $Param{RowData}->[$Row]->{OutputHeight} <= $Position{Y} - $Dim{Bottom}
                        ) {
                            for ( $Block{ReturnColumnStart} .. $Block{ReturnColumnStop} ) {
                                my $Column = $_;
                                $Self->_TableCellOutput(
                                    Text          => $Param{CellData}->[$Row]->[$Column]->{Content},
                                    Type          => $Param{CellData}->[$Row]->[$Column]->{Type},
                                    Width         => $Param{ColumnData}->[$Column]->{OutputWidth},
                                    Height        => $Param{RowData}->[$Row]->{OutputHeight},
                                    Font          => $Param{CellData}->[$Row]->[$Column]->{Font},
                                    FontSize      => $Param{CellData}->[$Row]->[$Column]->{FontSize},
                                    FontColor     => $Param{CellData}->[$Row]->[$Column]->{FontColor},
                                    Align         => $Param{CellData}->[$Row]->[$Column]->{Align},
                                    Lead          => $Param{CellData}->[$Row]->[$Column]->{Lead},
                                    PaddingTop    => $Param{PaddingTop},
                                    PaddingRight  => $Param{PaddingRight},
                                    PaddingBottom => $Param{PaddingBottom},
                                    PaddingLeft   => $Param{PaddingLeft},
                                    BackgroundColor =>
                                        $Param{CellData}->[$Row]->[$Column]->{BackgroundColor},
                                    Border      => $Param{Border},
                                    BorderColor => $Param{BorderColor},
                                );

                                # deactivate cell and delete content
                                $Param{CellData}->[$Row]->[$Column]->{Off}     = 1;
                                $Param{CellData}->[$Row]->[$Column]->{Content} = ' ';

                                # set new position
                                $Self->_CurPositionSet(
                                    X => $Position{X}
                                        + $Param{ColumnData}->[$Column]->{OutputWidth}
                                        - $Param{Border},
                                    Y => $Position{Y},
                                );

                                # get current position
                                %Position = $Self->_CurPositionGet();
                            }

                            # set new position
                            $Self->_CurPositionSet(
                                X => $PositionOld{X},
                                Y => $PositionOld{Y}
                                    - $Param{RowData}->[$Row]->{OutputHeight}
                                    + $Param{Border},
                            );

                            # get current position
                            %Position = $Self->_CurPositionGet();
                        }
                        else {
                            my $NewOutputHeight = $Position{Y} - $Dim{Bottom};
                            my $NewTextHeight   = $NewOutputHeight
                                - $Param{PaddingTop}
                                - $Param{PaddingBottom}
                                - ( 2 * $Param{Border} );

                            if ( $NewTextHeight > $Param{RowData}->[$Row]->{MinFontSize} ) {
                                for ( $Block{ReturnColumnStart} .. $Block{ReturnColumnStop} ) {
                                    my $Column = $_;
                                    my $Type   = 'ReturnLeftOver';
                                    if (
                                        $Param{CellData}->[$Row]->[$Column]->{Type} eq 'ReturnLeftOverHard'
                                    ) {
                                        $Type = 'ReturnLeftOverHard';
                                    }
                                    my %Return = $Self->_TableCellOutput(
                                        Text            => $Param{CellData}->[$Row]->[$Column]->{Content},
                                        Type            => $Type,
                                        Width           => $Param{ColumnData}->[$Column]->{OutputWidth},
                                        Height          => $NewOutputHeight,
                                        Font            => $Param{CellData}->[$Row]->[$Column]->{Font},
                                        FontSize        => $Param{CellData}->[$Row]->[$Column]->{FontSize},
                                        FontColor       => $Param{CellData}->[$Row]->[$Column]->{FontColor},
                                        Align           => $Param{CellData}->[$Row]->[$Column]->{Align},
                                        Lead            => $Param{CellData}->[$Row]->[$Column]->{Lead},
                                        PaddingTop      => $Param{PaddingTop},
                                        PaddingRight    => $Param{PaddingRight},
                                        PaddingBottom   => $Param{PaddingBottom},
                                        PaddingLeft     => $Param{PaddingLeft},
                                        BackgroundColor => $Param{CellData}->[$Row]->[$Column]->{BackgroundColor},
                                        Border          => $Param{Border},
                                        BorderColor     => $Param{BorderColor},
                                    );

                                    # set new content
                                    if ( !$Return{State} ) {
                                        $Param{CellData}->[$Row]->[$Column]->{Content} = $Return{LeftOver};
                                    }
                                    else {
                                        $Param{CellData}->[$Row]->[$Column]->{Content} = ' ';
                                    }

                                    # correcting content
                                    if ( $Param{CellData}->[$Row]->[$Column]->{Content} eq '' ) {
                                        $Param{CellData}->[$Row]->[$Column]->{Content} = ' ';
                                    }
                                    $Param{CellData}->[$Row]->[$Column]->{TmpOff} = 1;

                                    # recalculate height
                                    if (
                                        $Block{ReturnBlock} eq $LastBlock
                                        && $Column eq $Block{ReturnColumnStop}
                                    ) {

                                        # if Height was given
                                        if ( $Param{RowData}->[$Row]->{Height} > 0 ) {
                                            $Param{RowData}->[$Row]->{Height} -= $NewTextHeight;

                                            # if rest too small, deactivate all cells of this row
                                            if (
                                                $Param{RowData}->[$Row]->{Height} < $Param{RowData}->[$Row]->{MinFontSize}
                                            ) {
                                                for my $CellOff ( @{ $Param{CellData}->[$Row] } ) {
                                                    $CellOff->{Content} = ' ';
                                                    $CellOff->{Off}     = 1;
                                                    $CellOff->{Tmp}     = 0;
                                                }
                                            }
                                        }
                                        $Self->_TableRowCalculate(
                                            Row => $Row,
                                            %Param,
                                        );
                                    }

                                    # set new position
                                    $Self->_CurPositionSet(
                                        X => $Position{X}
                                            + $Param{ColumnData}->[$Column]->{OutputWidth}
                                            - $Param{Border},
                                        Y => $Position{Y},
                                    );

                                    # get current position
                                    %Position = $Self->_CurPositionGet();
                                }
                            }
                            $RowLoop = 0;
                        }
                    }
                    else {
                        $RowLoop = 0;
                    }

                    if ( $RowCounter > 100 ) {
                        $Kernel::OM->Get('Kernel::System::Log')->Log(
                            Priority => 'error',
                            Message =>
                                "Too much row loops on page! Infinite Loop protection. Table Output aborted."
                        );
                        $RowLoop = 0;
                    }

                    # increment Row and RowCounter
                    $Row++;
                    $RowCounter++;
                }
            }
            else {
                $Kernel::OM->Get('Kernel::System::Log')->Log(
                    Priority => 'error',
                    Message  => "No active cells! Table Output aborted."
                );
                $Param{State} = 1;
            }
        }
        else {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Too much loops! Infinite Loop protection. Table Output aborted."
            );
            $Param{State} = 1;
        }
    }
    else {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message =>
                "Need array references of CellData, ColumnData and RowData! Table Output aborted."
        );
        $Param{State} = 1;
    }

    # count remaining cells
    my $RemainingCells = $Self->_TableCellOnCount(
        CellData => $Param{CellData},
    );

    # set state
    if ( !$RemainingCells ) {
        $Param{State} = 1;
    }

    $Param{OutputCount}++;

    return %Param;
}

=item Text()

Output a textline

    Return
        $Return{State}
        $Return{RequiredWidth}
        $Return{RequiredHeight}
        $Return{LeftOver}

    %Return = $PDFObject->Text(
        Text     => 'Text',              # Text
        Width    => 300,                 # (optional) available width of textblock
        Height   => 200,                 # (optional) available height of textblock
        Type     => 'Cut',               # (optional) default ReturnLeftOver (ReturnLeftOver|ReturnLeftOverHard|Cut)
        Font     => 'ProportionalBold',  # (optional) default Proportional  (see DocumentNew())
        FontSize => 15,                  # (optional) default 10
        Color    => '#FF0000',           # (optional) default #000000
        Align    => 'center',            # (optional) default left (left|center|right)
        Lead     => 20,                  # (optional) default 1 distance between lines
    );

=cut

sub Text {
    my ( $Self, %Param ) = @_;

    # check needed stuff
    for (qw(Text)) {
        if ( !defined( $Param{$_} ) ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $_!"
            );
            return;
        }
    }
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    my %Dim;

    # get dimension (printable or content)
    if ( $Self->DimGet() eq 'printable' ) {
        %Dim = $Self->_CurPrintableDimGet();
    }
    else {
        %Dim = $Self->_CurContentDimGet();
    }

    # get current position
    my %Position = $Self->_CurPositionGet();

    $Param{Type}  = $Param{Type}  || 'ReturnLeftOver';
    $Param{Color} = $Param{Color} || 'black';
    $Param{Font}  = $Param{Font}  || 'Proportional';
    $Param{Align} = $Param{Align} || 'left';

    if ( !defined( $Param{FontSize} ) || $Param{FontSize} <= 0 ) {
        $Param{FontSize} = 10;
    }
    if ( !defined( $Param{Lead} ) || $Param{Lead} < -( $Param{FontSize} ) ) {
        $Param{Lead} = int( $Param{FontSize} / 4 );
        if ( $Param{Lead} < 1 ) {
            $Param{Lead} = 1;
        }
    }

    # check Width
    if (
        !defined( $Param{Width} )
        || $Param{Width} < 0
        || ( $Position{X} + $Param{Width} ) >= ( $Dim{Left} + $Dim{Width} )
    ) {
        $Param{Width} = $Dim{Left} + $Dim{Width} - $Position{X};
    }

    # check Height
    if (
        !defined( $Param{Height} )
        || $Param{Height} < 0
        || ( $Position{Y} - $Param{Height} ) < $Dim{Bottom}
    ) {
        $Param{Height} = $Position{Y} - $Dim{Bottom};
    }

    # calculate the given text
    my %Return = $Self->_TextCalculate(
        Text     => $Param{Text},
        Type     => $Param{Type},
        Width    => $Param{Width},
        Height   => $Param{Height},
        Font     => $Param{Font},
        FontSize => $Param{FontSize},
        Lead     => $Param{Lead},
    );

    if ( $Return{LeftOver} ne $Param{Text} ) {

        # create a text object
        my $Text = $Self->{Page}->text();

        # set font and fontsize
        $Text->font( $Self->{Font}->{ $Param{Font} }, $Param{FontSize} );

        # set fontcolor
        $Text->fillcolor( $Param{Color} );

        # save original X position
        my $PositionX = $Position{X};

        my $Counter1 = 0;
        for my $Row ( @{ $Return{PossibleRows} } ) {

            # calculate width of row
            my $RowWidth = $Self->_StringWidth(
                Text     => $Row,
                Font     => $Param{Font},
                FontSize => $Param{FontSize},
            );

            if ( $Param{Align} eq 'right' ) {

                # set new position
                $Self->_CurPositionSet(
                    X => $PositionX + $Param{Width} - $RowWidth,
                );
            }
            elsif ( $Param{Align} eq 'center' ) {

                # set new position
                $Self->_CurPositionSet(
                    X => $PositionX + ( ( $Param{Width} - $RowWidth ) / 2 ),
                );
            }

            # set new position
            if ( $Counter1 > 0 ) {
                $Self->_CurPositionSet(
                    Y => $Position{Y} - $Param{FontSize} - $Param{Lead},
                );
            }
            else {
                $Self->_CurPositionSet(
                    Y => $Position{Y} - $Param{FontSize},
                );
            }

            # get current position
            %Position = $Self->_CurPositionGet();

            # get to position
            $Text->translate( $Position{X}, $Position{Y} );

            # output text
            $Text->text($Row);

            $Counter1++;
        }

        # set new position
        $Self->_CurPositionSet(
            X => $PositionX,
        );
    }

    return %Return;
}

=item Image()

Output a image

    $True = $PDFObject->Image(
        File   => '/path/image.gif',  # (gif|jpg|png)
        Type   => 'ReturnFalse'       # (optional) default Reduce (ReturnFalse|Reduce)
        Width  => 300,                # width of image
        Height => 150,                # height of image
    );

=cut

sub Image {
    my ( $Self, %Param ) = @_;

    # check needed stuff
    for (qw(File Width Height)) {
        if ( !defined( $Param{$_} ) ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $_!"
            );
            return;
        }
    }
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }
    if ( !-e $Param{File} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "File $Param{File} not found!"
        );
        return;
    }

    my %Dim;

    # get dimension (printable or content)
    if ( $Self->DimGet() eq 'printable' ) {
        %Dim = $Self->_CurPrintableDimGet();
    }
    else {
        %Dim = $Self->_CurContentDimGet();
    }

    $Param{Width}  = $Param{Width} /  ( 300 / 72 );
    $Param{Height} = $Param{Height} / ( 300 / 72 );

    my $Image = $Self->{Page}->graphics();
    my $ImageFile;

    # if image already used, use the existing image object
    if ( defined( $Self->{CacheImageObject}->{ $Param{File} } ) ) {
        $ImageFile = $Self->{CacheImageObject}->{ $Param{File} };
    }
    else {
        $ImageFile = $Self->{PDF}->image( $Param{File} );

        if ( !$ImageFile ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Image type of File $Param{File} not supported"
            );
            return;
        }

        # cache image object
        $Self->{CacheImageObject}->{ $Param{File} } = $ImageFile;
    }

    # get current position
    my %Position = $Self->_CurPositionGet();

    my $Reduce = 0;

    # check values
    if ( ( $Position{X} + $Param{Width} ) >= ( $Dim{Left} + $Dim{Width} ) ) {
        $Param{Width} = $Dim{Left} + $Dim{Width} - $Position{X};
        $Reduce = 1;
    }
    if ( $Param{Width} < 1 ) {
        $Param{Width} = 1;
    }

    if ( ( $Position{Y} - $Param{Height} ) <= $Dim{Bottom} ) {
        $Param{Height} = $Position{Y} - $Dim{Bottom};
        $Reduce = 1;
    }
    if ( $Param{Height} < 1 ) {
        $Param{Height} = 1;
    }

    my $Return = 1;

    if ( defined( $Param{Type} ) && $Param{Type} eq 'ReturnFalse' && $Reduce ) {
        $Return = 0;
    }
    else {

        # output the image
        $Image->object(
            $ImageFile, $Position{X}, $Position{Y} - $Param{Height},
            $Param{Width}, $Param{Height},
        );

        # set new position
        $Self->_CurPositionSet(
            Y => $Position{Y} - $Param{Height},
        );
    }

    return $Return;
}

=item HLine()

Output a horizontal line

    $True = $PDFObject->HLine(
        Width     => 300,           # (optional) default 'end of printable dimension'
        Type      => 'ReturnFalse'  # (optional) default Cut (ReturnFalse|Cut)
        Color     => '#101010',     # (optional) default black
        LineWidth => 1,             # (optional) default 1
    );

=cut

sub HLine {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    my %Dim;

    # get current position
    my %Position = $Self->_CurPositionGet();

    # get dimension (printable or content)
    if ( $Self->DimGet() eq 'printable' ) {
        %Dim = $Self->_CurPrintableDimGet();
    }
    else {
        %Dim = $Self->_CurContentDimGet();
    }

    # set default color
    $Param{Color} = $Param{Color} || 'black';

    # check LineWidth
    if ( !defined( $Param{LineWidth} ) || $Param{LineWidth} <= 0 || $Param{LineWidth} > 100 ) {
        $Param{LineWidth} = 1;
    }

    my $Cut = 0;

    if ( $Position{Y} - $Param{LineWidth} < $Dim{Bottom} ) {
        $Param{LineWidth} = $Position{Y} - $Dim{Bottom};
        if ( $Param{LineWidth} < 1 ) {
            $Param{LineWidth} = 1;
        }
        $Cut = 1;
    }
    $Param{LineWidth} = 0 - $Param{LineWidth};

    # check Width
    if ( defined( $Param{Width} ) && $Param{Width} >= 1 ) {
        if ( $Position{X} + $Param{Width} > $Dim{Left} + $Dim{Width} ) {
            $Param{Width} = $Dim{Left} + $Dim{Width} - $Position{X};
            $Cut = 1;
        }
    }
    else {
        $Param{Width} = $Param{Width} = $Dim{Left} + $Dim{Width} - $Position{X};
    }

    # output the lines in top and bottom of the page
    my $Line = $Self->{Page}->graphics();
    $Line->fillcolor( $Param{Color} );

    # check values
    my $Output = 0;
    if (
        $Self->DimGet() eq 'printable'
        && $Self->_CurPrintableDimCheck(
            X => $Position{X},
            Y => $Position{Y}
        )
        && $Self->_CurPrintableDimCheck(
            X => $Position{X} + $Param{Width},
            Y => $Position{Y} - $Param{LineWidth}
        )
    ) {
        $Output = 1;
    }
    elsif (
        $Self->_CurContentDimCheck(
            X => $Position{X},
            Y => $Position{Y}
        )
        && $Self->_CurContentDimCheck(
            X => $Position{X} + $Param{Width},
            Y => $Position{Y} - $Param{LineWidth}
        )
    ) {
        $Output = 1;
    }

    if ( defined( $Param{Type} ) && $Param{Type} eq 'ReturnFalse' && $Cut ) {
        $Output = 1;
    }

    if ($Output) {

        # output line
        $Line->rect( $Position{X}, $Position{Y}, $Param{Width}, $Param{LineWidth} );
        $Line->fill();

        # set new position
        $Self->_CurPositionSet(
            Y => $Position{Y} - $Param{LineWidth},
        );
    }

    return $Output;
}

=item PositionSet()

Set new position on current page

    $True = $PDFObject->PositionSet(
        Move => 'absolut',  # (optional) default absolut (absolut|relativ)
        X    => 10,         # (optional) (<integer>|left|center|right)
        Y    => 20,         # (optional) (<integer>|top|middle|bottom)
    );

=cut

sub PositionSet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    my %Data;
    my %Dim;
    my %Position = $Self->_CurPositionGet();

    # get dimension (printable or content)
    if ( $Self->DimGet() eq 'printable' ) {
        $Data{Dim} = 'printable';
        %Dim = $Self->_CurPrintableDimGet();
    }
    else {
        $Data{Dim} = 'content';
        %Dim = $Self->_CurContentDimGet();
    }

    if ( defined( $Param{X} ) ) {
        if ( $Param{X} eq 'left' ) {
            $Data{X} = $Dim{Left};
        }
        elsif ( $Param{X} eq 'center' ) {
            $Data{X} = ( $Dim{Width} / 2 ) + $Dim{Left};
        }
        elsif ( $Param{X} eq 'right' ) {
            $Data{X} = $Dim{Left} + $Dim{Width};
        }
        else {
            if ( defined( $Param{Move} ) && $Param{Move} eq 'relativ' ) {
                if (
                    ( $Position{X} + $Param{X} ) >= $Dim{Left}
                    && ( $Position{X} + $Param{X} ) < ( $Dim{Left} + $Dim{Width} )
                ) {
                    $Data{X} = $Position{X} + $Param{X};
                }
                elsif ( ( $Position{X} + $Param{X} ) >= ( $Dim{Left} + $Dim{Width} ) ) {
                    $Data{X} = $Dim{Left} + $Dim{Width};
                }
                else {
                    $Data{X} = $Dim{Left};
                }
            }
            else {
                if ( $Param{X} >= $Dim{Left} && $Param{X} < ( $Dim{Left} + $Dim{Width} ) ) {
                    $Data{X} = $Param{X};
                }
                elsif ( $Param{X} >= ( $Dim{Left} + $Dim{Width} ) ) {
                    $Data{X} = $Dim{Left} + $Dim{Width};
                }
                else {
                    $Data{X} = $Dim{Left};
                }
            }
        }
    }

    if ( defined( $Param{Y} ) ) {
        if ( $Param{Y} eq 'top' ) {
            $Data{Y} = $Dim{Bottom} + $Dim{Height};
        }
        elsif ( $Param{Y} eq 'middle' ) {
            $Data{Y} = ( $Dim{Height} / 2 ) + $Dim{Bottom};
        }
        elsif ( $Param{Y} eq 'bottom' ) {
            $Data{Y} = $Dim{Bottom};
        }
        else {
            if ( defined( $Param{Move} ) && $Param{Move} eq 'relativ' ) {
                if (
                    ( $Position{Y} + $Param{Y} ) <= ( $Dim{Bottom} + $Dim{Height} )
                    && ( $Position{Y} + $Param{Y} ) > $Dim{Bottom}
                ) {
                    $Data{Y} = $Position{Y} + $Param{Y};
                }
                elsif ( ( $Position{Y} + $Param{Y} ) <= $Dim{Bottom} ) {
                    $Data{Y} = $Dim{Bottom};
                }
                else {
                    $Data{Y} = $Dim{Bottom} + $Dim{Height};
                }
            }
            else {
                if ( $Param{Y} > $Dim{Bottom} && $Param{Y} <= ( $Dim{Bottom} + $Dim{Height} ) ) {
                    $Data{Y} = $Param{Y};
                }
                elsif ( $Param{Y} <= $Dim{Bottom} ) {
                    $Data{Y} = $Dim{Bottom};
                }
                else {
                    $Data{Y} = $Dim{Bottom} + $Dim{Height};
                }
            }
        }
    }

    $Self->_CurPositionSet( %Data, );

    return 1;
}

=item PositionGet()

Get position on current page

    Return
        $Position{X}
        $Position{Y}

    %Position = $PDFObject->PositionGet();

=cut

sub PositionGet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    my %Position = $Self->_CurPositionGet();

    return %Position;
}

=item DimSet()

Set active dimension

    $Dim = $PDFObject->DimSet(
        Dim => 'printable',  # (optional) default content (content|printable)
    );

=cut

sub DimSet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    if ( defined( $Param{Dim} ) && $Param{Dim} eq 'printable' ) {
        $Self->{Current}->{Dim} = 'printable';
    }
    else {
        $Self->{Current}->{Dim} = 'content';
    }

    return $Self->{Current}->{Dim};
}

=item DimGet()

Get active dimension (printable or content)

    $Dim = $PDFObject->DimGet();

=cut

sub DimGet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    if ( $Self->{Current}->{Dim} eq 'printable' || $Self->{Current}->{Dim} eq 'content' ) {
        $Self->{Current}->{Dim} = 'content';
    }

    return $Self->{Current}->{Dim};
}

=begin Internal:

=item _TableCalculate()

calculate params of table.

    Return  # normally no return required, only references
        %Param

The returned hash is usually not needed, as the passed in references are
modified in place.
In case of missing or misused parameters, C<undef> is returned in scalar context
and an empty list is returned in list context.

    %Return = $PDFObject->_TableCalculate(
        CellData            => $CellData,     # 2D arrayref (see example)
        ColumnData          => $ColumnData,   # arrayref (see example)
        RowData             => $RowData,      # arrayref (see example)
        Width               => 300,           # (optional) default default maximal width
        Height              => 400,           # (optional) default minimal height
        Font                => 'Monospaced',  # (optional) default Proportional (see DocumentNew())
        FontSize            => 9,             # (optional) default 11
        FontColor           => 'red',         # (optional) default black
        FontColorEven       => 'blue',        # (optional) cell font color for even rows
        FontColorOdd        => 'green',       # (optional) cell font color for odd rows
        Align               => 'right',       # (optional) default left (left|center|right)
        Lead                => 3,             # (optional) default 1
        PaddingTop          => 10,            # (optional) top cell padding, overides Padding
        PaddingRight        => 30,            # (optional) right cell padding, overides Padding
        PaddingBottom       => 30,            # (optional) bottom cell padding, overides Padding
        PaddingLeft         => 10,            # (optional) left cell padding, overides Padding
        BackgroundColor     => '#101010',     # (optional) default white
        BackgroundColorEven => '#F0F0F0',     # (optional) cell background color for even rows
        BackgroundColorOdd  => '#A0A0A0',     # (optional) cell background color for odd rows
        Border              => 1,             # (optional) default 1 (values between 0 and 20)
        BorderColor         => '#FF0000',     # (optional) default black
    );

    $CellData = [
        [
            {
                Content         => "Cell 1 (Row 1, Column 1)",  # (optional)
                Font            => 'Monospaced',                # (optional)
                FontSize        => 13,                          # (optional)
                FontColor       => '#00FF00',                   # (optional)
                Align           => 'center',                    # (optional)
                Lead            => 7,                           # (optional)
                BackgroundColor => '#101010',                   # (optional)
            },
            {
                Content => "Cell 2 (Row 1, Column 2)",
            },
        ],
        [
            {
                Content => "Cell 3 (Row 2, Column 1)",
            },
            {
                Content => "Cell 4 (Row 2, Column 2)",
            },
        ],
    ];

    $ColumData = [        # this array was automaticly generated, if not given
        {
            Width => 11,  # (optional)
        },
        {
            Width => 44,
        },
    ];

    $RowData = [           # this array was automaticly generated, if not given
        {
            Height => 11,  # (optional)
        },
        {
            Height => 44,
        },
    ];

=cut

sub _TableCalculate {
    my ( $Self, %Param ) = @_;

    # check needed stuff
    for (
        qw(
        CellData ColumnData RowData
        Type Font FontSize Lead FontColor Align BackgroundColor Width Border BorderColor
        PaddingTop PaddingRight PaddingBottom PaddingLeft
        )
    ) {
        if ( !defined( $Param{$_} ) ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $_!"
            );
            return;
        }
    }
    if (
        ref( $Param{CellData} ) ne 'ARRAY'
        || ref( $Param{ColumnData} ) ne 'ARRAY'
        || ref( $Param{RowData} ) ne 'ARRAY'
    ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need array references of CellData, ColumnData and RowData!"
        );
        return;
    }
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    # analyse, if table is corrupt
    my $ColumnMax = 0;
    my $RowMax    = 0;
    for my $Row ( @{ $Param{CellData} } ) {
        if ( scalar(@$Row) > $ColumnMax ) {
            $ColumnMax = scalar(@$Row);
        }
        $RowMax++;
    }

    # repair, if table is corrupt
    if ( $RowMax == 0 ) {
        ${ $Param{CellData} }[0] = [];
        $RowMax = 1;
    }
    if ( $ColumnMax == 0 ) {
        $ColumnMax = 1;
    }

    # cut ColumnData, if to much values
    if ( defined( ${ $Param{ColumnData} }[$ColumnMax] ) ) {
        splice( @{ $Param{ColumnData} }, $ColumnMax );
    }

    # cut RowData, if to much values
    if ( defined( ${ $Param{RowData} }[$RowMax] ) ) {
        splice( @{ $Param{RowData} }, $RowMax );
    }

    my $RowCounter = 0;
    for my $Row ( @{ $Param{CellData} } ) {
        my $MinFontSize = 999;
        for ( my $ColumnCounter = 0; $ColumnCounter < $ColumnMax; $ColumnCounter++ ) {

            # repair, if row is corrupt
            if ( !defined( $Row->[$ColumnCounter] ) ) {
                $Row->[$ColumnCounter] = {};
            }

            # reference of current cell
            my $Cell = $Row->[$ColumnCounter];

            # if row is odd
            if ( $RowCounter & 1 ) {

                # set FontColor, if row is odd
                if (
                    !defined( $Cell->{FontColor} )
                    && defined( $Param{FontColorOdd} )
                ) {
                    $Cell->{FontColor} = $Param{FontColorOdd};
                }

                # set BackgroundColor, if row is odd
                if (
                    !defined( $Cell->{BackgroundColor} )
                    && defined( $Param{BackgroundColorOdd} )
                ) {
                    $Cell->{BackgroundColor} = $Param{BackgroundColorOdd};
                }
            }

            # if row is even
            else {

                # set FontColor, if row is even
                if (
                    !defined( $Cell->{FontColor} )
                    && defined( $Param{FontColorEven} )
                ) {
                    $Cell->{FontColor} = $Param{FontColorEven};
                }

                # set BackgroundColor, if row is even
                if (
                    !defined( $Cell->{BackgroundColor} )
                    && defined( $Param{BackgroundColorEven} )
                ) {
                    $Cell->{BackgroundColor} = $Param{BackgroundColorEven};
                }
            }

            # set cell state
            if ( !defined( $Cell->{Off} ) ) {
                $Cell->{Off} = 0;
            }

            # set temp cell state
            if ( !defined( $Cell->{TmpOff} ) ) {
                $Cell->{TmpOff} = 0;
            }

            # prepare text
            if ( defined( $Cell->{Content} ) ) {
                $Cell->{Content} = $Self->_PrepareText(
                    Text => $Cell->{Content},
                );
            }

            # set content blank, if not defined
            if (
                !defined( $Cell->{Content} )
                || $Cell->{Content} eq ''
            ) {
                $Cell->{Content} = ' ';
            }

            # set default values
            for (qw(Type Font FontSize FontColor Align Lead BackgroundColor)) {
                if ( !defined( $Cell->{$_} ) ) {
                    $Cell->{$_} = $Param{$_};
                }
            }

            # calculate width of complete column content
            if ( !defined( $Param{ColumnData}->[$ColumnCounter]->{MaxColWidth} ) ) {
                $Param{ColumnData}->[$ColumnCounter]->{MaxColWidth} = 0;
            }
            my $CompleteContentWidth = $Self->_StringWidth(
                Text     => $Cell->{Content},
                Font     => $Cell->{Font},
                FontSize => $Cell->{FontSize},
            );
            if ( $CompleteContentWidth > $Param{ColumnData}->[$ColumnCounter]->{MaxColWidth} ) {
                $Param{ColumnData}->[$ColumnCounter]->{MaxColWidth} = $CompleteContentWidth;
            }

            # calculate with of the greaterst word
            if ( !defined( $Param{ColumnData}->[$ColumnCounter]->{MinColWidth} ) ) {
                $Param{ColumnData}->[$ColumnCounter]->{MinColWidth} = 0;
            }
            my @Words = split( /\s+/, $Cell->{Content} );
            my $WordMaxLength = 0;
            for (@Words) {
                my $WordLength = length($_);
                if ( $WordMaxLength <= $WordLength + 2 ) {
                    $WordMaxLength = $WordLength;

                    # calculate width of word
                    my $WordWidth = $Self->_StringWidth(
                        Text     => $_,
                        Font     => $Cell->{Font},
                        FontSize => $Cell->{FontSize},
                    );
                    if ( $WordWidth > $Param{ColumnData}->[$ColumnCounter]->{MinColWidth} ) {
                        $Param{ColumnData}->[$ColumnCounter]->{MinColWidth} = $WordWidth;
                    }
                }
            }

            # find the smallerst fontsize
            if ( $Cell->{FontSize} < $MinFontSize ) {
                $MinFontSize = $Cell->{FontSize};
            }
        }

        # set MinFontSize
        $Param{RowData}->[$RowCounter]->{MinFontSize} = $MinFontSize;
        $RowCounter++;
    }

    # estimate width of columns (without padding and border)
    for my $Column ( @{ $Param{ColumnData} } ) {
        if ( !defined( $Column->{Width} ) ) {
            $Column->{Width} = 0;
        }
        if ( $Column->{Width} > 0 ) {
            $Column->{EstimateWidth} = $Column->{Width};
        }
        else {

            # estimate width of column
            $Column->{EstimateWidth} = ( $Column->{MaxColWidth} + $Column->{MinColWidth} ) / 2;
        }

        # reduce calculated width and width, if calculated width is greater than table width
        my $MaxWidth = $Param{Width} - $Param{PaddingLeft} - $Param{PaddingRight} - ( 2 * $Param{Border} );
        if ( $Column->{EstimateWidth} > $MaxWidth ) {
            $Column->{EstimateWidth} = $MaxWidth;
            if ( $Column->{Width} > 0 ) {
                $Column->{Width} = $MaxWidth;
            }
        }

        # set width to 1, if width is too small
        if ( $Column->{EstimateWidth} < 1 ) {
            $Column->{EstimateWidth} = 1;
        }
    }

    # calculate exactly width of columns
    my $ColumnBlocks = [];
    $ColumnBlocks->[0]->{Width}       = 0;
    $ColumnBlocks->[0]->{ColumnStart} = 0;
    $ColumnBlocks->[0]->{ColumnStop}  = 0;
    $ColumnBlocks->[0]->{ColumnFix}   = 0;
    $ColumnBlocks->[0]->{ColumnDyn}   = 0;

    my $Block   = 0;
    my $Counter = 0;
    for my $Column ( @{ $Param{ColumnData} } ) {
        my $ColumnWidth = $Column->{EstimateWidth}
            + $Param{PaddingLeft}
            + $Param{PaddingRight}
            + ( 2 * $Param{Border} );

        if ( !$ColumnBlocks->[$Block]->{Width} ) {
            $ColumnBlocks->[$Block]->{Width} = $ColumnWidth;
        }
        else {
            if (
                ( $ColumnBlocks->[$Block]->{Width} + $ColumnWidth - $Param{Border} ) > $Param{Width}
            ) {
                $ColumnBlocks->[$Block]->{ColumnStop} = $Counter - 1;
                $Block++;
                $ColumnBlocks->[$Block]->{Width}       = $ColumnWidth;
                $ColumnBlocks->[$Block]->{ColumnStart} = $Counter;
                $ColumnBlocks->[$Block]->{ColumnFix}   = 0;
                $ColumnBlocks->[$Block]->{ColumnDyn}   = 0;
            }
            else {
                $ColumnBlocks->[$Block]->{Width} += $ColumnWidth - $Param{Border};
            }
            $ColumnBlocks->[$Block]->{ColumnStop} = $Counter;
        }

        if ( $Column->{Width} > 0 ) {
            $ColumnBlocks->[$Block]->{ColumnFix}++;
        }
        else {
            $ColumnBlocks->[$Block]->{ColumnDyn}++;
        }

        $Counter++;
    }
    my $LastBlock = $#{$ColumnBlocks};
    my $Counter2  = 0;
    for my $CurBlock ( @{$ColumnBlocks} ) {
        my $ExtraSpaceComplete;

        # no extra space for laast block
        if ( $Counter2 && $Counter2 eq $LastBlock ) {
            $ExtraSpaceComplete = 0;
        }
        else {
            $ExtraSpaceComplete = $Param{Width} - $CurBlock->{Width};
        }

        my $ExtraSpaceDyn = 0;
        my $ExtraSpaceFix = 0;

        if ( $CurBlock->{ColumnDyn} > 0 ) {
            $ExtraSpaceDyn = $ExtraSpaceComplete / $CurBlock->{ColumnDyn};
        }
        else {
            $ExtraSpaceFix = $ExtraSpaceComplete / $CurBlock->{ColumnFix};
        }

        for ( $CurBlock->{ColumnStart} .. $CurBlock->{ColumnStop} ) {
            my $Column     = $Param{ColumnData}->[$_];
            my $ExtraSpace = 0;
            if ( $Column->{Width} > 0 ) {
                $ExtraSpace = $ExtraSpaceFix;
            }
            else {
                $ExtraSpace = $ExtraSpaceDyn;
            }

            $Column->{OutputWidth} = $Column->{EstimateWidth}
                + $ExtraSpace
                + $Param{PaddingLeft}
                + $Param{PaddingRight}
                + ( 2 * $Param{Border} );
            $Column->{TextWidth} = $Column->{EstimateWidth} + $ExtraSpace;

            if ( $Column->{OutputWidth} < 1 ) {
                $Column->{OutputWidth} = 1;
            }
            if ( $Column->{TextWidth} < 1 ) {
                $Column->{TextWidth} = 1;
            }
            $Column->{Block} = $Counter2;
        }
        $Counter2++;
    }

    return %Param;
}

=item _TableBlockNextCalculate()

calculate what block can output next

   Return
       $Return{State}
       $Return{ReturnBlock}
       $Return{ReturnRowStart}
       $Return{ReturnColumnStart}
       $Return{ReturnColumnStop}

   %Return = $PDFObject->_TableBlockNextCalculate(
       CellData   => $CellData,    # 2D arrayref
       ColumnData => $ColumnData,  # arrayref
   );

=cut

sub _TableBlockNextCalculate {
    my ( $Self, %Param ) = @_;

    my %Return = (
        State             => 0,
        ReturnBlock       => 0,
        ReturnRowStart    => 0,
        ReturnColumnStart => 0,
        ReturnColumnStop  => 0,
    );

    # check needed stuff
    for (qw(CellData ColumnData)) {
        if ( !defined $Param{$_} ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $_!"
            );
            return;
        }
    }
    if ( ref $Param{CellData} ne 'ARRAY' || ref $Param{ColumnData} ne 'ARRAY' ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need array references of CellData and ColumnData!"
        );
        return;
    }
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    my $RowStart    = 'NULL';
    my $ColumnStart = 'NULL';
    my $ColumnStop  = 0;

    # calculate, what cells can output (what cells are active)
    my $RowCounter = 0;
    for my $Row ( @{ $Param{CellData} } ) {

        # if last block was temporary off, reactivate the row
        if ( $Param{CellData}->[$RowCounter]->[ $#{ $Param{CellData}->[$RowCounter] } ]->{TmpOff} ) {
            for ( my $ColumnCounter = 0; $ColumnCounter < scalar(@$Row); $ColumnCounter++ ) {
                $Row->[$ColumnCounter]->{TmpOff} = 0;
            }
        }

        # now calculate, what cells can output (what cells are active)
        COLUMN_COUNTER:
        for ( my $ColumnCounter = 0; $ColumnCounter < scalar @$Row; $ColumnCounter++ ) {

            # calculate RowStart and ColumnStart
            if (
                $Row->[$ColumnCounter]->{Off} ne "1"
                && $Row->[$ColumnCounter]->{TmpOff} ne "1"
                && $RowStart eq 'NULL'
                && $ColumnStart eq 'NULL'
            ) {
                $RowStart    = $RowCounter;
                $ColumnStart = $ColumnCounter;
                $ColumnStop  = $ColumnStart;
                last COLUMN_COUNTER;
            }
        }
        $RowCounter++;
    }

    if ( $RowStart ne 'NULL' && $ColumnStart ne 'NULL' ) {

        # find last column of block
        my $Block         = $Param{ColumnData}->[$ColumnStart]->{Block};
        my $ColumnCounter = 0;
        for my $Column ( @{ $Param{ColumnData} } ) {
            if (
                $ColumnCounter > $ColumnStop
                && $Column->{Block} eq $Block
            ) {
                $ColumnStop = $ColumnCounter;
            }
            $ColumnCounter++;
        }

        $Return{State}             = 1;
        $Return{ReturnBlock}       = $Block;
        $Return{ReturnRowStart}    = $RowStart;
        $Return{ReturnColumnStart} = $ColumnStart;
        $Return{ReturnColumnStop}  = $ColumnStop;
    }

    return %Return;
}

=item _TableRowCalculate()

calculate row of table

   Return  # normally no return required, only references
       %Param

   %Return = $PDFObject->_TableRowCalculate(
       CellData   => $CellData,    # 2D arrayref
       RowData    => $RowData,     # arrayref
       ColumnData => $ColumnData,  # arrayref
       Row        => 3,            # current row
   );

=cut

sub _TableRowCalculate {
    my ( $Self, %Param ) = @_;

    # check needed stuff
    for (qw(CellData RowData ColumnData Row)) {
        if ( !defined( $Param{$_} ) ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $_!"
            );
            return;
        }
    }
    if (
        ref( $Param{CellData} ) ne 'ARRAY'
        || ref( $Param{ColumnData} ) ne 'ARRAY'
        || ref( $Param{RowData} ) ne 'ARRAY'
    ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need array references of CellData, ColumnData and RowData!"
        );
        return;
    }
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    if ( $Param{RowData}->[ $Param{Row} ]->{Height} ) {
        $Param{RowData}->[ $Param{Row} ]->{TextHeight} = $Param{RowData}->[ $Param{Row} ]->{Height};
    }
    else {

        # calculate height of row
        $Param{RowData}->[ $Param{Row} ]->{Height}     = 0;
        $Param{RowData}->[ $Param{Row} ]->{TextHeight} = 0;
        my $BiggerstFontSize = 0;
        my $ColumnCounter    = 0;
        for my $Column ( @{ $Param{ColumnData} } ) {
            my $Cell      = $Param{CellData}->[ $Param{Row} ]->[$ColumnCounter];
            my %Calculate = $Self->_TextCalculate(
                Text     => $Cell->{Content},
                Type     => 'ReturnLeftOver',
                Width    => $Column->{TextWidth},
                Height   => 1000000,
                Font     => $Cell->{Font},
                FontSize => $Cell->{FontSize},
                Lead     => $Cell->{Lead},
            );
            if ( $Calculate{RequiredHeight} > $Param{RowData}->[ $Param{Row} ]->{TextHeight} ) {
                $Param{RowData}->[ $Param{Row} ]->{TextHeight} = $Calculate{RequiredHeight};
            }
            if ( $Cell->{FontSize} > $BiggerstFontSize ) {
                $BiggerstFontSize = $Cell->{FontSize};
            }
            $ColumnCounter++;
        }
        if ( !$Param{RowData}->[ $Param{Row} ]->{TextHeight} ) {
            $Param{RowData}->[ $Param{Row} ]->{TextHeight} = $BiggerstFontSize;
        }
    }
    $Param{RowData}->[ $Param{Row} ]->{OutputHeight} = $Param{RowData}->[ $Param{Row} ]->{TextHeight}
        + $Param{PaddingTop}
        + $Param{PaddingBottom}
        + ( 2 * $Param{Border} );

    return %Param;
}

=item _TableCellOutput()

output a cell of a table

   Return
       $Return{State}
       $Return{RequiredWidth}
       $Return{RequiredHeight}
       $Return{LeftOver}

   %Return = $PDFObject->_TableCellOutput(
       Width           => 70,
       Height          => 40,
       Text            => 'Text',
       Type            => 'Cut',
       Font            => 'ProportionalBold',
       FontSize        => 15,
       FontColor       => '#FF0000',
       Align           => 'center',
       Lead            => 20,
       PaddingTop      => 10,
       PaddingRight    => 30,
       PaddingBottom   => 30,
       PaddingLeft     => 10,
       BackgroundColor => '#101010',
       Border          => 1,
       BorderColor     => '#FF0000',
   );

=cut

sub _TableCellOutput {
    my ( $Self, %Param ) = @_;

    my %Return = (
        State          => 0,
        RequiredWidth  => 0,
        RequiredHeight => 0,
        LeftOver       => '',
    );

    # check needed stuff
    for (
        qw(Width Height Text Type Font FontSize FontColor Align Lead
        PaddingTop PaddingRight PaddingBottom PaddingLeft BackgroundColor Border BorderColor)
    ) {
        if ( !defined( $Param{$_} ) ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $_!"
            );
            return;
        }
    }
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }
    my %Dim;

    # get dimension (printable or content)
    if ( $Self->DimGet() eq 'printable' ) {
        %Dim = $Self->_CurPrintableDimGet();
    }
    else {
        %Dim = $Self->_CurContentDimGet();
    }

    # get current position
    my %Position = $Self->_CurPositionGet();

    # output background
    if ( $Param{BackgroundColor} ne 'NULL' ) {
        my $Background = $Self->{Page}->graphics();
        $Background->fillcolor( $Param{BackgroundColor} );
        $Background->rect( $Position{X}, $Position{Y}, $Param{Width}, -( $Param{Height} ) );
        $Background->fill();
    }

    # output top border
    if ( $Param{Border} > 0 ) {
        my $BorderTop = $Self->{Page}->graphics();
        $BorderTop->fillcolor( $Param{BorderColor} );
        $BorderTop->rect( $Position{X}, $Position{Y}, $Param{Width}, -( $Param{Border} ) );
        $BorderTop->fill();
    }

    # output right border
    if ( $Param{Border} > 0 ) {
        my $BorderRight = $Self->{Page}->graphics();
        $BorderRight->fillcolor( $Param{BorderColor} );
        $BorderRight->rect(
            ( $Position{X} + $Param{Width} - $Param{Border} ),
            $Position{Y}, $Param{Border}, -( $Param{Height} )
        );
        $BorderRight->fill();
    }

    # output bottom border
    if ( $Param{Border} > 0 ) {
        my $BorderBottom = $Self->{Page}->graphics();
        $BorderBottom->fillcolor( $Param{BorderColor} );
        $BorderBottom->rect(
            $Position{X}, ( $Position{Y} - $Param{Height} + $Param{Border} ),
            $Param{Width}, -( $Param{Border} )
        );
        $BorderBottom->fill();
    }

    # output left border
    if ( $Param{Border} > 0 ) {
        my $BorderLeft = $Self->{Page}->graphics();
        $BorderLeft->fillcolor( $Param{BorderColor} );
        $BorderLeft->rect( $Position{X}, $Position{Y}, $Param{Border}, -( $Param{Height} ) );
        $BorderLeft->fill();
    }

    # calculate text start position
    my $TextX = $Position{X} + $Param{Border} + $Param{PaddingLeft};
    my $TextY = $Position{Y} - $Param{Border} - $Param{PaddingTop} + 1;

    # calculate width and height of text
    my $TextWidth  = $Param{Width} - $Param{PaddingLeft} - $Param{PaddingRight} -  ( 2 * $Param{Border} );
    my $TextHeight = $Param{Height} - $Param{PaddingTop} - $Param{PaddingBottom} - ( 2 * $Param{Border} );

    # set new position
    $Self->PositionSet(
        X => $TextX,
        Y => $TextY,
    );

    %Return = $Self->Text(
        Text     => $Param{Text},
        Type     => $Param{Type},
        Width    => $TextWidth,
        Height   => $TextHeight,
        Font     => $Param{Font},
        FontSize => $Param{FontSize},
        Color    => $Param{FontColor},
        Align    => $Param{Align},
        Lead     => $Param{Lead},
    );

    return %Return;
}

=item _TableCellOnCount()

count all active cells

   Return
       $CellCount

   $Count = $PDFObject->_TableCellOnCount(
       CellData => $CellData,  # 2D arrayref
   );

=cut

sub _TableCellOnCount {
    my ( $Self, %Param ) = @_;

    my $Return = 0;

    # check needed stuff
    for (qw(CellData)) {
        if ( !defined( $Param{$_} ) ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $_!"
            );
            return;
        }
    }
    if ( ref( $Param{CellData} ) ne 'ARRAY' ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need array references of CellData!"
        );
        return;
    }
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }
    for my $Row ( @{ $Param{CellData} } ) {
        for ( my $ColumnCounter = 0; $ColumnCounter < scalar(@$Row); $ColumnCounter++ ) {
            if ( $Row->[$ColumnCounter]->{Off} ne "1" ) {
                $Return++;
            }
        }
    }

    return $Return;
}

=item _TextCalculate()

calculate required values of given text

   Return
       $Return{State}
       $Return{RequiredWidth}
       $Return{RequiredHeight}
       $Return{LeftOver}
       $Return{PossibleRows}  # (Array Ref)

   %Return = $PDFObject->_TextCalculate(
       Text     => $Text,               # text
       Type     => 'Cut',               # (ReturnLeftOver|ReturnLeftOverHard|Cut)
       Width    => 300,                 # available width
       Height   => 200,                 # available height
       Font     => 'ProportionalBold',  # font of text
       FontSize => 6,                   # fontsize of text
       Lead     => 20,                  # lead
   );

=cut

sub _TextCalculate {
    my ( $Self, %Param ) = @_;

    my %Return = (
        State          => 0,
        RequiredWidth  => 0,
        RequiredHeight => 0,
        LeftOver       => '',
    );
    my @PossibleRows;

    # check needed stuff
    for (qw(Text Type Width Height Font FontSize Lead)) {
        if ( !defined( $Param{$_} ) ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $_!"
            );
            return;
        }
    }
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }
    my $TextLength = 0;

    if ( $Param{Width} <= 0 || $Param{Height} <= 0 ) {
        $Return{LeftOver} = $Param{Text};
        $Param{Text}      = undef;
    }
    else {
        $Param{Text} = $Self->_PrepareText(
            Text => $Param{Text},
        );
        $TextLength = length( $Param{Text} );
    }
    my $Counter1 = 0;
    while ( defined( $Param{Text} ) ) {
        my $Row;
        my $DelPreSpace = 0;

        # get next row of given text
        if ( $Param{Text} =~ s/^(.*?)\n(.*)/$2/s ) {
            $Row = $1;
        }
        else {
            $Row = $Param{Text};
            $Param{Text} = undef;
        }

        # delete one space at begin of row, if exists
        $Row =~ s/^\s//;

        # calculate width of the row
        my $RowWidth = $Self->_StringWidth(
            Text     => $Row,
            Font     => $Param{Font},
            FontSize => $Param{FontSize},
        );

        # calculate height of the row
        my $RowHeight = $Param{FontSize};
        if ( $Counter1 > 0 ) {
            $RowHeight += $Param{Lead};
        }

        if ( $Return{RequiredHeight} + $RowHeight <= $Param{Height} ) {

            # if row is greater then $Param{Width}
            if ( $RowWidth > $Param{Width} ) {

                # estimate point of cut
                my $Factor = $RowWidth / $Param{Width};
                my $Cut    = int( length($Row) / $Factor );

                # cut the row
                my $RowFore = substr( $Row, 0, $Cut );
                my $RowRear = substr( $Row, $Cut );

                # calculate width of fore row
                my $RowForeWidth = $Self->_StringWidth(
                    Text     => $RowFore,
                    Font     => $Param{Font},
                    FontSize => $Param{FontSize},
                );

                # caculate exactly point of cut
                while ( $RowForeWidth < $Param{Width} ) {
                    $RowFore .= substr( $RowRear, 0, 1 );
                    $RowRear = substr( $RowRear, 1 );
                    $RowForeWidth = $Self->_StringWidth(
                        Text     => $RowFore,
                        Font     => $Param{Font},
                        FontSize => $Param{FontSize},
                    );
                }
                while ( $RowForeWidth > $Param{Width} ) {
                    $RowRear      = chop($RowFore) . $RowRear;
                    $RowForeWidth = $Self->_StringWidth(
                        Text     => $RowFore,
                        Font     => $Param{Font},
                        FontSize => $Param{FontSize},
                    );
                }

                if ( $Param{Type} eq 'ReturnLeftOver' || $Param{Type} eq 'Cut' ) {
                    if ( $RowFore =~ /[^\s]$/ && $RowRear =~ /^[^\s]/ ) {
                        $RowFore =~ s/^(.*)(\s+.+?)$/$1/;
                        if ($2) {
                            $RowRear = $2 . $RowRear;
                        }
                    }
                }

                $Row = $RowFore;
                if ( $Param{Text} ) {
                    $Param{Text} = $RowRear . "\n" . $Param{Text};
                }
                else {
                    $Param{Text} = $RowRear;
                }
            }

            # delete spaces at end of row, if spaces exists
            $Row =~ s/^(.*)\s$/$1/;

            # add Row to PossibleRows array
            push( @PossibleRows, $Row );
            $Return{RequiredHeight} += $RowHeight;

            # check, if min one character can count (protection of infinite loop)
            if ( defined( $Param{Text} ) ) {
                if ( length( $Param{Text} ) >= $TextLength ) {
                    $Return{RequiredWidth}  = 0;
                    $Return{RequiredHeight} = 0;
                    $Return{LeftOver}       = $Param{Text};
                    $Param{Text}            = undef;
                    @PossibleRows           = ();
                }
                else {
                    $TextLength = length( $Param{Text} );
                }
            }
        }
        else {
            $Return{LeftOver} = $Row;
            if ( $Param{Text} ) {
                $Return{LeftOver} .= "\n" . $Param{Text};
                $Param{Text} = undef;
            }
        }
        $Counter1++;
    }

    # cut text if type is Cut
    if ( $Param{Type} eq 'Cut' && $Return{LeftOver} ) {
        my $LastRow = $PossibleRows[-1];
        if ($LastRow) {

            # calculate width [..]
            my $PPWidth = $Self->_StringWidth(
                Text     => '[..]',
                Font     => $Param{Font},
                FontSize => $Param{FontSize},
            );
            if ( $PPWidth <= $Param{Width} ) {

                # calculate width of LastRow and [..]
                my $TextCutWidth = $Self->_StringWidth(
                    Text     => $LastRow,
                    Font     => $Param{Font},
                    FontSize => $Param{FontSize},
                );

                # calculate last line
                while ( $TextCutWidth + $PPWidth > $Param{Width} ) {
                    chop($LastRow);

                    # calculate width of shorted LastRow and [..]
                    $TextCutWidth = $Self->_StringWidth(
                        Text     => $LastRow,
                        Font     => $Param{Font},
                        FontSize => $Param{FontSize},
                    );
                }
                $PossibleRows[-1] = $LastRow . '[..]';

            }
            $Return{LeftOver} = '';
        }
    }

    # calculate RequiredWidth
    my $Counter2 = 0;
    for (@PossibleRows) {
        my $RowWidth = $Self->_StringWidth(
            Text     => $_,
            Font     => $Param{Font},
            FontSize => $Param{FontSize},
        );

        # set new RequiredWidth
        if ( $RowWidth > $Return{RequiredWidth} ) {
            $Return{RequiredWidth} = $RowWidth;
        }

        $Counter2++;
    }

    # correct RequiredHeight
    if ( $Return{RequiredWidth} eq "0" ) {
        $Return{RequiredHeight} = 0;
    }

    # set state
    if ( !$Return{LeftOver} ) {
        $Return{State} = 1;
    }

    $Return{PossibleRows} = \@PossibleRows;

    return %Return;
}

=item _StringWidth()

calculate width of given text

   $Width = $PDFObject->_StringWidth(
       Text     => 'Text',              # text
       Font     => 'ProportionalBold',  # font of text
       FontSize => 6,                   # fontsize of text
   );

=cut

sub _StringWidth {
    my ( $Self, %Param ) = @_;

    # check needed stuff
    for (qw(Text Font FontSize)) {
        if ( !defined $Param{$_} ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $_!"
            );
            return;
        }
    }

    # check document
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }

    # check page
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    return $Self->{CacheStringWidth}->{ $Param{Font} }->{ $Param{FontSize} }->{ $Param{Text} }
        if $Self->{CacheStringWidth}->{ $Param{Font} }->{ $Param{FontSize} }->{ $Param{Text} };

    # create a text object
    $Self->{TextWidthObject} ||= $Self->{Page}->text();

    # set font and fontsize
    $Self->{TextWidthObject}->font( $Self->{Font}->{ $Param{Font} }, $Param{FontSize} );

    # calculate width of given text
    my $StringWidth = $Self->{TextWidthObject}->text_width( $Param{Text} );

    return $StringWidth if length $Param{Text} > 20;

    # write width cache if length is not more than 20 chars
    $Self->{CacheStringWidth}->{ $Param{Font} }->{ $Param{FontSize} }->{ $Param{Text} } = $StringWidth;

    return $StringWidth;
}

=item _PrepareText()

prepare given text for output

   $Width = $PDFObject->_PrepareText(
       Text => 'Text',  # text
   );

=cut

sub _PrepareText {
    my ( $Self, %Param ) = @_;

    # check needed stuff
    for (qw(Text)) {
        if ( !defined( $Param{$_} ) ) {
            $Kernel::OM->Get('Kernel::System::Log')->Log(
                Priority => 'error',
                Message  => "Need $_!"
            );
            return;
        }
    }
    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    # prepare new line
    $Param{Text} =~ s/(\n\r|\r\r\n|\r\n)/\n/g;
    $Param{Text} =~ s/\r/\n/g;

    # convert page brake to new lines
    $Param{Text} =~ s/\f/\n\n/g;

    # convert tabs to spaces
    $Param{Text} =~ s/\t/  /g;

    return $Param{Text};
}

=item _CurPageNumberSet()

set number of current page

   $PDFObject->_CurPageNumberSet(
       ShowPageNumber => 0,  # (optional) default 1
   );

=cut

sub _CurPageNumberSet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    # set number of all over pages to 0, if first page
    if ( !defined( $Self->{Current}->{Page} ) ) {
        $Self->{Current}->{Page} = 0;
    }

    # set number of displayed pages to 0, if first page
    if ( !defined( $Self->{Current}->{PageNumber} ) ) {
        $Self->{Current}->{PageNumber} = 0;
    }

    # increment all over pages
    $Self->{Current}->{Page}++;

    # set page number of current page
    if ( $Param{ShowPageNumber} eq "0" ) {
        $Self->{PageData}->{ $Self->{Current}->{Page} }->{PageNumber} = '';
    }
    else {
        $Self->{Current}->{PageNumber}++;
        $Self->{PageData}->{ $Self->{Current}->{Page} }->{PageNumber} = $Self->{Current}->{PageNumber};
    }

    return 1;
}

=item _CurPageDimSet()

Set current Page Dimension

   $PDFObject->_CurPageDimSet(
       Width           => 123,          # (optional) default 595 (Din A4)
       Height          => 321,          # (optional) default 842 (Din A4)
       PageOrientation => 'landscape',  # (optional) (normal|landscape)
   );

=cut

sub _CurPageDimSet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    my $NewValue;

    # set CurPageWidth
    if ( defined( $Param{Width} ) && $Param{Width} >= 100 && $Param{Width} <= 10000 ) {
        $Self->{Current}->{PageWidth} = int( $Param{Width} );
        $NewValue = 1;
    }

    # set CurPageHeight
    if ( defined( $Param{Height} ) && $Param{Height} >= 100 && $Param{Height} <= 10000 ) {
        $Self->{Current}->{PageHeight} = int( $Param{Height} );
        $NewValue = 1;
    }

    # get default pagesize
    my $DefaultWidth  = 595;    # DIN A4
    my $DefaultHeight = 842;    # DIN A4
    if ( $Kernel::OM->Get('Kernel::Config')->Get('PDF::PageSize') eq 'letter' ) {
        $DefaultWidth  = 612;
        $DefaultHeight = 792;
    }

    # set page orientation
    if ( defined( $Param{PageOrientation} ) && $Param{PageOrientation} eq 'landscape' ) {
        my $TmpWidth = $DefaultWidth;
        $DefaultWidth  = $DefaultHeight;
        $DefaultHeight = $TmpWidth;
    }

    # set default values
    if ( !defined( $Self->{Current}->{PageWidth} ) ) {
        $Self->{Current}->{PageWidth} = $DefaultWidth;
        $NewValue = 1;
    }
    if ( !defined( $Self->{Current}->{PageHeight} ) ) {
        $Self->{Current}->{PageHeight} = $DefaultHeight;
        $NewValue = 1;
    }

    if ($NewValue) {

        # set new printable dimension
        $Self->{Current}->{PrintableTop}    = 0;
        $Self->{Current}->{PrintableRight}  = 0;
        $Self->{Current}->{PrintableBottom} = 0;
        $Self->{Current}->{PrintableLeft}   = 0;
        $Self->{Current}->{PrintableWidth}  = $Self->{Current}->{PageWidth};
        $Self->{Current}->{PrintableHeight} = $Self->{Current}->{PageHeight};

        # set new content dimension
        $Self->{Current}->{ContentTop}    = $Self->{Current}->{PrintableTop};
        $Self->{Current}->{ContentRight}  = $Self->{Current}->{PrintableRight};
        $Self->{Current}->{ContentBottom} = $Self->{Current}->{PrintableBottom};
        $Self->{Current}->{ContentLeft}   = $Self->{Current}->{PrintableLeft};
        $Self->{Current}->{ContentWidth}  = $Self->{Current}->{PrintableWidth};
        $Self->{Current}->{ContentHeight} = $Self->{Current}->{PrintableHeight};

        # set new current position
        $Self->{Current}->{PositionX} = $Self->{Current}->{ContentLeft};
        $Self->{Current}->{PositionY} = $Self->{Current}->{PageHeight} - $Self->{Current}->{ContentTop};
    }

    return 1;
}

=item _CurPageDimGet()

Get current Page Dimension (Width, Height)

   Return
       $CurPageDim{Width}
       $CurPageDim{Height}

   %CurPageDim = $PDFObject->_CurPageDimGet();

=cut

sub _CurPageDimGet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    if ( !$Self->{Current}->{PageWidth} || !$Self->{Current}->{PageHeight} ) {
        $Self->_CurPageDimSet();
    }

    my %Data;
    if ( $Self->{Current}->{PageWidth} && $Self->{Current}->{PageHeight} ) {
        $Data{Width}  = $Self->{Current}->{PageWidth};
        $Data{Height} = $Self->{Current}->{PageHeight};
    }

    return %Data;
}

=item _CurPrintableDimSet()

Set current Printable Dimension

   $True = $PDFObject->_CurPrintableDimSet(
       Top    => 20,  # (optional)
       Right  => 20,  # (optional)
       Bottom => 20,  # (optional)
       Left   => 20,  # (optional)
   );

=cut

sub _CurPrintableDimSet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    if ( $Self->{Current}->{PageWidth} && $Self->{Current}->{PageHeight} ) {
        my $NewValue;

        # set CurPrintableTop
        if (
            defined( $Param{Top} )
            && $Param{Top} > 0
            && $Param{Top} < $Self->{Current}->{PageHeight} / 2
        ) {
            $Self->{Current}->{PrintableTop} = $Param{Top};
            $NewValue = 1;
        }

        # set CurPrintableRight
        if (
            defined( $Param{Right} )
            && $Param{Right} > 0
            && $Param{Right} < $Self->{Current}->{PageWidth} / 2
        ) {
            $Self->{Current}->{PrintableRight} = $Param{Right};
            $NewValue = 1;
        }

        # set CurPrintableBottom
        if (
            defined( $Param{Bottom} )
            && $Param{Bottom} > 0
            && $Param{Bottom} < $Self->{Current}->{PageHeight} / 2
        ) {
            $Self->{Current}->{PrintableBottom} = $Param{Bottom};
            $NewValue = 1;
        }

        # set CurPrintableLeft
        if (
            defined( $Param{Left} )
            && $Param{Left} > 0
            && $Param{Left} < $Self->{Current}->{PageWidth} / 2
        ) {
            $Self->{Current}->{PrintableLeft} = $Param{Left};
            $NewValue = 1;
        }

        if ($NewValue) {

            # calculate new printable width and height
            $Self->{Current}->{PrintableWidth} = $Self->{Current}->{PageWidth}
                - $Self->{Current}->{PrintableLeft}
                - $Self->{Current}->{PrintableRight};
            $Self->{Current}->{PrintableHeight} = $Self->{Current}->{PageHeight}
                - $Self->{Current}->{PrintableTop}
                - $Self->{Current}->{PrintableBottom};

            # set new content dimension
            $Self->{Current}->{ContentTop}    = $Self->{Current}->{PrintableTop};
            $Self->{Current}->{ContentRight}  = $Self->{Current}->{PrintableRight};
            $Self->{Current}->{ContentBottom} = $Self->{Current}->{PrintableBottom};
            $Self->{Current}->{ContentLeft}   = $Self->{Current}->{PrintableLeft};
            $Self->{Current}->{ContentWidth}  = $Self->{Current}->{PrintableWidth};
            $Self->{Current}->{ContentHeight} = $Self->{Current}->{PrintableHeight};

            # set new current position
            $Self->{Current}->{PositionX} = $Self->{Current}->{ContentLeft};
            $Self->{Current}->{PositionY} = $Self->{Current}->{PageHeight} - $Self->{Current}->{ContentTop};
        }
    }

    return 1;
}

=item _CurPrintableDimGet()

Get current Printable Dimension

   Return
       $CurPrintableDim{Top}
       $CurPrintableDim{Right}
       $CurPrintableDim{Bottom}
       $CurPrintableDim{Left}
       $CurPrintableDim{Width}
       $CurPrintableDim{Height}

   %CurPrintableDim = $PDFObject->_CurPrintableDimGet();

=cut

sub _CurPrintableDimGet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    my %Data;
    if ( $Self->{Current}->{PageWidth} && $Self->{Current}->{PageHeight} ) {
        $Data{Top}    = $Self->{Current}->{PrintableTop};
        $Data{Right}  = $Self->{Current}->{PrintableRight};
        $Data{Bottom} = $Self->{Current}->{PrintableBottom};
        $Data{Left}   = $Self->{Current}->{PrintableLeft};
        $Data{Width}  = $Self->{Current}->{PrintableWidth};
        $Data{Height} = $Self->{Current}->{PrintableHeight};
    }

    return %Data;
}

=item _CurPrintableDimCheck()

Check given X an/or Y if inside the printable dimension

   $True = $PDFObject->_CurPrintableDimCheck(
       X => 200,  # (optional)
       Y => 100,  # (optional)
   );

=cut

sub _CurPrintableDimCheck {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    my $Return    = 0;
    my %Printable = $Self->_CurPrintableDimGet();

    if ( defined( $Param{X} ) ) {
        if (
            $Param{X} >= $Printable{Left}
            && $Param{X} <= ( $Printable{Left} + $Printable{Width} )
        ) {
            $Return = 1;
        }
    }

    if ( defined( $Param{Y} ) ) {
        if (
            $Param{Y} >= $Printable{Bottom}
            && $Param{Y} <= ( $Printable{Bottom} + $Printable{Height} )
        ) {
            $Return = 1;
        }
    }

    return $Return;
}

=item _CurContentDimSet()

Set current Content Dimension

   $True = $PDFObject->_CurContentDimSet(
       Top    => 20,  # (optional)
       Right  => 20,  # (optional)
       Bottom => 20,  # (optional)
       Left   => 20,  # (optional)
   );

=cut

sub _CurContentDimSet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    if ( $Self->{Current}->{PageWidth} && $Self->{Current}->{PageHeight} ) {
        my $NewValue;

        # set CurContentTop
        if (
            defined( $Param{Top} )
            && $Param{Top} >= $Self->{Current}->{PrintableTop}
            && $Param{Top} < $Self->{Current}->{PageHeight} / 2
        ) {
            $Self->{Current}->{ContentTop} = $Param{Top};
            $NewValue = 1;
        }

        # set CurContentRight
        if (
            defined( $Param{Right} )
            && $Param{Right} >= $Self->{Current}->{PrintableRight}
            && $Param{Right} < $Self->{Current}->{PageWidth} / 2
        ) {
            $Self->{Current}->{ContentRight} = $Param{Right};
            $NewValue = 1;
        }

        # set CurContentBottom
        if (
            defined( $Param{Bottom} )
            && $Param{Bottom} >= $Self->{Current}->{PrintableBottom}
            && $Param{Bottom} < $Self->{Current}->{PageHeight} / 2
        ) {
            $Self->{Current}->{ContentBottom} = $Param{Bottom};
            $NewValue = 1;
        }

        # set CurContentLeft
        if (
            defined( $Param{Left} )
            && $Param{Left} >= $Self->{Current}->{PrintableLeft}
            && $Param{Left} < $Self->{Current}->{PageWidth} / 2
        ) {
            $Self->{Current}->{ContentLeft} = $Param{Left};
            $NewValue = 1;
        }

        if ($NewValue) {

            # calculate new content width and height
            $Self->{Current}->{ContentWidth} = $Self->{Current}->{PageWidth}
                - $Self->{Current}->{ContentLeft}
                - $Self->{Current}->{ContentRight};
            $Self->{Current}->{ContentHeight} = $Self->{Current}->{PageHeight}
                - $Self->{Current}->{ContentTop}
                - $Self->{Current}->{ContentBottom};

            # set new current position
            $Self->{Current}->{PositionX} = $Self->{Current}->{ContentLeft};
            $Self->{Current}->{PositionY} = $Self->{Current}->{PageHeight} - $Self->{Current}->{ContentTop};
        }
    }

    return 1;
}

=item _CurContentDimGet()

Get current Content Dimension

   Return
       $CurContentDim{Top}
       $CurContentDim{Right}
       $CurContentDim{Bottom}
       $CurContentDim{Left}
       $CurContentDim{Width}
       $CurContentDim{Height}

   %CurContentDim = $PDFObject->_CurContentDimGet();

=cut

sub _CurContentDimGet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    my %Data;
    if ( $Self->{Current}->{PageWidth} && $Self->{Current}->{PageHeight} ) {
        $Data{Top}    = $Self->{Current}->{ContentTop};
        $Data{Right}  = $Self->{Current}->{ContentRight};
        $Data{Bottom} = $Self->{Current}->{ContentBottom};
        $Data{Left}   = $Self->{Current}->{ContentLeft};
        $Data{Width}  = $Self->{Current}->{ContentWidth};
        $Data{Height} = $Self->{Current}->{ContentHeight};
    }

    return %Data;
}

=item _CurContentDimCheck()

Check given X an/or Y if inside the content dimension

   $True = $PDFObject->_CurContentDimCheck(
       X => 200,  # (optional)
       Y => 100,  # (optional)
   );

=cut

sub _CurContentDimCheck {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    my $Return  = 0;
    my %Content = $Self->_CurContentDimGet();

    if ( defined( $Param{X} ) ) {
        if ( $Param{X} >= $Content{Left} && $Param{X} <= ( $Content{Left} + $Content{Width} ) ) {
            $Return = 1;
        }
    }

    if ( defined( $Param{Y} ) ) {
        if ( $Param{Y} >= $Content{Bottom} && $Param{Y} <= ( $Content{Bottom} + $Content{Height} ) ) {
            $Return = 1;
        }
    }

    return $Return;
}

=item _CurPositionSet()

Set current Position

   $True = $PDFObject->_CurPositionSet(
       X => 20,  # (optional)
       Y => 20,  # (optional)
   );

=cut

sub _CurPositionSet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    if ( $Self->{Current}->{PageWidth} && $Self->{Current}->{PageHeight} ) {
        if ( $Self->DimGet() eq 'printable' ) {
            if (
                defined( $Param{X} )
                && $Param{X} >= $Self->{Current}->{PrintableLeft}
                && $Param{X} <= $Self->{Current}->{PageWidth} - $Self->{Current}->{PrintableRight}
            ) {
                $Self->{Current}->{PositionX} = $Param{X};
            }
            if (
                defined( $Param{Y} )
                && $Param{Y} <= $Self->{Current}->{PageHeight} - $Self->{Current}->{PrintableTop}
                && $Param{Y} >= $Self->{Current}->{PrintableBottom}
            ) {
                $Self->{Current}->{PositionY} = $Param{Y};
            }
        }
        else {
            if (
                defined( $Param{X} )
                && $Param{X} >= $Self->{Current}->{ContentLeft}
                && $Param{X} <= $Self->{Current}->{PageWidth} - $Self->{Current}->{ContentRight}
            ) {
                $Self->{Current}->{PositionX} = $Param{X};
            }
            if (
                defined( $Param{Y} )
                && $Param{Y} <= $Self->{Current}->{PageHeight} - $Self->{Current}->{ContentTop}
                && $Param{Y} >= $Self->{Current}->{ContentBottom}
            ) {
                $Self->{Current}->{PositionY} = $Param{Y};
            }
        }
    }

    return 1;
}

=item _CurPositionGet()

Get current Position

   Return
       $CurPosition{X}
       $CurPosition{Y}

   %CurPosition = $PDFObject->_CurPositionGet();

=cut

sub _CurPositionGet {
    my ( $Self, %Param ) = @_;

    if ( !$Self->{PDF} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a PDF Document!"
        );
        return;
    }
    if ( !$Self->{Page} ) {
        $Kernel::OM->Get('Kernel::System::Log')->Log(
            Priority => 'error',
            Message  => "Need a Page!"
        );
        return;
    }

    my %Data;
    if ( $Self->{Current}->{PageWidth} && $Self->{Current}->{PageHeight} ) {
        $Data{X} = $Self->{Current}->{PositionX};
        $Data{Y} = $Self->{Current}->{PositionY};
    }

    return %Data;
}

sub DESTROY {
    my ( $Self, %Param ) = @_;

    # set cache
    $Kernel::OM->Get('Kernel::System::Cache')->Set(
        Type  => 'PDF',
        TTL   => 60 * 60 * 24 * 20,
        Key   => 'CacheStringWidth',
        Value => $Self->{CacheStringWidth},
    );

    return 1;
}

1;

=end Internal:

=back

=head1 TERMS AND CONDITIONS

This software is part of the KIX project
(L<https://www.kixdesk.com/>).

This software comes with ABSOLUTELY NO WARRANTY. For details, see the enclosed file
LICENSE for license information (AGPL). If you did not receive this file, see

<https://www.gnu.org/licenses/agpl.txt>.

=cut
