Sonntag, 26. Juli 2015

Python: Tabellen in ReportLab

ReportLab ist die wohl leistungsfähigste Bibliothek für Python, wenn es um das Erzeugen von PDF-Dateien geht. Von High-Level Zugriffen via Platypus (=Page Layout and Typography Using Scripts) bis zu Low-Level mit direktem Zugriff auf den Canvas bietet ReportLab alles.

Tabellen unterstützt Platypus mit Hilfe der Table-Klasse (welche ein Flowable darstellt) direkt und die Verwendung ist auch erst Mal nicht weiter schwierig.

Es soll ein einfaches PDF-Dokument mit Hilfe von ReportLab gebaut werden. Das Dokument besteht aus einem Absatz Text, einer Tabelle und einem Absatz Text nach der Tabelle.

Der Code dafür sieht im einfachsten Fall so aus:

from reportlab.platypus import Paragraph, Table, SimpleDocTemplate
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import A4


my_data = [['foo', 'bar', 'spam'],
           ['egg', 123, 'python'],
           ['a bit longer', 666, '9B']]
          
styles = getSampleStyleSheet()
doc = SimpleDocTemplate('table_test.pdf', pagesize=A4)
story = []
story.append(Paragraph('Text vor Tabelle', styles['Normal']))
story.append(Table(my_data))
story.append(Paragraph('Text nach Tabelle', styles['Normal']))
doc.build(story)


my_data enthält die Daten für die Tabelle, der Rest ist weitestgehend selbsterklärend.

Nur das das Dokument nicht gerade "hübsch". Das Ergebnis ist im folgenden Screenshot zu sehen:

Standardmäßig werden für die Tabelle keine Rahmen gezeichnet und die Tabelle ist horizontal auf der Seite zentriert. Außerdem ist der Text sehr nah an der Tabelle.

Das ist im folgenden Code korrigiert:

from reportlab.platypus import Paragraph, Table, TableStyle, SimpleDocTemplate
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors

my_data = [['foo', 'bar', 'spam'],
           ['egg', 123, 'python'],
           ['a bit longer', 666, '9B']]
          
styles = getSampleStyleSheet()
doc = SimpleDocTemplate('table_test.pdf', pagesize=A4)
story = []
story.append(Paragraph('Text vor Tabelle', styles['Normal']))
t = Table(my_data)
t.hAlign = 'LEFT'
t.spaceBefore =  10
t.spaceAfter = 10
t.setStyle(TableStyle(

    [('BOX', (0,0), (-1,-1), 0.5, colors.black),
     ('INNERGRID', (0,0), (-1,-1), 0.5, colors.black)]))
story.append(t)
story.append(Paragraph('Text nach Tabelle', styles['Normal']))
doc.build(story)


Das ganze liefert als Ergebnis folgendes:
Über das Attribute hAlign wird die Tabelle die Ausrichtung der Tabelle bestimmt, der Wert LEFT richtet sie linksbündig aus. Mit den Attributen spaceBefore und SpaceAfter wird zusätzlicher Leerraum (hier: 10 px) eingefügt.

Die Tabelle an sich wird über die setStyle-Methode formatiert, welche als Wert die TableStyle-Klasse übergeben bekommt. Diese erlaubt einen detaillierten Eingriff in die Formatierung der Tabelle.

Die Werte für TableStyle werden als Liste von Tupeln übergeben. Im obigen Beispiel wird mit BOX ein Rahmen um die Tabelle gezeichnet und INNERGRID zeichnet den inneren Rahmen, beide in schwarz.
Die Tupel (0, 0), (-1, -1) definieren den Geltungsbereich der Formatierung. (0,0) ist dabei die Zelle links oben, (-1, -1) die Zelle rechts unten - somit ist also die ganze Tabelle abgedeckt.

Möchte man z.B. das innere Gitter mur für einen Teil der Zellen zeichnen und in der mittleren Zelle soll die Schrift rot sein, so ist die Codezeile wie folgt anzupassen:

