# App::a2pdf
#
# Copyright (C) 2007 Jon Allen <jj@jonallen.info>
#
# This software is licensed under the terms of the Artistic
# License version 2.0.
#
# For full license details, please read the file 'artistic-2_0.txt'
# included with this distribution, or see
# http://www.perlfoundation.org/legal/licenses/artistic-2_0.html
package App::a2pdf;
use strict;
use warnings;
use PDF::API2;
use Switch 'Perl6';
BEGIN {
}
#-----------------------------------------------------------------------
sub new {
my $invocant = shift;
my $class = ref($invocant) || $invocant;;
# Set default options
my $self = { @_ };
bless $self,$class;
# Define style mapping
# This will relate Perl::Tidy's token types to a printing style
$self->{stylemap} = {
'header' => 'helvetica_bold_10',
'footer' => 'helvetica_bold_10',
'k' => 'black_bold',
'{' => 'black_bold',
'}' => 'black_bold',
'POD' => 'grey_italic',
'POD_START' => 'grey_italic',
'POD_END' => 'grey_italic',
'END_START' => 'grey_italic',
'DATA_START' => 'grey_italic',
'DATA' => 'grey_italic',
'SYSTEM' => 'grey_italic',
'#' => 'grey_italic',
'J' => 'red_italic',
'j' => 'red_italic',
'i' => 'blue',
'->' => 'blue',
'w' => 'green',
'L' => 'brown',
'R' => 'brown',
'Q' => 'purple',
'q' => 'purple',
};
# Define styles
# Supports 3 properties, font (e.g. Helvetica, Courier, Times),
# color (in hex), and type (Bold, Oblique, or BoldOblique)
$self->{stylist} = {
'helvetica9' => {font=>'Helvetica',size=>9},
'helvetica_bold_10' => {font=>'Helvetica',size=>10,type=>'Bold'},
'black_bold' => {color=>'#000000',type=>'Bold'},
'grey_italic' => {color=>'#333333',type=>'Oblique'},
'red_italic' => {color=>'#cc2222',type=>'Oblique'},
'blue' => {color=>'#222288'},
'green' => {color=>'#228822'},
'brown' => {color=>'#666622'},
'purple' => {color=>'#882288'},
};
# Set up first page
$self->{page_number} = 0;
$self->{line_number} = 1;
$self->{line_number_width} = 0;
$self->{line_spacing} = $self->{font_size}+2 unless ($self->{line_spacing});
$self->{x_position} = $self->{left_margin};
$self->{y_position} = $self->{page_height} - $self->{top_margin};
$self->{pdf} = PDF::API2->new;
$self->{pdf}->mediabox($self->{page_width},$self->{page_height});
if ($self->{icon}) {
# Load required modules to handle images
eval "use File::Type;use Image::Size";
unless ($@) {
if (-e $self->{icon}) {
my $type = File::Type->new->checktype_filename($self->{icon});
given ($type) {
when 'image/jpeg' {$self->{icon_img} = $self->{pdf}->image_jpeg($self->{icon})}
when 'image/tiff' {$self->{icon_img} = $self->{pdf}->image_tiff($self->{icon})}
when 'image/gif' {$self->{icon_img} = $self->{pdf}->image_gif($self->{icon})}
when 'image/x-png' {$self->{icon_img} = $self->{pdf}->image_png($self->{icon})}
when 'image/x-pnm' {$self->{icon_img} = $self->{pdf}->image_pnm($self->{icon})}
default {warn "[Warning] Unknown image format '$type' for icon ".$self->{icon}."\n"}
}
if ($self->{icon_img}) {
($self->{icon_width},$self->{icon_height}) = imgsize($self->{icon});
}
} else {
warn("[Warning] Cannot open icon file: ".$self->{icon}."\n");
}
} else {
warn("[Warning] The modules File::Type and Image::Size are required to use icons\n")
}
}
$self->formfeed;
$self->set_style;
return $self;
}
#-----------------------------------------------------------------------
sub print {
my $self = shift;
my $line = shift;
if ($self->{newline_flag}) {
$self->newline;
$self->{newline_flag} = 0;
}
$self->print_line_number if $self->{line_numbers};
$self->print_text_with_style($line);
$self->{newline_flag} = 1;
}
#-----------------------------------------------------------------------
sub write_line { # This is the write_line method called by Perl::Tidy
my $self = shift;
my $line = shift;
my $line_number = $line->{_line_number};
my $line_type = $line->{_line_type};
my $line_text = $line->{_line_text};
chomp $line_text;
if ($self->{newline_flag}) {
$self->newline;
$self->{newline_flag} = 0;
}
$self->print_line_number if $self->{line_numbers};
if ($line_type eq 'CODE') {
$self->print_text_with_style($1) if ($line_text =~ /^(\s+)/);
my @rtoken_list = @{$line->{_rtokens}};
my @rtoken_types = @{$line->{_rtoken_type}};
foreach my $rtoken (@rtoken_list) {
my $rtoken_type = shift @rtoken_types;
$self->print_text_with_style($rtoken,$rtoken_type);
}
} else {
$self->print_text_with_style($line_text,$line_type);
}
$self->{newline_flag} = 1;
}
#-----------------------------------------------------------------------
sub newline {
my $self = shift;
$self->linefeed;
$self->{line_number}++;
$self->{overflow} = 0;
}
#-----------------------------------------------------------------------
sub linefeed {
my $self = shift;
$self->{y_position} -= $self->{line_spacing};
$self->{x_position} = $self->{left_margin} + $self->{line_number_width};
$self->{overflow} = 1;
if ($self->{y_position} < ($self->{bottom_margin} + $self->{footer_height})) {
my $style = $self->{style};
$self->formfeed;
$self->set_style($style);
}
}
#-----------------------------------------------------------------------
sub formfeed {
my $self = shift;
$self->{page} = $self->{pdf}->page;
$self->{x_position} = $self->{left_margin} + $self->{line_number_width};
$self->{page_number}++;
delete $self->{text};
delete $self->{gfx};
$self->{gfx} = $self->{page}->gfx;
$self->{text} = $self->{page}->text;
$self->{y_position} = $self->{page_height} - $self->{top_margin} - $self->{line_spacing};
$self->{header_height} = ($self->{header}) ? $self->generate_header : 0;
$self->{footer_height} = ($self->{footer}) ? $self->generate_footer : 0;
$self->{y_position} -= $self->{header_height};
}
#-----------------------------------------------------------------------
sub generate_header {
my $self = shift;
$self->set_style('header');
my $header_padding = 2;
my $header_spacing = 3;
my $header_height = $self->{text_size} + $header_spacing + $header_padding;
# Draw header icon
if ($self->{icon_img}) {
my $icon_height_in_points = $self->{icon_height} * $self->{icon_scale};
if ($icon_height_in_points > $self->{text_size}) {
$header_height += ($icon_height_in_points - $self->{text_size});
}
my $ypos = $self->{page_height} - $self->{top_margin} - $icon_height_in_points;
$self->{gfx}->image($self->{icon_img},$self->{left_margin},$ypos,$self->{icon_scale});
}
# Add page title
my $x = $self->{page_width} - $self->{right_margin} - $self->{text}->advancewidth($self->{title});
my $y = $self->{page_height} - $self->{top_margin} - $header_height + $header_spacing + $header_padding;
$self->{text}->textlabel($x,$y,$self->{fontcache}->{$self->{font}},$self->{text_size},$self->{title});
# Draw horizontal line
$self->{gfx}->move($self->{left_margin},$self->{page_height}-$self->{top_margin}-$header_height + $header_padding);
$self->{gfx}->line($self->{page_width}-$self->{right_margin},$self->{page_height}-$self->{top_margin}-$header_height + $header_padding);
$self->{gfx}->stroke;
return $header_height;
}
#-----------------------------------------------------------------------
sub generate_footer {
my $self = shift;
$self->set_style('footer');
# Add page footer
my $t = 'Page '.$self->{page_number};
my $x = $self->{page_width} - $self->{right_margin} - $self->{text}->advancewidth($t);
my $y = $self->{bottom_margin};
$self->{text}->textlabel($x,$y,$self->{fontcache}->{$self->{font}},$self->{text_size},$t);
$self->{gfx} = $self->{page}->gfx unless (exists $self->{gfx});
$self->{gfx}->move($self->{left_margin},$self->{bottom_margin}+10);
$self->{gfx}->line($self->{page_width}-$self->{right_margin},$self->{bottom_margin}+10);
$self->{gfx}->stroke;
return 18; # Footer height in points
}
#-----------------------------------------------------------------------
sub output {
my $self = shift;
print $self->{pdf}->stringify;
#$self->{pdf}->end;
}
#-----------------------------------------------------------------------
sub line_number_chars {
my $self = shift;
my $line_number_chars = shift;
$self->{line_number_chars} = $line_number_chars;
$self->{line_number_width} = ($self->{line_numbers}) ? $self->{text}->advancewidth('X' x ($line_number_chars + 2)) : 0;
$self->{line_number_template} = '%'.$line_number_chars.'d: %s';
$self->{x_position} = $self->{left_margin} + $self->{line_number_width};
}
#-----------------------------------------------------------------------
sub print_line_number {
my $self = shift;
$self->set_style;
my $width = $self->{text}->advancewidth($self->{line_number}.':X');
my $x_pos = $self->{left_margin} + $self->{line_number_width} - $width;
$self->{text}->textlabel($x_pos,$self->{y_position},$self->{fontcache}->{$self->{font}},$self->{text_size},$self->{line_number}.':');
}
#--print_text_with_style---------------------------------------------------
sub print_text_with_style {
my $self = shift;
my $text = shift;
$self->set_style(shift);
while ($text =~ /(\f|[^\f]+)/g) {
my $block = $1;
if ($block =~ /\f/ && !exists $self->{noformfeed}) {
$self->formfeed;
$self->{x_position} = $self->{left_margin} + $self->{line_number_width};
} else {
while ($block =~ /(\s+|\S+)/g) {
my $word = $1;
$self->print_word($word);
}
}
}
}
#--print_word--------------------------------------------------------------
#
# Purpose: Adds a single word to the PDF in the current style
#
# Usage: $self->print_word('word');
#
#--------------------------------------------------------------------------
sub print_word {
my $self = shift;
my $word = shift;
my $width = $self->{text}->advancewidth($word);
if ($self->{x_position} + $width > $self->{page_width} - $self->{right_margin}) {
# If the word will not fit on one line, split it up and recurse the 'print_word' sub
if ($width > ($self->{page_width} - $self->{left_margin} - $self->{right_margin})) {
my $fit = int(($self->{page_width} - $self->{x_position} - $self->{right_margin}) / $self->{nspace});
my @words = (substr($word,0,$fit),substr($word,$fit));
$self->print_word($_) foreach @words;
return;
}
$self->linefeed;
if ($word =~ /^\s+$/ &&
$self->{overflow} &&
$self->{x_position} == $self->{left_margin} + $self->{line_number_width}) {
return;
}
}
$self->{x_position} += $self->{text}->textlabel($self->{x_position},
$self->{y_position},
$self->{fontcache}->{$self->{font}},
$self->{text_size},
$word,
-color => $self->{text_color});
if ($self->{x_position} > $self->{page_width} - $self->{right_margin}) {
$self->linefeed;
}
}
#--set_style---------------------------------------------------------------
#
# Purpose: Sets current style (font, size, colour)
#
# Usage: $self->set_style('stylename');
#
#--------------------------------------------------------------------------
sub set_style {
my $self = shift;
my $style = shift || 'default';
$style = (exists $self->{stylemap}->{$style}) ? $self->{stylemap}->{$style} : 'default';
# Create font object if necessary
my $font = ($self->{stylist}->{$style}->{font} || $self->{font_face}) .
((exists $self->{stylist}->{$style}->{type}) ? '-'.$self->{stylist}->{$style}->{type} : '');
unless (exists $self->{fontcache}->{$font}) {
$self->{fontcache}->{$font} = $self->{pdf}->corefont($font);
}
$self->{style} = $style;
$self->{font} = $font;
$self->{text_color} = $self->{stylist}->{$style}->{color} || '#000000';
$self->{text_size} = $self->{stylist}->{$style}->{size} || $self->{font_size};
$self->{text}->font($self->{fontcache}->{$font},$self->{text_size});
$self->{nspace} = $self->{text}->advancewidth('n');
}
#--------------------------------------------------------------------------
sub _MANIFEST {
require File::Type;
require Image::Size;
require PDF::API2::Content;
require PDF::API2::Win32;
require PDF::API2::Lite;
require PDF::API2::UniWrap;
}
1;