Mit Linux gelingt mittlerweile alles: digitale Bilder von der Kamera einlesen, digitale Musik hören, CDs brennen, sogar der USB-Scanner brummt zufrieden mit, wenn er von »xsane« seine Aufträge bekommt. Doch für eine Aufgabe musste ich bislang noch Windows booten: Um einmal im Monat etwa 20 Umschläge für einen Serienbrief mit Absender und Empfängeradressen aus einer Datenbank zu beschriften, nutzte ich bis vor kurzem noch ein Uraltprogramm aus der Windows-Welt. Schluss damit! Mit Ghostscript wird aus dem heimischen 08/15-Drucker schnell ein zwölfzylindriger Postscript-Bolide.
Falls die Distribution nicht schon dessen Einrichtung übernommen hat, zeigt[2] wie's geht. Die bislang unter Windows genutzte Adressdatenbank ließ sich, wie die Abbildung 1 zeigt, problemlos im Komma-separierten CSV-Format exportieren. Was zu tun blieb war, für jeden Briefumschlag eine Postscript-Datei zu generieren und diese dann an den Drucker zu schicken. Kinderleicht mit den beiden Modulen »PostScript::File« und »PostScript::TextBlock« vom CPAN, wie aus[3] zu erfahren. Postscript ist im Prinzip auch nur eine Programmiersprache. Die Postscript-Dateien bestehen aus lesbarem Ascii-Text, der die zur Erzeugung von Seiten notwendigen Kommandos aneinander reiht.
Malen nach Zahlen
Postscript arbeitet allerdings mit einem so genannten mathematischen Koordinatensystem, was für die Layout-Branche eher ungewöhnlich ist. Der Koordinatenursprung ist die linke untere Ecke des Papiers. Die x-Achse führt von dort aus nach rechts, die y-Achse zeigt nach oben, also genau so, wie man es noch aus der Schule gewöhnt ist. Maßeinheit ist der in der amerikanischen Typographie übliche Pica-Punkt oder auch Postscript-Point, ein Zweiundsiebzigstel Inch, das wiederum etwa 2,54 Zentimeter misst. 100 Postscript-Punkte entsprechen also ungefähr 3,5 Zentimetern.
Um zum Beispiel den Text »Max Schuster« ins Adressfeld des Briefumschlags zu platzieren, sind folgende Kommandos notwendig:
0 setgray 401.95 156 moveto
/Helvetica-iso findfont
18 scalefont setfont
(Max Schuster) show
Hierbei geht es zunächst fast 402 Points (etwa 14,2 Zentimeter) von der linken unteren Ecke des Umschlags nach rechts und dann 156 Points (etwa 5,5 Zentimeter) nach oben, um dann dort die angegebene Buchstabenkette im genannten Font (Helvetica-iso) und in der gesetzten Größe 18 von links nach rechts aufs Papier zu setzen.
Etwas vereinfacht wird dieses Kauderwelsch durch die vom CPAN erhältlichen Module »PostScript::File« und »PostScript::TextBlock«. Ersteres übernimmt die Aufgabe, den Postscript-Header, mit
%!PS-Adobe-3.0
beginnend, zu schreiben und sich um die Seitenorientierung, die Randmaße und die Seitenfolge zu kümmern. »PostScript::TextBlock« nimmt mehrzeilige Strings entgegen und fängt bei einer angegebenen Koordinate an zu schreiben. Allerdings verlangen diese Module stundenlanges Herumtüfteln, damit das Layout auch einigermaßen an der richtigen Stelle landet.
Für die Briefumschläge soll folgendes Layout gelten: Der Textblock mit dem Absender soll in beiden Richtungen jeweils zwei Zentimeter von der linken oberen Ecke des Kuverts entfernt beginnen. Der Textblock für das Adressfeld dagegen soll sich in x- wie in y-Richtung bis 2 Zentimeter vor die rechte untere Ecke des Umschlags erstrecken. Wir legen also nicht den Schreibanfang des Adressfelds fest, sondern die Lage der rechten unteren Ecke des Textblocks. Das gewährleistet, dass der Block unabhängig von der variablen Namens- oder Adressenlänge sauber rechts unten im Umschlag hängt.
Das Skript in Listing 1 definiert als Beispiel einen konstanten Absender in »$SENDER« (Zeile 14). Die Adressaten liest es aus einer Adressdatei und gibt zu jeder gefundenen Adresse einen Umschlag nach Abbildung 2 oder 3 auf dem Drucker aus.
Listing 1: »envelope«
|
001 #!/usr/bin/perl
002 ###########################################
003 # envelope - Print paper envelopes
004 # Mike Schilli, 2003 (m@perlmeister.com)
005 ###########################################
006 use warnings;
007 use strict;
008
009 use PostScript::File;
010 use PostScript::TextBlock;
011 use File::Temp qw(tempfile);
012
013 my $ADDR_CSV = "mailaddr.csv";
014 my $SENDER = q{Ansel Absender
015 Amselweg 9
016 D-78333 Ansbach};
017 my $PRINT_CMD = "lpr";
018
019 open FILE, $ADDR_CSV or
020 die "Cannot open $ADDR_CSV";
021
022 while(<FILE>) {
023 next if /^s*#/;
024 my @addr = split /,/, $_;
025 @addr = map { s/"//g; $_; } @addr;
026
027 my $ps = PostScript::File->new(
028 landscape => 1,
029 reencode => 'ISOLatin1Encoding',
030 paper => "Envelope-DL",
031 );
032
033 my ($tmp_fh, $tmp_file) =
034 tempfile(SUFFIX => ".ps");
035
036 my($last, $first, $city, $str) = @addr;
037
038 # Sender
039 my($bw, $bh, $b) = textbox($SENDER,
040 "Helvetica-iso", 10, 12);
041 my ($code) = $b->Write($bw, $bh, cm(2),
042 $ps->get_width() - cm(2));
043 $ps->add_to_page($code);
044
045 # Recipient
046 my $to = "$first $lastn$strnn$cityn";
047 ($bw, $bh, $b) = textbox($to,
048 "Helvetica-iso", 18, 20);
049 ($code) = $b->Write($bw, $bh,
050 $ps->get_height() - $bw - cm(2),
051 $bh + cm(2));
052 $ps->add_to_page($code);
053
054 # Print to temporary file
055 (my $base = $tmp_file) =~ s/.ps$//;
056 $ps->output($base);
057
058 # Send to printer
059 system("$PRINT_CMD $tmp_file") and
060 die "$PRINT_CMD $tmp_file: $!";
061
062 # Delete
063 unlink "$tmp_file" or
064 die "Cannot unlink $tmp_file: $!";
065 }
066
067 ###########################################
068 sub textbox {
069 ###########################################
070 my($text, $font, $size, $leading) = @_;
071
072 my $b = PostScript::TextBlock->new();
073
074 $b->addText(
075 font => $font,
076 text => $text,
077 size => $size,
078 leading => $leading);
079
080 return(tb_width($text, $font, $size),
081 tb_height($text, $leading),
082 $b);
083 }
084
085 ###########################################
086 sub cm {
087 ###########################################
088 return int($_[0]*72/2.54);
089 }
090
091 ###########################################
092 sub tb_width {
093 ###########################################
094 my($text, $font, $size) = @_;
095
096 $font =~ s/-iso//;
097
098 my $max_width = 0;
099
100 for(split /n/, $text) {
101 s/[äÄöÖüÜß]/A/ig;
102 my $w =
103 PostScript::Metrics::stringwidth(
104 $_, $font, $size);
105 $max_width = $w if $w > $max_width;
106 }
107
108 return $max_width;
109 }
110
111 ###########################################
112 sub tb_height {
113 ###########################################
114 my($text, $leading) = @_;
115
116 my $lines = 1;
117 $lines++ for $text =~ /n/g;
118
119 return $lines*$leading;
120 }
|
Abbildung 2: Ob der Absender einen kurzen oder ...
Abbildung 3: ... einen langen Namen hat: Der seitliche Abstand bleibt konstant.
Import aus der Windows-Welt
Zeile 13 definiert in »$ADDR_CSV« den Namen der Adressdatei, die ein Format nach Abbildung 1 aufweisen sollte. Das Kommando, um eine Postscript-Datei durch den Drucker zu jagen, legt »$PRINT_CMD« in Zeile 17 fest. Wer das Skript nur im Trockenlauf ausprobieren möchte ohne gleich Tonnen von Papier zu produzieren, ersetze »"lpr"« einfach durch »"ghostview"«, dann erscheinen die Kuverts auf dem Bildschirm.
Abbildung 1: Die Felder der Komma-separierten Adressdatei.
Der Code in Zeile 19 öffnet die Adressdatei und der »while«-Block ab Zeile 22 iteriert durch die Einträge, die jeweils mittels regulärer Ausdrücke extrahiert werden. Stattdessen wäre auch der Einsatz des CPAN-Moduls »Text::CSV_XS« denkbar, aber da die Adresseinträge im vorliegenden Fall denkbar einfach sind und ohne Komplikationen wie Wörtliche-Rede-Anführungszeichen oder eingebettete Kommas auskommen, könnte das etwas übertrieben sein.