DHL-Paketetiketten und Briefmarken der Post online zu kaufen, ist eine (mehr oder weniger) praktische Sache. Weniger praktisch ist die "letzte Meile", bis die Frankierung dann auf Paket oder Brief ist: Ausdrucken, ausschneiden, mit Kleber einschmieren oder mit zig Streifen Tesafilm befestigen.

Praktisch, wenn man einen Etikettendrucker zur Hand hat, wie etwa jenen hier.
Zusammen mit 100x200mm-Etiketten für DHL-Paketmarken und 76x51-Etiketten für Briefmarken ist die Frankierung nun ein Kinderspiel, besonders nachdem ich mir zwei virtuelle CUPS-Drucker erstellt habe, die die heruntergeladenen PDF-Etiketten im DIN A4-Format auf die passende Etikettgröße beschneiden.

Das heißt: Paketmarke oder Briefmarke herunterladen, PDF öffnen und auf meinem virtuellen Drucker "DHL" oder "Briefmarke" ausdrucken. Sofern die richtigen Etiketten eingelegt sind, kommt alles passend raus.

Bild 1
Bild 2
Normalerweise kennt man einen am Computer eingerichteten Drucker als etwas, das man im Druckdialog auswählt und...ja, dann kommt ein Ausdruck aus einem Gerät raus.
Mit CUPS lassen sich allerdings auch (virtuelle) Drucker einrichten, die aus dem Druckauftrag nur ein PDF generieren oder den Druckjob manipulieren.

Genau das habe ich hier vor: Man "druckt" die DHL-Paketmarke, welche man im A4-Format herunterlädt (Bild 1) und CUPS schneidet den relevanten Teil, der auf das Etikett gedruckt werden soll, aus und schickt dann nur jenen Teil auf den eigentlichen Drucker.
Raus kommt dann soetwas wie auf Bild 2 zu sehen ist.

Dasselbe funktioniert natürlich auch mit Hermes-Etiketten oder einer online gekauften Briefmarke (wobei man bei der Post darauf achten sollte, dass nur eine Briefmarke auf dem heruntergeladenen PDF ist. Kauft man mehrere, werden mehrere auf dem A4-Blatt platziert).

 

Virtueller Etikettendrucker (nur) für DHL-Etiketten

Unser Drucker namens "etikett" wird durch ein Shell-Skript (ohne Dateiendung .sh) im Verzeichnis /usr/libexec/cups/backend/ definiert (gilt für MacOS X; bei Linux wäre es /usr/lib/cups/backend/).

Achtung: GhostScript muss installiert sein!

Sollte man einen anderen Namen als "etikett" für jenes Backend wählen, dann ist dies im Skript an 3 Stellen zu ändern. Ich habe die Stellen als Kommentar markiert.

/usr/libexec/cups/backend/etikett
#!/bin/bash

PDFBIN=/usr/libexec/cups/pdf/etikett.cups # <-- Bei anderem Namen optimalerweise
                                          #     auch hier anpassen (optional)