t.setStyle(TableStyle(
    [('BOX', (0,0), (-1,-1), 0.5, colors.black
     ('INNERGRID', (0,0), (1,1), 0.5, colors.black),
     ('TEXTCOLOR', (1,1), (1,1), colors.red)]))

Und das Ergebnis sieht dann so aus:


TableStyle bietet eine Vielzahl weiterer Möglichkeiten, dass Aussehen der Tabelle zu beeinflussen. Eine komplette Übersicht ist im Kapitel 7 der ReportLab Dokumentation zu finden.

So ist es z.B. auch möglich, Zellen innerhalb einer Spalte oder Zeile zusammenzufassen. Möchte man z.B. die drei Zellen der ersten Zeile zusammenfassen, so ist der obiger Code wie folgt zu ändern:

...
my_data = [['Überschrift', '', ''],
           ['egg', 123, 'python'],
           ['a bit longer', 666, '9B']]

....
t.setStyle(TableStyle(
    [('BOX', (0,0), (-1,-1), 0.5, colors.black),
     ('INNERGRID', (0,0), (-1,-1), 0.5, colors.black),
     ('SPAN', (0, 0), (-1, 0))]))


Das sieht im Ergebnis so aus:

Die bisher generierten Tabellen ermitteln die Breite der Spalten und Höhe der Zeilen automatisch, aber auch das kann natürlich beeinflusst werden. Möchte man z.B. eine fixe Spaltenbreite  von 100 px haben, so ist im Code die Zeile wie folgt zu ändern:

t = Table(my_data, colWidths=100)

Weitere Möglichkeit sind ebenfalls im Kapitel 7 der Dokumentation zu finden.

In den obigen Beispielen wird der Table-Klasse einfach eine Liste von Strings bzw. Integern übergeben. Man kann aber auch eine Liste von Flowables übergeben, was sich anbieten, wenn man z.B. längeren Text oder Grafiken in der Tabelle unterbringen möchte.

Des Weiteren gibt es noch die Klasse LongTable, welche anstellen von Table verwendet werden kann. Das Ergebnis ist laut Dokumentation das gleiche, allerdings soll LongTable bei sehr langen Tabelle etwas performanter sein.

Wie hier gezeigt wird, ist das Generieren einer Tabelle in einem mit Platypus gebauten PDF-Dokuments in ReportLab nicht weiter schwierig. Wie bei ReportLab üblich kann man auf Wunsch aber auch sehr detailliert in die Gestaltung und das Layout der Tabelle eingreifen.

Alle Beispiele aus diesem Blogbeitrag sind unter Python 3.4 und ReportLab 3.0 erstellt.

Sonntag, 19. Juli 2015

Grafikfehler auf Lenovo T450 und die passende Abhilfe

Seit ein paar Monaten nutze ich beruflich ein Lenovo T450 Thinkpad in der Varianten mit Intel Grafik (und ohne zusätzlich Nvidia Grafik).

Ubuntu 14.04 läuft darunter auch ohne Probleme, nur einen kleinen "Schönheitsfehler" gab es: nach dem Login war manchmal die Beschriftung der Symbole / Dateien auf dem Desktop unvollständig. Unvollständig heißt, dass einzelne oder ganz selten auch alle Buchstaben fehlten. Das kam bei ca. 50% der Logins vor. "Meine" Abhilfe war: abmelden, neu anmelden und alles war ok - zumindest in 95% der Fälle. Selten musst ich mich auch nochmal abmelden und einloggen, damit alles war, wie es sein sollte.

Auch mit dem oben beschriebenen Grafikfehler war das System uneingeschränkt benutzbar. Das Fehlen der Buchstaben trat z.B. auch im Menü der Systemeinstellung und in der Tab-Beschriftung des Firefox auf. Der Inhalt der Programmfenster an sich (z.B. die vom Firefox dargestellte Seite, die Liste der Lieder in Quod Libet etc.) war aber immer korrekt und fehlerlos.

Der Grafikfehler war zwar etwas nervig, aber die "Lösung" "Ausloggen, Einloggen" war so schnell und funktionierend, dass ich nie weiter für den Grund des Problems gesucht habe.

Anfang Juli hatte ich dann im Planet von ubuntuusers.de einen Beitrag des Intux-Blogs gelesen, in dem genau das gleiche Problem und eine passende Lösung für ein Thinkpad E550 beschrieben wurde.

Und das ist auch genau die Lösung, die beim Thinkpad T450 mit Intel GraKa funktioniert. Das ganze geht unter Ubuntu 14.04 in vier Schritten:
  • die Verzeichnis /etc/X11/xorg.conf.d/ anlegen
  • darin die Datei 20-intel.conf anlegen
  • die Datei mit folgendem Inhalt befüllen
    Section "Device"
       Identifier  "Intel Graphics"
       Driver      "intel"
       Option      "AccelMethod"  "uxa"
    EndSection
  • abmelden und wieder anmelden
Zumindest bei mir sind seitdem die oben beschrieben Grafikfehler nicht mehr aufgetreten.

An diese Stelle nochmal Danke an den Intux-Blog und zum Abschluss nochmal der volle Link auf den Blogartikel: http://www.intux.de/2015/07/das-thinkpad-e550-und-die-grafik/