Artikel

PDF-generering i Drupal

Emil Johnsson
Emil Johnsson27 september 2013

Att generera PDF-dokument i Drupal kan låta som ett komplext fenomen men med rätt verktyg är det faktiskt ganska enkelt. Emil Johnsson guidar dig från start till slut.

Placeholder

När vi byggde Garantera Ebeco fanns det ett behov av att maila en PDF-version utav garantibeviset till slutkunden. Detta var en ny utmaning för vårt produktionsteam som vi var ivriga att ta oss an. I den här reflektionen visar jag hur vi gick tillväga för att generera en PDF i Drupal, men det går enkelt att anpassa koden till valfritt PHP-ramverk.

Val av bibliotek

Det finns en uppsjö av olika bibliotek för att generera PDF-filer som alla skulle ha klarat av uppgiften galant. När vi valde vilket bibliotek vi skulle använda oss utav ville vi ha något som är enkelt att implementera och ger ett bra resultat. Vi landade i wkhtmltopdf som kortfattat tar emot en HTML-struktur och skapar en PDF-fil.

Det fina med wkhtmltopdf är att den använder QtWebKit som är en portning av WebKit (en renderingsmotor som bland annat används av Safari). Detta gör att vi enkelt kan få ett snyggt resultat med hjälp av tekniker vi redan är experter på: HTML och CSS. Det verkar dock som att QtWebKit ligger ett par versioner bakom WebKit, till exempel saknade vi stöd för ett par CSS3-features.

Implementation

Exekveringsrättigheterna kan man sätta till 755, se denna chmod-tutorial för mer information.

Innan man sätter igång bör man se till så att man har biblioteket installerat på servern. Det kan egentligen ligga var som helst så länge man vet sökvägen. För att hålla oss till god Drupal-praxis använde vi oss därmed av Libraries-modulen och placerade således biblioteket i 'sites/all/libraries/wkhtmltopdf/'. Här är det viktigt att man ser till så att wkthmltopdf har exekveringsrättigheter på servern.

Förbered HTML

Vi börjar med att bygga upp en HTML-struktur. I vårt fall genererade vi HTML via en tema-funktion och data från noder, men för att förenkla exemplet tar vi här bara en vanlig HTML-sträng.

Nedan har vi en vanlig page-callback i Drupal, men det skulle lika gärna kunna vara en funktion som kallas någon annanstans ifrån. Vi wrappar vår HTML-sträng med annan nödvändig HTML (html-, head- och bodytagg) och skickar in den till vår wkthmltopdf-funktion.


function example_pdf_generate_pdf() {
  // Sätt upp en HTML-struktur
  $html = '<h1>Min första PDF</h1><p>Det här är en exempel-PDF</p>';

  // Wrappa HTML med nödvändig information
  $html = example_pdf_format_html($html);

  // Skicka in slutgiltig HTML till vår implementation av wkhtlmtopdf
  $pdf = example_pdf_wkhtmltopdf($html, 'exempel.pdf');
}

Nu har vi en färdig HTML-struktur. Dags att titta på det roliga, generering av PDF-filen.

Kör wkhtmltopdf

wkhtmltopdf tar två argument, en källa och ett mål. Källan innehåller vår HTML och målet är sökvägen till den slutgiltiga PDF-filen. För att den skall få rätt format skickar vi även med ett par inställningar, så som pappersstorlek och orientering. Alla inställningar finns tillgängliga i dokumentationen för wkhtmltopdf.

Om allt har gått rätt till kommer vi att ha en färdig PDF-fil som vi returnerar. Skulle det inte fungera är det vanligaste felet brist på rättigheter, som vi gick igenom tidigare.


function example_pdf_wkhtmltopdf($html) {
  // Definiera inställningar för wkhtmltopdf
  $wkthmltopdf_path = libraries_get_path('wkhtmltopdf') . '/wkhtmltopdf-amd64';
  $paper_size = 'A4';
  $page_orientation = 'portrait';
  $dpi = 96;

  // Definiera sökvägar till temporära filer
  $tmp_path = variable_get('file_temporary_path', '/tmp');
  $tmp_html = $tmp_path . '/exempel.html';
  $tmp_pdf = $tmp_path . '/exempel.pdf';

  // Försök att skriva till tmp-filen och logga om det inte lyckas
  $file_contents = file_put_contents($tmp_html, $html);
  if ($file_contents === FALSE) {
    watchdog('example_pdf', 'Kunde inte skriva till tmp-fil.');
  }

  // Bygg upp kommandot
  $cmd = realpath($wkthmltopdf_path) . " --page-size $paper_size --orientation $page_orientation --dpi $dpi $tmp_html $tmp_pdf";
  exec($cmd);

  if (file_exists($tmp_pdf)) {
    $pdf = file_get_contents($tmp_pdf);
  }
  else {
    watchdog('example_pdf', 'PDF kunde inte genereras');
  }

  if (!empty($pdf)) {
    return $pdf;
  }
  else {
    return FALSE;
  }
}

Nedladdning av PDF

Härifrån kan vi välja att göra lite vad vi vill med PDF-filen vi genererat. I Garantera Ebecos fall skickade vi den till slutanvändaren via e-post, men det användarfallet är i sig värt en egen reflektion. Istället visar jag hur man kan låta den laddas ner direkt av användaren.

Nedladdningen gör vi genom att utöka vår tidigare page-callback. Vi kollar först så att den faktiskt returnerades av vår wkhtmltopdf-funktion. Efter det skickar vi ett par headers som säger åt webbläsaren att det är en PDF-fil vi returnerar och sedan printar vi ut filens innehåll. Och vips, användaren får en PDF-fil för nedladdning!


function example_pdf_generate_pdf() {
  // Sätt upp en HTML-struktur
  $html = '<h1>Min första PDF</h1><p>Det här är en exempel-PDF.</p>';

  // Wrappa HTML med nödvändig information
  $html = example_pdf_format_html($html);

  // Skicka in slutgiltig HTML till vår implementation av wkhtlmtopdf
  $pdf = example_pdf_wkhtmltopdf($html);

  if ($pdf) {
    if (headers_sent()) {
      // Headers har redan skickats
      exit('Kan inte streama PDF: headers har redan skickats');
    }

    // Definiera headers
    header('Cache-Control: private');
    header('Content-Type: application/pdf');
    header('Content-Disposition: attachment; filename="exempel.pdf"');

    // Mata ut filen och flusha output buffer
    print $pdf;
    flush();
    exit;
  }
  else {
    return 'Fel: Kunde inte generera PDF';
  }
}

Drupalmodul

Som en liten bonus har jag lagt upp hela exemplet som en Drupalmodul på GitHub.

Prenumerera på vårt nyhetsbrev

I vårt nyhetsbrev delar vi med oss av vår vardag som kretsar kring skräddarsydda webblösningar och vårt medarbetardrivna arbetssätt.

Vill du veta mer om hur vi behandlar personuppgifter?