Um Kuverts für normale Postbriefe zu erstellen, braucht man weder ein Office-Paket noch Latex. Auch mit Perls Postscript-Modulen, einer Adressdatenbank und dem im Folgenden vorgestellten Skript lassen sich massenweise Umschläge beschriften.
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 }
|
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.
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.
Handarbeit statt Modul-Power
Zeile 23 interpretiert alle mit »#« (und wahlweise führendem Whitespace) anfangenden Zeilen als Kommentare. Das ist praktisch, falls man nur selektierte Einträge drucken will. Denn dann ist es ein Leichtes, alle anderen schnell mit »#« auszukommentieren. Der »split«-Befehl in Zeile 24 bricht die Zeilen an trennenden Kommas auf, »map« entfernt anschließend alle doppelten Anführungszeichen. Da die darauf folgende Ersetzung »s/”//g;« jedoch nicht den Ergebnisstring zurückgibt, wird einfach »$_;« nachgeschaltet.
Ab Zeile 27 entsteht das »PostScript: :File«-Objekt, das mit »landscape« ins Querformat rotiert, mit »reencode => ‘ISOLatin1Encoding’« auch Umlaute unterstützt und das amerikanische Geschäftsbriefformat »Envelope-DL« wählt. Wer also zufälligerweise nicht in den USA wohnt und deshalb andere Umschläge verwendet, kann das Skript leicht anpassen. Beispielsweise misst ein DIN-A-6-Umschlag 10,47 mal 14,81 Zentimeter, es ist dafür also einfach Folgendes einzusetzen:
my $ps = new PostScript::File( landscape => 1, reencode => 'ISOLatin1Encoding', width => cm(10.47), height => cm(14.81), );
Zeile 36 legt die Adressfeldspalten in den Variablen »$last«, »$first«, »$city« und »$str« ab. Zeile 39 ruft die weiter unten definierte Funktion »textbox()« auf, die einen mehrzeiligen String, einen Fontnamen, eine Fontgröße und einen Zeilenabstand in Postscript-Points entgegennimmt. Als Font dient »Helvetica-iso«, da »Helvetica« standardmäßig vorhanden ist und mit dem »-iso«-Zusatz auch Umlaute führt. »textbox()« liefert drei Rückgabewerte: Ein »PostScript: :TextBlock«-Objekt sowie die Breite und Höhe des erzeugten Textblocks in Postscript-Points.
Anschließend ruft Zeile 41 die »Write()«-Methode des »PostScript::TextBlock«-Objekts auf, um den Postscript-Code zu erzeugen. »Write()« nimmt vier Parameter entgegen: die Breite und Höhe des Textblocks sowie den x- und y-Offset der Startkoordinate. Breite und Höhe stammen von der zuvor verwendeten »textbox()«-Funktion.
Offset-Rechnerei
Als x-Offset (Abstand vom linken Rand) kommen 2 Zentimeter dran, die einfach als »cm(2)« notieren, denn die weiter unten definierte Funktion »cm()« rechnet die Zentimeter in Postscript- Points um. Der y-Offset ist schon komplizierter, denn »Write()« erwartet ihn als Abstand vom unteren Rand, während wir 2 Zentimeter vom oberen Rand wollen. Aber kein Problem: Die Methode »$ps->get_width()« des vorher definierten »PostScript::File«-Objekts liefert die Höhe des Kuverts und davon zieht Zeile 42 einfach »cm(2)« ab.
Zu beachten ist, dass »PostScript::File« trotz »landscape«-Modus und damit um 90 Grad gedrehter Seite die ursprüngliche Idee von Breite und Höhe beibehält: Für unsere Zwecke liefert »get_width()« die Höhe und »get_height()« die Breite. »Write« liefert eine Liste zurück, deren erstes Element der Postscript-Code des Textblocks ist. Zeile 43 jubelt ihn der aktuellen Postscript-Seite unter.
Ähnliches geschieht mit dem Empfänger: Zeile 46 fügt Vor- und Nachnamen, Straße und Wohnort zu einem mehrzeiligen String zusammen. Die »textbox()«-Funktion nimmt jedoch dieses Mal einen etwas größeren Font und Zeilenabstand. Der x-Offset der linken oberen Ecke der Textbox vom Postscript-Ursprung ist diesmal die Länge des Kuverts (»$ps ->get_height()«) minus der Breite des Textkastens (»$bw«) minus 2 Zentimeter (»cm(2)«). Der y-Offset, also der Abstand der oberen Ecke der Textbox vom unteren Briefrand, ist jetzt die Höhe des Textkastens plus 2 Zentimeter (»$bh + cm(2)«).
Kurzzeitiges Vergnügen
Die Funktion »tempfile()« aus dem Modul »File::Temp« legt in Zeile 34 eine temporäre Datei mit einer ».ps«-Endung an und gibt ein beschreibbares File-Handle sowie den Dateinamen zurück. Die in Zeile 56 aufgerufene »output()«-Methode soll die Postscript-Daten in dieser Datei ablegen, erwartet aber deren Namen ohne ».ps«-Endung, deswegen putzt Zeile 55 diese schnell weg und schreibt das Ergebnis in »$base«. Nach dem Aufruf des Druckerkommandos in Zeile 59 muss Zeile 63 nur noch die temporäre Datei löschen.
»textbox()« ab Zeile 68 erzeugt ein neues »PostScript::TextBlock«-Objekt und ruft dessen »addText«-Methode auf. Übergeben werden der Fontname, dessen Größe, der Zeilenabstand (»$leading«) und der zu platzierende Text. Um die Größe des erzeugten Textkastens zu bestimmen, ruft es die weiter unten definierten Funktionen »tb_width()« und »tb_height()« (»tb« für Text Block) auf. Während »tb_height« lediglich den Zeilenabstand mit der Anzahl der im Text übergebenen Zeilen multiplizieren muss, gestaltet sich der horizontale Platzverbrauch eines Strings in einem Proportionalfont etwas schwieriger, denn die Buchstaben sind auf dem Papier unterschiedlich breit.
Fontmetriken – einfach ausgetrickst
Zum Glück gibt es das Modul »PostScript::Metrics« mit seiner Funktion »stringwidth()«, die das mittels intern gespeicherter Fonttabellen erledigt. Sie kennt jedoch »Helvetica-iso« nicht, also entfernt Zeile 96 den »-iso«-Zusatz. Dann aber funktionieren keine Umlaute mehr, weshalb Zeile 101 diese einfach durch schlichte Platzhalter in Form des Buchstabens »A« ersetzt – das stimmt zwar nicht genau, erfüllt aber den Zweck. Die längste Zeile bestimmt die Breite des Textblocks.
Zugegeben, diesmal musste ich mit ziemlich vielen Haken und Ösen programmieren, um die eigenwillige Implementierung der »PostScript::*«-Module zu überlisten, aber den Preis zahle ich gerne für mehr Freiheit mit Linux und das Vermeiden unnötiger Windows-Bootvorgänge. (uwo)
Infos |
|
[1] Listings: [ftp://www.linux-magazin.de/pub/listings/magazin/2003/11/Perl] [2] Hilfe bei der Druckerinstallation unter Linux: [http://www.linuxprinting.org] [3] Shawn Wallace, “Perl Graphics Programming”: O’Reilly, 2002 [4] Mehr zu Postscript: [http://www.mathematik.uni-ulm.de/help/pstut/] |
Der Autor |
|
Michael Schilli arbeitet als Web-Engineer für AOL/Netscape in Mountain View, Kalifornien. Er hat “Goto Perl 5” (deutsch) und “Perl Power” (englisch) für Addison-Wesley geschrieben und ist unter [mschilli@perlmeister.com] zu erreichen. Seine Homepage ist [http://perlmeister.com]. |









