Re: Tk::Canvas to PDF module offer
Petr Pajas <[email protected]>
| Newsgroups | gmane.comp.lang.perl.tk |
|---|---|
| Message-ID | <[email protected]> |
On Tuesday 30 of November 2004 23:09, Slaven Rezic wrote: > Petr Pajas <[email protected]> writes: > > Dne út 30. listopadu 2004 08:31 John napsal(a): > > > Hello > > > > > > How did you hack the module? > > > > Simply:-) It uses PDF::API2 module to create the actual PDF > > content. It creates a new PDF object and a PDF page, then it > > examinines every object on a given canvas one by one and > > translates it to a corresponding PDF-object. It's really only a > > matter of itemCget, some conversions on the object parameters > > and some PDF::API2 calls. > > > > The only harder thing to implement were the arrows (if a > > multi-line is smooth, you have to position the arrow head > > correctly) and shapes. > > The module looks promising. When trying the pdf() method I get > the following error message: > > ## Can't locate object method "hybrid" via package > "PDF::API2::Page" at /tmp/Tk/Canvas/PDF.pm line 271 Hm, works here, but I use PDF::API2 version '$Revisioning: 0.40_01 Mon Jul 14 12:56:43 2003 $' Maybe the new versions miss Hybrid.pm? In my version, it says that Hybrid is subclassed from PDF::API2::Gfx+Text+Content and $page->hybrid simply calls it's constructor. > both with the stable and the development versions of PDF::API2. > Replacing the hybrid() method by gfx() made the module work. Does text work with gfx too? If hybrid was removed from PDF::API2, then Tk::Canvas::PDF should probably create two objects: Text and Gfx and use them separately... Should be simple, but I have no time to play with that. > Lines, circles and rectangles of all shapes seem to work, while > polygons are not handled correctly (it seems that rectangles are > used instead of polygons). Yes, you're right. I never really tested polygons. The attached version FIXES this. I also attach my ugly test script, I sent John yesterday. I added a new smooth polygon to it. Strangely, it seems that PostScript command curveto and PDF command 'c' produce slightly different Bezier curves (the one in PDF with higher curvature), even if given exactly the same coords. As a result, all "smooth" object exported from canvas to PDF look little different from those seen in PostScript. -- Petr
tkpdfcanvastest_greek.pl
(application/x-perl, 2.2 KB)
#!/usr/bin/perl
use lib qw(.);
use Tk;
use Tk::Canvas::PDF;
# perl >= 5.7:
# what encoding will we get by calling itemcget on canvas text items
# and what encoding should we use to pass strings to it
use if ($Tk::VERSION >= 804), encoding => 'iso-8859-7';
if ($Tk::VERSION >= 804) {
$encoding = 'utf-8';
} else {
$encoding = 'iso-8859-7';
}
$pdfFont = '/home/pajas/.fonts/arial.ttf';
$TEXT = $ARGV[1] || "ÁÂÃÄÅÆÇÈÉÊËÌÍÎÏDxÐÑÓÔÕÖרÙÚÛÜÝÞßExàáâãäåæçèéêëìíîïFxðñòóôõö÷øùúûüýþ";
$top = MainWindow->new;
$c=$top->Canvas(-width => 300, -height => 300);
# draw various stuff
$c->createOval(10,10,20,20,-fill => 'white',-outline => 'black');
$c->createOval(-10,290,10,310,-fill => 'red',-outline => 'black');
$c->createPolygon(100,100,150,120,200,100,180,150,200,200,150,180,100,200,120,150,-fill => 'orange',-outline => 'black',-smooth=>1);
$c->createRectangle(98,98,202,202,-outline => 'green');
$c->createRectangle(122,122,178,178,-outline => 'green');
$c->createPolygon(150,120,180,150,150,180,120,150,-outline => 'green',-fill=>undef);
$line=$c->createLine(15,15,50,50,40,90,-arrow => both,-dash => [6,14,2,4,2,4]);
my $x=70;
my $y=30;
foreach (qw(e w n s ne se nw sw center)) {
$c->createText($x,$y,-text => "yQ$_ $TEXT", -anchor => $_);
$c->createOval($x-1,$y-1,$x+1,$y+1,-fill => 'red',-outline => 'red');
$y+=30;
}
$c->pack;
# use detailed API, rotation, scale, translation, media, ...
$b=$top->Button(-text => 'print PDF1',
-command => sub {
my $P = Tk::Canvas::PDF->new(-media => [0,0,300,300],
-encoding => $encoding,
-ttfont => $pdfFont);
$P->new_page();
$P->draw_canvas($c,
-rotate => -75,
-scale => [1,0.5],
-translate => [100,300]
);
$P->finish(-file => "$0.pdf");
print "Created $0.pdf\n";
$top->destroy();
})->pack();
$b2=$top->Button(-text => 'print PDF2',
-command => sub {
$c->pdf(-file => "$0.pdf",
-encoding => $encoding,
-ttfont => $pdfFont);
print "Created $0.pdf\n";
$top->destroy();
})->pack();
$b2=$top->Button(-text => 'print postscript',
-command => sub {
open PS, ">$0.ps";
print PS $c->postscript();
close PS;
})->pack();
MainLoop;
PDF.pm
(application/x-perl-module, 14.8 KB)
#$Id: PDF.pm,v 1.5 2003/12/12 12:01:10 pajas Exp $
# Tk::Canvas to PDF convertor.
# Copyright (c) 2003 by Petr Pajas
#
# This library is free software; you can use, modify, and
# redistribute it under the terms of GPL - The General Public
# Licence. Full text of the GPL can be found at
# http://www.gnu.org/copyleft/gpl.html
#
package Tk::Canvas::PDF;
BEGIN {
use Exporter;
use Tk::rgb;
use strict;
use base qw(Tk::Canvas Exporter);
use vars qw(%media %join %capstyle @EXPORT_OK);
@EXPORT_OK=(qw(%media));
eval "use Encode";
%join = (
bevel => 2,
miter => 0,
round => 1
);
%capstyle = (
butt => 0,
round => 1,
projecting => 2
);
%media = (
Letter => [612, 792],
LetterSmall => [612, 792],
Legal => [612, 1008],
Statement => [396, 612],
Tabloid => [792, 1224],
Ledger => [1224, 792],
Folio => [612, 936],
Quarto => [610, 780],
'7x9' => [504, 648],
'9x11' => [648, 792],
'9x12' => [648, 864],
'10x13' => [720, 936],
'10x14' => [720, 1008],
Executive => [540, 720],
A0 => [2384, 3370],
A1 => [1684, 2384],
A2 => [1191, 1684],
A3 => [842, 1191],
A4 => [595, 842],
A4Small => [595, 842],
A5 => [420, 595],
A6 => [297, 420],
A7 => [210, 297],
A8 => [148, 210],
A9 => [105, 148],
A10 => [73, 105],
B0 => [2920, 4127],
B1 => [2064, 2920],
B2 => [1460, 2064],
B3 => [1032, 1460],
B4 => [729, 1032],
B5 => [516, 729],
B6 => [363, 516],
B7 => [258, 363],
B8 => [181, 258],
B9 => [127, 181],
B10 => [91, 127],
ISOB0 => [2835, 4008],
ISOB1 => [2004, 2835],
ISOB2 => [1417, 2004],
ISOB3 => [1001, 1417],
ISOB4 => [709, 1001],
ISOB5 => [499, 709],
ISOB6 => [354, 499],
ISOB7 => [249, 354],
ISOB8 => [176, 249],
ISOB9 => [125, 176],
ISOB10 => [88, 125],
C0 => [2599, 3676],
C1 => [1837, 2599],
C2 => [1298, 1837],
C3 => [918, 1296],
C4 => [649, 918],
C5 => [459, 649],
C6 => [323, 459],
C7 => [230, 323]
);
}
=item $canvas->pdf(options)
Export cavnas content to PDF. Options:
=over 4
=item -media => media-name
A4,A5,B4,etc. or a bbox array of the form [x1,y1,x2,y2].
Defaults to A4.
=item -ttfont => filename
filename of a TrueType font
=item -psfont => [font-filename, afm-filename]
PostScript font as a pair of filenames of a PostScript font (pfb) and
a PostScript font metrics file (afm)
=item -font => name
PDF corefont filename
=item -file => filename
output filename
=back
=cut
sub __debug {
# print join "",@_; print "\n";
}
sub color2gray {
my ($color)=@_;
unless (ref($color)) {
if (!defined($color)) {
return undef;
} elsif ($color =~ /^#(..)(..)(..)/) {
$color = [map hex,$1,$2,$3];
} elsif (exists($Tk::rgb::rgb{$color})) {
$color = $Tk::rgb::rgb{$color};
} else {
warn "unknown color $color\n";
return 0;
}
}
no integer;
foreach (@$color) {
$_=$_ / 255;
$_=1-(1 - $_ ** 1.5);
$_=$_ * 255;
}
my $gray = int(sqrt($color->[0]**2+$color->[1]**2+$color->[2]**2)/sqrt(3));
return ("#".(sprintf("%02x",$gray) x 3));
}
sub new {
my ($class,%opts)=@_;
require PDF::API2;
my $pdf=PDF::API2->new;
my %fontmap;
my $unicode=$opts{-unicode}; # force unicode if possible
my $encoding=$opts{-encoding} || 'utf8';
$encoding = 'utf8' if $encoding =~ /^\s*unicode\s*$|^\s*utf-?8\s*$/i;
$encoding =~ s/^\s*windows-?/cp/i;
$encoding =~ s/^\s*latin-(\d+)?/latin$1/i;
$encoding =~ s/^\s*iso-?8859(\d+)/iso-8859-$1/i;
my @media;
if ($opts{-media}) {
if (ref($opts{-media})) {
@media=@{$opts{-media}};
} elsif (exists $media{$opts{-media}}) {
@media=(0,0,@{$media{$opts{-media}}});
} else {
die "Unknown media type $opts{-media}";
}
} else {
@media=(0,0,@{$media{A4}});
}
$pdf->mediabox(@media);
__debug("Media: @media, Encoding: $encoding\n");
if ($opts{-fontmap}) {
foreach my $fn (keys %{$opts{-fontmap}}) {
if ($opts{-fontmap}->{$fn}->[0] =~ /tt|truetype/i) {
if ($unicode or $encoding eq 'utf8') {
$fontmap{$fn}=$pdf->ttfont($opts{-fontmap}->{$fn}->[1])->unicode();
} else {
$fontmap{$fn}=$pdf->ttfont($opts{-fontmap}->{$fn}->[1],
-encode => $encoding);
}
} elsif ($opts{-fontmap}->{$fn}->[0] =~ /ps|postscript/i) {
$fontmap{$fn}=$pdf->psfont($opts{-fontmap}->{$fn}->[1],$opts{-fontmap}->{$fn}->[2],
$encoding ne 'utf8' ?
(-encode => $encoding) : ()
);
} elsif ($opts{-fontmap}->{$fn}->[0] =~ /core|builtin/i) {
$fontmap{$fn}=$pdf->corefont($opts{-fontmap}->{$fn}->[1],
$encoding ne 'utf8' ?
(-encode => $encoding) : ()
);
} else {
die "Canvas::PDF->pdf: unknown font type: $opts{-fontmap}->{$fn}";
}
}
}
my $font;
my $fontType;
if ($opts{-ttfont}) {
$fontType='TT';
if ($unicode or $encoding eq 'utf8') {
$font=$pdf->ttfont($opts{-ttfont})->unicode();
} else {
$font=$pdf->ttfont($opts{-ttfont},-encode => $encoding);
}
} elsif ($opts{-psfont}) {
$fontType='PS';
$font=$pdf->psfont($opts{-psfont}->[0],$opts{-psfont}->[1],
$encoding ne 'utf8' ?
(-encode => $encoding) : ()
);
} else {
$fontType='Core';
$font=$pdf->corefont('Helvetica',
$encoding ne 'utf8' ?
(-encode => $encoding) : ()
);
}
return bless {
Debug => $opts{-debug},
Encoding => $encoding,
Unicode => $unicode,
PDF => $pdf,
FontMap => \%fontmap,
Media => \@media,
DefaultFont => $font,
DefaultFontType => $fontType
},$class;
}
sub pdf {
my ($canvas,%opts)=@_;
my $P = __PACKAGE__->new(%opts);
$P->new_page(%opts);
$P->draw_canvas($canvas,%opts);
return $P->finish(%opts);
}
sub new_page {
my ($P,%opts)=@_;
$P->{current_page} = $P->{PDF}->page;
}
sub finish {
my ($P,%opts)=@_;
if ($opts{-file}) {
$P->{PDF}->saveas($opts{-file});
$P->{PDF}->end;
} else {
my $string = $P->{PDF}->stringify;
$P->{PDF}->end;
return $string;
}
}
sub draw_canvas {
my ($P,$canvas,%opts)=@_;
my $draw = $P->{current_page}->hybrid;
if ($opts{-transform}) {
$draw->transform(%{$opts{-transform}});
}
if ($opts{-translate}) {
$draw->translate(@{$opts{-translate}});
}
if ($opts{-rotate}) {
$draw->rotate($opts{-rotate});
}
if ($opts{-scale}) {
$draw->scale(@{$opts{-scale}});
}
if ($opts{-skew}) {
$draw->skew(@{$opts{-skew}});
}
if ($opts{-matrix}) {
$draw->matrix(@{$opts{-matrix}});
}
$draw->linedash();
$draw->linecap(0);
$draw->linejoin(0);
my $x = 0;
my $y = 0;
my $w = $opts{-width} || $P->{Media}[2];
my $h = $opts{-height} || $P->{Media}[3];
my $i;
foreach my $item ($canvas->find('all')) {
$draw->save;
my $type=$canvas->type($item);
my $state = $canvas->itemcget($item, '-state');
next if $state eq 'hidden';
$state = $state eq 'disabled' ? $state : '';
my @coords=$canvas->coords($item);
__debug("$type: orig @coords");
# recalculate coords for bottom/up
my $even=0;
foreach (@coords) {
$_=$h-$_ if $even;
$even=!$even;
}
__debug "$type: new @coords";
if ($type eq 'text') {
$draw->textstart;
my $anchor=$canvas->itemcget($item,'-anchor') || 'center';
my $color=$canvas->itemcget($item,"-${state}fill");
next unless defined($color); # transparent text = no text
$color = color2gray($color) if $opts{-grayscale};
my %canvasfont = $canvas->fontActual($canvas->itemcget($item,"-font"));
__debug "FONT:", (map {" $_ => $canvasfont{$_}, "} keys %canvasfont),"\n";
my $fn;
my $font_lookup_string = lc($canvasfont{-family}." ".$canvasfont{-weight}." ".$canvasfont{-slant});
if ($P->{FontMap}{$font_lookup_string}) {
$fn=$P->{FontMap}{$font_lookup_string};
} else {
warn ("'$font_lookup_string' font isn't mapped\n") if $P->{Debug};
$fn = $P->{DefaultFont};
}
my $fnsize=abs($canvasfont{-size});
my $text=$canvas->itemcget($item,"-text");
my $textwidth=$canvas->itemcget($item,"-width");
# TODO: width
__debug "$anchor\n";
$draw->linewidth(1);
$draw->linedash();
$draw->font($fn,$fnsize);
$draw->fillcolor($color);
my ($posx,$posy)=@coords;
my $ascent=$fn->ascender*$fnsize/1000;
my $descent=-$fn->descender*$fnsize/1000;
my $height=$ascent+$descent;
# my $height = $fn->capheight*$fnsize/1000;
my $width;
if (eval "Encode::is_utf8(\$text)" and not $@) {
$width = $fn->width_utf8($text)*$fnsize;
} elsif ($P->{Unicode}) {
eval "\$text= Encode::decode(\$P->{Encoding},\$text);";
$width = $fn->width_utf8($text)*$fnsize;
} else {
$width = $fn->width($text)*$fnsize;
}
__debug "Width: $width";
$posx-=$width/2;
$posy-=$height/2;
$anchor = '' if $anchor eq 'center';
if ($anchor =~ /s/) { $posy+=$height/2 }
elsif ($anchor =~ /n/) { $posy-=$height/2 }
if ($anchor =~ /e/) { $posx-=$width/2 }
elsif ($anchor =~ /w/) { $posx+=$width/2 }
$draw->translate($posx,$posy+$descent);
__debug "Text: $posx $posy $anchor";
if (eval "Encode::is_utf8(\$text)" and not $@ or $P->{Encoding} eq 'utf8') {
$draw->text($text,-utf8 => 1);
} else {
$draw->text($text);
}
$draw->textend;
} elsif ($type eq 'line') {
my $color=$canvas->itemcget($item,"-${state}fill");
next unless defined $color; # transparent line = no line
$color = color2gray($color) if $opts{-grayscale};
my $join=$canvas->itemcget($item,'-joinstyle');
my $capstyle=$canvas->itemcget($item,'-capstyle');
my $width=$canvas->itemcget($item,'-width');
my @dash=_canvas_to_pdf_dash($width,$canvas->itemcget($item,"-${state}dash"));
@dash=() if @dash<2;
my $smooth = $canvas->itemcget($item,"-smooth");
my $arrow = $canvas->itemcget($item,"-arrow");
my $ars = $canvas->itemcget($item,"-arrowshape") || [8,10,3];
# TODO: dashoffset
$draw->linewidth($width);
$draw->linedash(@dash);
$draw->linejoin($join{$join});
$draw->linecap($capstyle{$capstyle});
$draw->strokecolor($color);
$draw->fillcolor($color);
__debug "Line: @coords";
my @c=@coords;
# shorten line for arrows
for (qw(first last)) {
if ($arrow eq $_ or $arrow eq 'both') {
# we should adjust the enpoints to make space for the arrows
my ($x1,$y1,$x2,$y2) = $_ eq 'first' ? (0..3) : (-2,-1,-4,-3);
my $len = sqrt(($c[$x2]-$c[$x1])**2 + ($c[$y2]-$c[$y1])**2);
$c[$x1]+=($c[$x2]-$c[$x1])*0.9*$ars->[0]/$len;
$c[$y1]+=($c[$y2]-$c[$y1])*0.9*$ars->[0]/$len;
}
}
# draw line
if ($smooth and @c>=6) {
$draw->move(@c[0,1]);
$draw->curve(@c);
} else {
$draw->move(@c[0,1]);
$draw->line(@c[2..$#c]);
}
$draw->stroke;
# draw arrows
for (qw(first last)) {
if ($arrow eq $_ or $arrow eq 'both') {
@c=$_ eq 'first' ? @coords[0..3] : @coords[-2,-1,-4,-3];
my $angle = 180*atan2($c[2]-$c[0],$c[1]-$c[3])/3.14159265;
$draw->save;
$draw->translate(@c[0,1]);
$draw->linedash();
$draw->linecap(0);
$draw->linejoin(0);
$draw->rotate($_ eq 'first' ? -$angle : $angle-90);
$draw->move(0,0);
$draw->line($ars->[1],$ars->[2], $ars->[0],0, $ars->[1],-$ars->[2], 0,0);
$draw->close;
$draw->fillstroke;
$draw->restore;
}
}
} elsif ($type eq 'oval') {
my $width=$canvas->itemcget($item,'-width');
my @dash=_canvas_to_pdf_dash($width,$canvas->itemcget($item,"-${state}dash"));
@dash=() if @dash<2;
my $color=$canvas->itemcget($item,"-${state}fill");
my $outlinecolor=$canvas->itemcget($item,"-${state}outline");
$outlinecolor=$color if !defined($outlinecolor);
if ($opts{-grayscale}) {
$color = color2gray($color);
$outlinecolor = color2gray($outlinecolor);
}
# TODO: dashoffset
$draw->linewidth($width);
$draw->linedash(@dash);
$draw->strokecolor($outlinecolor) if defined $outlinecolor;
$draw->fillcolor($color) if defined $color;
my @c = (($coords[2]+$coords[0])/2,($coords[3]+$coords[1])/2,
($coords[2]-$coords[0])/2,($coords[3]-$coords[1])/2);
__debug "Ellipse: @c";
$draw->ellipse(@c);
if (defined($color)) {
$draw->fillstroke;
} else {
$draw->stroke;
}
} elsif ($type eq 'polygon') {
my $width=$canvas->itemcget($item,'-width');
my $join=$canvas->itemcget($item,'-joinstyle');
my @dash=_canvas_to_pdf_dash($width,$canvas->itemcget($item,"-${state}dash"));
@dash=() if @dash<2;
my $color=$canvas->itemcget($item,"-${state}fill");
my $outlinecolor=$canvas->itemcget($item,"-${state}outline");
$outlinecolor=$color if !defined($outlinecolor);
if ($opts{-grayscale}) {
$color = color2gray($color) if defined $color;
$outlinecolor = color2gray($outlinecolor);
}
my $smooth = $canvas->itemcget($item,"-smooth");
# TODO: dashoffset
$draw->linewidth($width);
$draw->linedash(@dash);
$draw->linejoin($join{$join});
$draw->strokecolor($outlinecolor) if defined($color);
$draw->fillcolor($color) if defined($color);
__debug "Polygon: @c\n";
if ($smooth) {
no integer;
my @c=(@coords,@coords[0..3]);
my $first=1;
while (@c>5) {
my @d = (($c[2]+$c[0])/2,($c[3]+$c[1])/2,$c[2],$c[3],($c[4]+$c[2])/2,($c[5]+$c[3])/2);
$draw->move(@d[0,1]) if $first;
$draw->curve(@d[2,3,2,3,4,5]);
splice @c,0,2;
$first = 0;
}
$draw->close();
if (defined $color) {
$draw->fillstroke(0);
} else {
$draw->stroke;
}
} else {
$draw->poly(@coords,@coords[0,1]);
if (defined $color) {
$draw->fillstroke(0);
} else {
$draw->stroke;
}
}
} elsif ($type eq 'rectangle') {
my $width=$canvas->itemcget($item,'-width');
my @dash=_canvas_to_pdf_dash($width,$canvas->itemcget($item,"-${state}dash"));
@dash=() if @dash<2;
my $color=$canvas->itemcget($item,"-${state}fill");
my $outlinecolor=$canvas->itemcget($item,"-${state}outline");
$outlinecolor=$color if !defined($outlinecolor);
if ($opts{-grayscale}) {
$color = color2gray($color);
$outlinecolor = color2gray($outlinecolor);
}
# TODO: dashoffset
$draw->linewidth($width);
$draw->linedash(@dash);
$draw->linejoin(0);
$draw->linecap(0);
$draw->strokecolor($outlinecolor) if defined($outlinecolor);
$draw->fillcolor($color) if defined($color);
__debug "Rectangle: @coords";
$draw->rectxy(@coords);
if (defined $color) {
$draw->fillstroke;
} else {
$draw->stroke;
}
}
# TODO image, ...
} continue {
$draw->restore;
}
}
sub _canvas_to_pdf_dash {
my ($linewidth,@dash)=@_;
my $dash = join " ",@dash;
my %d=qw(. 40 - 120 , 80 _ 160);
$dash =~ s/(\d+)/$1*$linewidth/ge;
$dash =~ s/[-.,_]( *)/$d{$1}." ".40*(1+length($2))." "/ge;
$dash =~ s/[{}]//;
return split /\s*/,$dash;
}
package Tk::Canvas;
sub pdf {
my $self = shift;
Tk::Canvas::PDF::pdf($self,@_);
}
1;
signature.asc
(application/pgp-signature, 189 B)
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1.2.3 (GNU/Linux) iD8DBQBBrZ+ZQfxdLDi03+IRArG0AJsFipcRYP18e/eEWDdT+CqXV4d3XQCfU6y1 ID0fMdT2/668LKmtG/AbLKU= =p0xo -----END PGP SIGNATURE-----