Tutorial – PDF-Rechnung mit PHP erzeugen

Ich habe nun endlich das zweite Tutorial zu PDF mit PHP erzeugen gemacht.

Diesmal gehts um eine PDF-Rechnung.

zum Tutorial PDF-Rechnung mit PHP erzeugen

Wie immer die Fragen, Anregungen usw. zum Tutorial gleich hier als Kommentar posten ;-)

Viel Spaß!

29 Kommentare

  1. 1. Bert

    Kommentar vom 19. Mai 2009 um 15:46

    Hallo.
    Das Thema “PDF erzeugen” ist genau das was ich brauchen kann.
    Das Beispiel aus dem 1. Tutorial läuft top.
    Beim 2. Beispiel “Rechnung” jedoch bekomme ich nur den pdf-text im Explorer angezeigt, aber nicht als PDF interpretiert.
    Wo muss ich vielleicht noch was drehen?
    Ein Hinweis oder die Beantwortung meiner Frage wäre hilfreich.
    Danke.

  2. 2. Thomas

    Kommentar vom 19. Mai 2009 um 16:35

    Falls das Problem nur bei deinem Internet Explorer auftritt, könnte es eventuell an folgendem liegen (ich poste mal den Original-Text):

    Diese “wirren” Zeichen sind der tatsächliche Inhalt des PDF Dokuments der durch den Acrobat Reader interpretiert und dargestellt wird. Dieses Verhalten ist ein Bug im Internet Explorer. Wenn der Internet Explorer unter ein und derselben URL zuerst ein HTML Dokument und danach ein PDF Dokument erhält, wird dieses direkt, ohne den Acrobat Reader zu starten ausgegeben. Dieses Verhalten tritt meistens während der Entwicklung eines Script auf: Das Script hat einen Fehler und eine Fehlermeldung des Parseres wird als HTML an den Browser gesendet. Nachdem der Fehler behoben wurde und das Script ausgeführt wird, sendet dieses ein PDF Dokument und der obige Sachverhalt tritt ein. Um dieses Problem zu lösen kann ein Neustart des Internet Explorers vorgenommen werden, oder vor dem nächsten Aufruf eine andere URL geöffnet werden. Um dieses Verhalten zu umgehen, sollte das PDF Dokument während der Entwicklung als Datei erzeugt werden die dann geöffnet werden kann.

    Gib mal Bescheid, ob es daran lag.

  3. 3. Bert

    Kommentar vom 20. Mai 2009 um 09:35

    Danke Thomas.
    Während meiner Tests hat es nach ein paar Versuchen verblüffenderweise plötzlich auch funktioniert. Im weiteren Verlauf bin ich dann auch auf die Erklärung zu dem IE-Bug gestossen und habs damit bestätigt gesehen und soweit abgehakt.

    Nochmals Danke für Deine schnelle Antwort.
    Werde jetzt FPDF in meine Anwendung integrieren.

  4. 4. inspiron

    Kommentar vom 13. September 2009 um 16:18

    Hallo,
    ich möchte ein eigenes Logo einfügen, bekomme aber folgende Fehlermeldung:
    FPDF error: Alpha channel not supported: fpdf/klasse/FWP.png
    Was kann ich da machen?

    Gruß

  5. 5. Thomas

    Kommentar vom 13. September 2009 um 18:18

    FPDF unterstützt von Haus aus kein bei png-Bildern keinen Alpha-Channel.
    Eine Lösung dieses Problems wird hier vorgestellt, ich selbst hab das aber nicht probiert, sieht auch recht kompliziert aus.
    Am Einfachsten ist es, wenn Du das Bild als gif speicherst. Damit sollte FPDF keine Probleme haben.

  6. 6. inspiron

    Kommentar vom 1. November 2009 um 19:07

    Hallo,
    ich möchte den Ausschnitt “twShowRechnungspositionen()”mit Daten aus einer Datenbank füllen.
    Soweit funktioniert es, nur werden alle augelesenen Daten übereinander “// Tabellenzeile (mit MultiCell)” geschrieben. (alles in einer ZeileY “$this->SetXY(18, 45);”
    Leider habe ich es mit Schleifen nicht so drauf. Wie muß die Schleife für Y aussehen damit es passt?
    Gruß


    private function ShowPositionen() {

    // Spaltenbreiten und Beschriftung der Spalten & Zeilenköpfe festlegen
    $this->twSetSpaltenbreiten(array(8, 35, 40, 35, 18));
    $this->twSetSpaltenkoepfe(array('Pos', 'Name', 'Beschreibung', 'Uhrzeit', 'Dauer in Min' ));

    // Tabellenköpfe (nur mit Cell)
    $this->SetFillColor(244);
    $this->SetTextColor(000);
    $this->SetLineWidth(.3);
    $this->SetFont('Arial', 'B', '10');
    $this->SetXY(18, 38);
    for ($i=0; $itwArrSpaltenkoepfe); $i++) {
    $this->Cell($this->twArrSpaltenbreiten[$i], 7, $this->twArrSpaltenkoepfe[$i], 1, 0, 'C', 1);
    }
    $this->ln();

    // Datenbankzugriff
    .
    .
    .
    while($row = mysql_fetch_array($result))
    {

    // Tabellenzeile (mit MultiCell)
    $this->SetFillColor(224, 235, 255);
    $this->SetFont('Arial', '', 8);
    $this->SetXY(18, 45);
    $i = 0;
    foreach ($this->twArrRechnungspositionen as $pos) {
    $i++;
    $this->twShowZeileMitMultiCell(array(
    $i,
    $row['NAME'],
    $row['BESCHREIBUNG'],
    $row['UHRZEIT'],
    $row['DAUER'],
    ));

    // Box für Details & Beschreibungen
    $this->SetFillColor(244);
    $this->SetTextColor(000);

    $this->SetFont('Arial','B','9');
    $this->SetXY(18, 50);
    $this->MultiCell(43, 15, 'Details', 1, 'L'); // (breite, höhe.....)
    $this->SetFont('Arial','','8');
    $this->SetXY(61, 50);
    $this->MultiCell(138, 3, $this->Beschreibung['details'], 1, 'J');
    .
    .
    .

  7. 7. inspiron

    Kommentar vom 2. November 2009 um 11:39

    ..ich habe es so geändert

    //$this->SetXY(18, 45);
    $this->SetXY(18, $this->GetY());
    …das ist o.k.

    Nun wird aber die 2. MultiCell in der nächsten Zeile positioniert, ich möchte diese aber nebeneinander haben. Was muss ich da ändern?

    Gruß


    $this->SetFont('Arial','B','9');
    //$this->SetXY(18, 50);
    $this->SetXY(18, $this->GetY());
    $this->MultiCell(43, 15, 'Details', 1, 'L'); // (breite, hˆhe.....)
    $this->SetFont('Arial','','8');
    //$this->SetXY(61, 50);
    $this->SetXY(61, $this->GetY());
    $this->MultiCell(138, 15, $this->twArrBeschreibung['details'], 1, 'J');

  8. 8. Thomas

    Kommentar vom 2. November 2009 um 12:29

    Du musst mit SetXY die Position rechts von der Multicell direkt angeben.
    Bsp:
    $this->MultiCell(43, 15, ‘Details’, 1, ‘L’);
    //die Position (rechts von der MultiCell) setzen
    $x = $this->GetX() + 43;
    $y = $this->GetY();
    $this->SetXY($x, $y);

    Habs jetzt nich ausprobiert, aber so müsste es gehen.

  9. 9. inspiron

    Kommentar vom 2. November 2009 um 13:03

    …danke,
    aber sie wird immer noch in der nächsten Zeile angezeigt.:-(


    $this->SetFont('Arial','B','9');
    $this->SetXY(18, $this->GetY());
    $this->MultiCell(43, 15, 'Details', 1, 'L'); // (breite, hˆhe.....)
    $this->SetFont('Arial','','8');
    $x = $this->GetX() + 51; //die Position (rechts von der MultiCell) setzen
    $y = $this->GetY();
    $this->SetXY($x, $y);
    $this->MultiCell(173, 15, $this->twArrBeschreibung['details'], 1, 'J');

  10. 10. Thomas

    Kommentar vom 2. November 2009 um 13:24

    Kann es sein, dass deine zweite Multicell ansich nicht mehr in der Breite reinpasst mit 173 und deshalb eine neue Zeile losgeht?

  11. 11. inspiron

    Kommentar vom 2. November 2009 um 13:30

    …das hatte ich auch erst gedacht,
    habe die Zellenbreite auf 50 geändert, es bleibt so.
    Die horizontale Anordnung passt, nur am Ende der 1. Zelle beginnt in der 2. Zeile die 2. MultiCell

    Gruß

  12. 12. Thomas

    Kommentar vom 2. November 2009 um 13:33

    Ist eventuell irgendwo noch ein
    $this->Ln($h);
    versteckt ??

  13. 13. inspiron

    Kommentar vom 2. November 2009 um 13:47

    …anbei die komplette Position.
    private function twShowRechnungspositionen() {

    // Spaltenbreiten und Beschriftung der Spalten & Zeilenkˆpfe festlegen
    $this->twSetSpaltenbreiten(array(8, 35, 40, 35));
    $this->twSetSpaltenkoepfe(array('Pos', 'Name', 'Uhrzeit', 'Dauer in Min'));

    // Tabellenkˆpfe (nur mit Cell)
    $this->SetFillColor(244);
    $this->SetTextColor(000);
    $this->SetLineWidth(.3);
    $this->SetFont('Arial', 'B', '10');
    $this->SetXY(18, 38);
    for ($i=0; $itwArrSpaltenkoepfe); $i++) {
    $this->Cell($this->twArrSpaltenbreiten[$i], 7, $this->twArrSpaltenkoepfe[$i], 1, 0, 'C', 1);
    }
    $this->ln();

    // Datenbankzugriff
    require("admin/includes/config.php");

    $kategorie = "PP2.2"; //testabfrage

    // Verbindung oeffnen und Datenbank ausweahlen
    $conID = mysql_connect( $db_host, $db_user, $db_pass ) or die( "Die Datenbank konnte nicht erreicht werden!" );
    if ($conID)
    {
    mysql_select_db( $db_name, $conID );
    }
    // Anfrage zusammenstellen um die Datensaetze auszulesen
    $result=mysql_query("SELECT * FROM tabelle WHERE (`kategorie` LIKE '".addslashes($kategorie)."')ORDER BY `ID` DESC");

    while($row = mysql_fetch_array($result))
    {

    // Tabellenzeilen (mit MultiCell)
    $this->SetFillColor(224, 235, 255);
    $this->SetFont('Arial', '', 8);
    //$this->SetXY(18, 45);
    $this->SetXY(18, $this->GetY());
    $i = 0;
    foreach ($this->twArrRechnungspositionen as $pos) {
    $i++;
    $this->twShowZeileMitMultiCell(array(
    $i,
    $row['NAME'],
    $row['UHRZEIT'],
    $row['DAUER'],
    ));

    // Box f¸r Details & Beschreibungen
    $this->SetFillColor(244);
    $this->SetTextColor(000);

    $this->SetFont('Arial','B','9');
    $this->SetXY(18, $this->GetY());
    $this->MultiCell(43, 15, 'Details', 1, 'L'); // (breite, hˆhe.....)
    $this->SetFont('Arial','','8');
    $x = $this->GetX() + 51; //die Position (rechts von der MultiCell) setzen
    $y = $this->GetY();
    $this->SetXY($x, $y);
    $this->MultiCell(50, 15, $this->twArrBeschreibung['details'], 1, 'J');

    $this->SetFont('Arial','B','9');
    //$this->SetXY(18, 95);
    $this->SetXY(26, $this->GetY());
    $this->MultiCell(75, 6, 'Bemerkungen', 1, 'L'); // (breite, hˆhe.....)
    $this->SetFont('Arial','','8');
    //$this->SetXY(61, 95);
    $this->SetXY(26, $this->GetY());
    $this->MultiCell(173, 15, $this->twArrBeschreibung['bemerkungen'], 1, 'J');
    $this->SetFillColor(210);
    $this->SetTextColor(000, 000, 102);
    $this->ln(5);
    $this->SetX(18); // sonst gehts immer ganz links los...
    }
    $this->Cell(array_sum($this->twArrSpaltenbreiten), 0, '', 'T'); //Tabellenlinie unten

    }
    }
    /**
    * Wird bei mehreren Seiten nur auf der letzten Seite angezeigtzeigt.

  14. 14. Thomas

    Kommentar vom 2. November 2009 um 14:01

    Du musst in der Schleife hinter:

    $row['DAUER'],

    folgende Zeile einfügen (siehe Tutorial)

    $this->SetX(27); // sonst gehts immer ganz links los…

    (und mach das Komma weg bei $row['DAUER'],)

  15. 15. inspiron

    Kommentar vom 2. November 2009 um 15:31

    …danke nochmal für die Hilfe!
    Der Versatz ist immer noch. :-(

    $row['NAME'],
    $row['UHRZEIT'],
    $row['DAUER'],
    ));
    $this->SetX(27);

    // Box f¸r Details & Beschreibungen
    $this->SetFillColor(244);
    $this->SetTextColor(000);

  16. 16. Thomas

    Kommentar vom 2. November 2009 um 15:41

    foreach ($this->twArrRechnungspositionen as $pos) {
    $i++;
    $this->twShowZeileMitMultiCell(array(
    $i,
    $pos['text'],
    sprintf(“%9.2f”, $pos['menge']),
    sprintf(“%9.2f”, $pos['einzelpreis']),
    sprintf(“%9.2f”, $pos['gesamtpreis'])
    ));
    $this->SetX(27); // sonst gehts immer ganz links los…
    }

  17. 17. inspiron

    Kommentar vom 2. November 2009 um 19:14

    …ich habe es jetzt so gelöst.
    Der 2.MultiCell habe ich den Wert -15 verpasst
    $y = $this->GetY()-15; und somit ist diese wieder neben der 1. MultiCell. :-)

    2.Frage:
    Leider finde ich nichts, wie kann ich den Zeilenabstand in einer MultiCell einstellen.

    $this->SetFont('Arial','','6');
    $x = $this->GetX() + 8; //die Position (rechts von der MultiCell) setzen
    $y = $this->GetY();
    $this->SetXY($x, $y);
    $this->MultiCell(35, 15, 'Details', 1, 'L');
    $this->SetX(18);

    $this->SetFont('Arial','','6');
    $x = $this->GetX() + 43; //die Position (rechts von der MultiCell) setzen
    $y = $this->GetY()-15;
    $this->SetXY($x, $y);
    $this->MultiCell(138, 15, $this->twArrBeschreibung['details'], 1, 'J');
    $this->SetX(18);

  18. 18. Thomas

    Kommentar vom 2. November 2009 um 19:29

    Nur ma zum “Fehlerausschluss”, mal die aus der Datenbank ausgelesenen Werte mit einfachen Strings ersetzen. Nicht dass irgendwelche Zeilenumbrüche in den aus der DB ausgelesenen Werten enthalten sind.

  19. 19. inspiron

    Kommentar vom 23. November 2009 um 16:19

    Hallo,
    ich habe da wieder ein Problem auf der Seite TwPdfRechnung.php.

    In den beiden folgenden Zellen werden Datum und Kategorie angezeigt. soweit i.O.


    $this->Cell(20, 5, $this->twArrRechnungsdaten['datum'], 1, 1, '');
    $this->Cell(30, 5, $this->twArrRechnungsdaten['kategorie'], 1, 1, '');

    Weiter unten werden Daten aus einer Datenbank abgerufen.
    Als Such-Variable soll der Wert von “twArrRechnungsdaten['kategorie'],..datum” verwendet werden.
    Leider habe ich da kein Erfolg, was kann da falsch sein?
    Wenn ich die Werte “//$DATUM = “2009-10-17″;
    //$KATEGORIE = “PP2.2″;” fest zuordne, dann funktioniert es!

    Gruß


    $DATUM = . $this->twArrRechnungsdaten['datum'];
    $KATEGORIE = . $this->twArrRechnungsdaten['kategorie'];

    //$DATUM = "2009-10-17";
    //$KATEGORIE = "PP2.2";

    // Anfrage zusammenstellen um die Datensaetze auszulesen
    $result=mysql_query("SELECT * FROM tabelle WHERE (`DATUM` LIKE '".addslashes($DATUM)."' AND `KATEGORIE` LIKE '".addslashes($KATEGORIE)."')ORDER BY `ID` DESC");
    while($row = mysql_fetch_array($result))
    .
    .
    .

  20. 20. Thomas

    Kommentar vom 23. November 2009 um 16:28

    Kontrolliere mal das Datum, was du aus der Datenbank ausliest, beswtimmt ist da irgendein Fehler drin, Leerzeichen oder was weiß ich. Meist liegts daran.
    Übrigens:
    Wie ist die letzte Sache mit den Zeilenumbrüchen ausgegangen. Hatte es am Ende geklappt?

  21. 21. inspiron

    Kommentar vom 23. November 2009 um 16:59

    Mit
    $DATUM = . $this->twArrRechnungsdaten['datum'];
    $KATEGORIE = . $this->twArrRechnungsdaten['kategorie'];

    bekomme ich diese nicht ausgelesen.
    Ich kann sie nur auslesen wenn ich die Variablen fest eingebe

    $DATUM = "2009-10-17";
    $KATEGORIE = "PP2.2";

    Mit der letzten Sache bin ich noch nicht weiter, konnte erst letzte Woche weiter machen.

  22. 22. inspiron

    Kommentar vom 24. November 2009 um 13:19

    …gelöst!
    Es lag am falschen Datumsformat.

    Gruß

  23. 23. Jürgen Schulze

    Kommentar vom 17. Dezember 2009 um 11:19

    Hallo,
    vielen Dank für dieses vorzügliche Skript.
    Ich werde es für meine Zwecke anpassen.
    Danke
    Jürgen Schulze

  24. 24. Olli

    Kommentar vom 19. Januar 2010 um 08:18

    Hallo Thomas,
    Vielen Dank erstmal für die tollen Erklärungen und das Beispiel zum Rechnungsdruck. Ich habe es nun angepasst und bestücke die Rechnung mit meinen Daten aus der DB. Nun möchte ich aber gerne nicht nur eine Rechnung, sondern mehrere Rechnungen auf einmal ausdrucken. Ich habe hierzu die Sessiondaten in ein Array gelegt und in TwPdfRechnung.php den Konstruktor angepasst, so daß dort alle Rechnungen in einer Schleife laufen sollen:

    public function __construct() {

    // Konstruktor der vererbenden Klasse (FPDF) aufrufen
    parent::__construct(‘P’, ‘mm’, ‘A4′); // L=Querformat(Landscape), P=Hochformat(Portrait)

    // Session-Variablen aus dem aufrufenden Skript übernehmen
    $this->twArrRechnungsdatenGes = $_SESSION['twArrRechnungsdaten'];
    $this->twArrRechnungspositionenGes = $_SESSION['twArrRechnungspositionen'];
    for ($x=0; $xtwArrRechnungspositionenGes); $x++)
    {
    $this->twArrRechnungsdaten = $this->twArrRechnungsdatenGes['$x'];
    $this->twArrRechnungspositionen = $this->twArrRechnungspositionenGes['$x'];

    // Einstellungen für das PDF
    $this->SetDisplayMode( 100 ); // wie groß wird Seite angezeigt(in %)
    $this->SetAutoPageBreak(true, 43); // 43mm von unten erfolgt ein Seitenumbruch hierhier
    $this->AliasNbPages(); // Anzahl der Seiten berechnen ({nb}-sache)

    // Seite erzeugen
    $this->AddPage(); // PDF starten (ruft auch Header() und Footer() auf

    // zusätzliche Sachen
    $this->twShowRechnungspositionen(); // Tabelle mit allen Rechnungspositionen
    $this->twShowLetzteSeite(); // nur auf der letzten Seite
    } // ($x=0; $x<sizeof($theArray); $x++)
    }

    Leider wird aber immer nur die 1. Rechnung, welche übergeben wird erzeugt und keine weitere. Da habe ich irgeneinen blöden Fehler drinnen, den ich gerade nicht finde.

  25. 25. Thomas

    Kommentar vom 19. Januar 2010 um 09:44

    Upps, das würd ich nicht in der Klasse machen, das da mehrere Dateien generiert werden.
    Wenn du dein Array aus der Datenbank sowieso schon fertig geladen hast ehe du die pdf-Klasse aufrufst, dann würd ich die Schleife auch dort schon platzieren und dann sozusagen die pdf-Datei in der Schleife jedesmal neu erstellen und auf den Server schreiben. Musste dir aber für die Dateinamen irgendeinen Zähler mit einbauen, dass diese nicht jedesmal wieder überschrieben wird.

  26. 26. Olli

    Kommentar vom 19. Januar 2010 um 09:55

    Daran habe ich auch schon gedacht. Dann muß ich aber wieder jede einzele Rechnung mit PDF öffnen und ausdrucken. Ich würde aber gerne z.B. 30 verschiedene Rechnungen auf einmal erzeugen und dann mit einem Drucken diese alle auf einmal auf den Drucker bringen.

  27. 27. Thomas

    Kommentar vom 19. Januar 2010 um 10:01

    Dann müsstest du alles in eine Datei bringen, das gibt aber Probleme mit Header, Footer usw.
    Da sehe ich eigentlich keine Lösung…

    Du wirst das schon über eigenständige Dateien bzw. PDF’s lösen müssen :(

  28. 28. Max Power

    Kommentar vom 17. Mai 2011 um 14:51

    Hallo
    ich habe eine Frage.
    In dem Beispiel werden die Positionen in der Rechnung immer vollständig auf einer Seite angezeigt. Wenn jedoch ein Artikelposition einen sehr langen Text über mehr als eine Seite hat, so funktioniert der Umbruch nicht korrekt und die Tabellenrahmen gehen über den vordefinierten Bereich raus bzw. fehlen teilweise.
    Ist es zu realisieren, dass auch Umbrüche in dem Artikeltext umgebrochen werden können?

  29. 29. Stefan

    Kommentar vom 11. Mai 2012 um 19:51

    Hallo,

    ich habe eine Frage zu dem Script, habe nun mein ganze Seite geändert da dieses Script leider kein Rechnungssystem hatte nun habe ich alles angepasst und alles erforderliche eingebaut und so das wenn einer auf sein pdf-icon klickt eine Rechnung mit diesem Script hier erstellt wird.

    Dies klappt auch alles wunder bar, leider erscheint in der Rechnung aber eine Position zuwenig =( ich vermute das es die erste Position ist die fehlt.

    Hier mal die Änderungen an dem Array für die Posten:

    // die Rechnungspositionen
    $arrPos = array();
    $pos = 0;
    while ($row = mysql_fetch_assoc($res))
    {
    $menge = 1;
    $einzelpreis = $row['amt'] – ($row['amt']*0.19);
    $arrPos[$pos] = array(
    ‘text’=>’Gebühren, Auktion-ID: ‘.$row['auc_id'].” vom: “.date(“d.m.Y”, $row['date']),
    ‘menge’ => $menge,
    ‘einzelpreis’ => $einzelpreis,
    ‘gesamtpreis’ => $einzelpreis * $menge,
    );
    $pos++;
    }

    Wäre sehr dankbar für einen Tip

    MFG: Stefan

Einen Kommentar schreiben