# Kein Argument übergeben
if [ $# -eq 0 ]; then
  if [ ! -x "$PDFBIN" ]; then
    exit 0
  fi
  # WICHTIG: Sollte das Backend "etikett" anders benannt werden,
  # sp ist die folgende Zeile entsprechend anzupassen!
  # z.B.  echo "direct blabla \"Unknown\" \"PDF Creator\""
  echo "direct etikett \"Unknown\" \"PDF Creator\""
  exit 0
fi

# case of wrong number of arguments
if [ $# -ne 5 -a $# -ne 6 ]; then
  echo "Usage: pdf job-id user title copies options [file]"
  exit 1
fi

# run ghostscript
if [ $# -eq 6 ]; then
  $PDFBIN $6 >& /dev/null
else
  $PDFBIN - >& /dev/null
fi

exit 0

Diese Datei muss ausführbar sein:

sudo chmod +x /usr/libexec/cups/backend/etikett

Am Rande: Was hat es mit diesen ganzen Variablen $1 bis $6 auf sich?

$1 job-id
$2 Benutzer
$3 Titel
$4 Anzahl Kopien
$5 Optionen
$6 Datei

Und in diesem Skript wird der Druckjob dann letztendlich bearbeitet und an den Drucker gesendet:

/usr/libexec/cups/pdf/etikett.cups
#!/bin/bash

infile=$1

exec /opt/local/bin/gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite \ # Wir nutzen GhostScript...
                       -sOutputFile=- \ # Ausgabe als Stream an nächsten  Befehl (lp)
                       -g5660x2830 \ # Beschneiden auf 100x200mm in Punkt-Einheit und x10
                       -c save pop \
                       -c "<</PageOffset [-10 -490]>> setpagedevice" \ # Ausschnitt -10/-490 Punkt verschoben
                       -f $infile \
                       | lp -d Zebra \              # Ausgabe von gs nun an lp-Befehl übergeben. Eingerichter Drucker heißt "Zebra"
                       -o media=Custom.100x200mm    # Format der Etiketten
                       -o orientation-requested=4 - # Um 90° drehen und Stream als Input (-)

# Zum Rumtesten. Druckjob wird hier nicht ausgedruckt, sondern landet
# als PDF in /tmp/test.pdf - Praktisch, um den passenden Ausschnitt zu finden
#exec /opt/local/bin/gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=/tmp/test.pdf -g2154x1445 -c save pop -c "<</PageOffset [-37 -623]>> setpagedevice" -f $infile
 

Ebenfalls ausführbar machen:

sudo chmod +x /usr/libexec/cups/pdf/etikett.cups

Mit -gBREITExHOEHE wird das A4-Format auf 100x200mm beschnitten (Wert muss allerdings im Punkt-Format und mit 10 multipliziert angegeben werden), mit PageOffset [x y] wird der Ausschnitt um so viele Punkt nach links unten verschoben, dass der relevante Inhalt letztendlich im Druckbereich liegt.

Nun legen wir einen virtuellen Drucker an, der nichts anderes macht, als dieses Skript auszuführen und den geänderten Druckjob dann an unseren eigentlichen Etikettendrucker weitergibt.
Ich gebe ihm den Namen "DHL Etikett".

Die PPD-Datei, die hier angegeben muss, habe ich in diesem Artikel verlinkt.

lpadmin -p dhl_etikett -D "DHL Etikett" -v etikett -E -P /usr/share/cups/model/pdfcolor.ppd.gz

Fehlermeldung lpadmin: Ungültiges Geräte-URI-Schema „etikett“.? Dann stimmt sehr wahrscheinlich etwas nicht mit den Rechten der Datei /usr/libexec/cups/backend/etikett
Prüfen, ob sie root:wheel gehört und ausführbar ist.

Verschiedene Etiketten

Möchte man verschiedene Arten von Etiketten verarbeiten, kann man nun oben aufgeführtes Cups-Backend und Verarbeitungs-Skript duplizieren oder die Auswahl der Etiketten-Art in einem einzigen Cups-Backend integrieren:

/usr/libexec/cups/backend/etikett
#!/bin/bash

PDFBIN=/usr/libexec/cups/pdf/etikett.cups

# Kein Argument übergeben
if [ $# -eq 0 ]; then
  if [ ! -x "$PDFBIN" ]; then
    exit 0
  fi
  # WICHTIG: Hier muss nochmals der verwendete Backend-Name
  # (in diesem Fall "pdf") stehen. Werden weitere Backends angelegt, so
  # ist die folgende Zeile entsprechend anzupassen!
  # z.B.  echo "direct pdfmail \"Unknown\" \"PDF Creator\""
  echo "direct etikett \"Unknown\" \"PDF Creator\""
  exit 0
fi

# case of wrong number of arguments
if [ $# -ne 5 -a $# -ne 6 ]; then
  echo "Usage: pdf job-id user title copies options [file]"
  exit 1
fi

# Art des Etiketts aus der DEVICE_URI auslesen (Option -v bei lpadmin-Befehl)
ETIKETT=${DEVICE_URI#etikett:}

# run ghostscript
if [ $# -eq 6 ]; then
  $PDFBIN $6 $ETIKETT >& /dev/null # <-- Etikett-Typ an das nächste Skript weitergeben
else
  $PDFBIN - $ETIKETT >& /dev/null  # <-- Etikett-Typ an das nächste Skript weitergeben
fi

exit 0

Diese Datei muss ausführbar sein:

sudo chmod +x /usr/libexec/cups/backend/etikett

/usr/libexec/cups/pdf/etikett.cups
#!/bin/bash

infile=$1
etikett=$2 # <-- Hier kommt die Art des Etiketts an

case "$etikett" in
  dhl)
  exec /opt/local/bin/gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=- \
                         -g5660x2830 -c save pop \
                         -c "<</PageOffset [-10 -490]>> setpagedevice" \
                         -f $infile | lp -d Zebra \
                         -o media=Custom.100x200mm \
                         -o orientation-requested=4 - ;;
  briefmarke)
  exec /opt/local/bin/gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=- \
                         -g2154x1445 -c save pop \
                         -c "<</PageOffset [-37 -623]>> setpagedevice" \
                         -f $infile | lp -d Zebra \
                         -o media=Custom.97x51mm - ;; # Breiteres Format, als es sein sollte,
                                                      # sonst zu weit links und abgeschnitten
esac

Ebenfalls ausführbar machen:

sudo chmod +x /usr/libexec/cups/pdf/etikett.cups

Nun kann man zwei Drucker einrichten und gibt mit der -v-Option mit, ob es sich um ein DHL-Etikett oder um eine Briefmarke handelt.
Je nachdem, wird dann im Shell-Skript der Teil dhl) oderbriefmarke) angewendet:

lpadmin -p etikett_dhl  -D "DHL Etikett" -v etikett:dhl -E -P /usr/share/cups/model/pdfcolor.ppd.gz
lpadmin -p etikett_post -D "Briefmarke"  -v etikett:briefmarke -E -P /usr/share/cups/model/pdfcolor.ppd.gz

Kurz zum lpadmin-Befehl, mit dem wir einen neuen Drucker zum System hinzufügen (als ob man ihn im GUI über die Systemeinstellungen hinzufügen würde):

lpadmin -p <interner kurzname> \
        -D <Aussagekräftige Bezeichnung, wie sie im Druckdialog auftaucht> \
        -v <Names des Skriptes in /usr/libexec/cups/backend/>:<Argument> \
        -E \ # Aktiviert den Drucker sofort
        -P <Pfad zur PPD>

Achtung, Sandbox!
Beim Umzug auf einen neuen Mac mit MacOS X 10.13 funktionierte die ganze Sache auf einmal nicht mehr.
Der virtuelle Drucker, der die PDFs beschneidet, funktionierte zwar, aber auf dem richtigen Drucker kam nur eine 0kB-Datei an.
Es stellte sich heraus, dass es sich um ein Sandbox-Problem handelt. Ab MacOS X 10.10 wurde CUPS in eine Sandbox gepackt. Sehr wahrscheinlich hat CUPS somit keinen Zugriff auf den gs-Befehl.

Umgehen kann man die ganze Sache (noch) mit einem kleinen Eingriff in die CUPS-Config /etc/cups/cups-files.conf zu welcher folgende Zeile hinzugefügt werden muss: Sandboxing Relaxed
Danach CUPS neustarten:

sudo launchctl stop org.cups.cupsd
sudo launchctl start org.cups.cupsd

Probleme mit dem CUPS Backend?

Leider sind die Logdateien in /var/log/cups nicht gerade sehr aussagekräftig. Sollte irgendetwas nicht klappen, dann steht man ziemlich auf dem Trockenen.
Man kann mit dem Befehl cupsctl --debug-logging das Log etwas gesprächiger machen. Dies kann mit cupsctl --no-debug-logging wieder ausgeschaltet werden.

So ein Backend hat ab MacOS X 10.10 nur noch Berechtigung auf die Verzeichnisse /usr/libexec/cups/backend und /etc (plus jeweilige Unter-Verzeichnisse). Sollte man auf Daten außerhalb dieser beiden Verzeichnisse zugreifen wollen, wird man kein Erfolg haben.
Somit ist es auch aussichtslos, aus Debug-Zwecken irgendetwas in /tmp schreiben zu wollen.

Zebra Etikettendrucker unter MacOS X installieren

Und wie installiert man den physischen Zebra-Etikettendrucker am Mac?
Keine große Sache und im Folgenden am Beispiel der Netzwerk-Version eines Zebra-Druckers beschrieben:

http://127.0.0.1:631/ im Browser aufrufen.

Seite lässt sich nicht aufrufen? Dann zuerst den Befehl cupsctl WebInterface=yes ins Terminal eingeben und nochmals versuchen.
Diese Seite kann standardmäßig nur lokal am Computer geöffnet werden und nicht über die spezifische IP auf anderen Computern im Netzwerk.

Verwaltung > Drucker hinzufügen
 
AppSocket/HP JetDirect wählen und fortfahren
 
socket://(IP-Adresse):9100 im Verbindungsfeld eingeben
 

Namen eingeben (z.B. Etikettendrucker)

Zebra aus der Hersteller-Liste auswählen und Zebra ZPL Label Printer als Modell

Bei den Standardeinstellungen habe ich noch das größte Format hinterlegt, welches ich verwende, aber wenn man den Drucker nicht direkt anspricht, spielt das keine Rolle, da ich das Format beim lp-Befehl im CUPS-Skript mitgebe.