Scal pliki PDF z PHP [zamknięte]

83

Moja koncepcja jest taka - na stronie jest 10 plików pdf. Użytkownik może wybrać kilka plików PDF, a następnie wybrać opcję scalania, aby utworzyć pojedynczy plik PDF zawierający wybrane strony. Jak mogę to zrobić za pomocą PHP?

Imrul.H
źródło
Powiązane pytanie (z odpowiedzią btw): stackoverflow.com/questions/2713701/…
Fran Verona
3
@Webnet faktycznie, 64% jest w porządku. Powiedziałbym, że 0-25% = porażka, ale wydaje mi się, że to jest subiektywne
Sean Patrick Floyd
Czy możesz użyć narzędzia wiersza poleceń?
Pekka
Czy możesz używać Zend Framework? stackoverflow.com/questions/4254218/…
Pekka
Gdzie mogę znaleźć plik „pdftk-112-1i386.rpm” i jak go zainstalować na serwerze?
Imrul.H

Odpowiedzi:

28

Robiłem to już wcześniej. Miałem plik PDF, który wygenerowałem za pomocą fpdf i musiałem dodać do niego zmienną liczbę plików PDF.

Miałem już więc obiekt fpdf i skonfigurowaną stronę (http://www.fpdf.org/) i użyłem fpdi do zaimportowania plików (http://www.setasign.de/products/pdf-php-solutions/ fpdi /) FDPI jest dodawane poprzez rozszerzenie klasy PDF:

class PDF extends FPDI
{

} 



    $pdffile = "Filename.pdf";
    $pagecount = $pdf->setSourceFile($pdffile);  
    for($i=0; $i<$pagecount; $i++){
        $pdf->AddPage();  
        $tplidx = $pdf->importPage($i+1, '/MediaBox');
        $pdf->useTemplate($tplidx, 10, 10, 200); 
    }

To zasadniczo sprawia, że ​​każdy plik PDF jest obrazem, który można umieścić w innym pliku PDF. Działał niesamowicie dobrze, do czego go potrzebowałem.

Christa
źródło
Nie rozumiem twojego kodu. czy możesz wyjaśnić więcej szczegółów? Nie znalazłem również funkcji "setSourceFile" i "importPage" w podręczniku fpdf.
Imrul.H
Wróciłem i przyjrzałem się mojemu rozwiązaniu bardziej szczegółowo. Mam nadzieję, że to jest bardziej pomocne. Zupełnie zapomniałem o części fdpi dziś rano, kiedy to napisałem, jest to jedna mała część dość skomplikowanego generatora PDF, który napisałem.
Christa,
7
@Christa Uważaj, FPDI będzie analizować tylko niektóre pliki PDF. Napotykam problem polegający na tym, że FPDI nie analizuje plików PDF powyżej wersji 1.4, a FPDI zmusza mnie do zakupu ich parsera do obsługi> v1.4 ... yar ....
n0nag0n
Nie sądzisz, że lepiej jest zrobić $ i = 0 i $ i <= $ pagecount. Myślę, że lepiej jest czytać. Przy okazji świetny przykład, naprawdę mi pomógł
Nebulosar
126

Poniżej znajduje się polecenie scalania php PDF.

$fileArray= array("name1.pdf","name2.pdf","name3.pdf","name4.pdf");

$datadir = "save_path/";
$outputName = $datadir."merged.pdf";

$cmd = "gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=$outputName ";
//Add each pdf file to the end of the command
foreach($fileArray as $file) {
    $cmd .= $file." ";
}
$result = shell_exec($cmd);

Zapomniałem linku, skąd go znalazłem, ale działa dobrze.

Uwaga: aby to działało, powinieneś mieć zainstalowany gs (na Linuksie i prawdopodobnie na Macu) lub Ghostscript (w Windows).

Sanjeev Chauhan
źródło
4
U mnie zadziałało bez problemów i bez instalowania zewnętrznych bibliotek jak FPDI czy inne.
Memochipan
4
To rozwiązanie działało najlepiej dla mnie. Instalacja Ghostscript na moim serwerze była bardzo łatwa. To było po prostu "yum install ghostscript". A twój scenariusz działał idealnie
Theo Kouzelis
1
Otrzymuję pustą stronę PDF :(
itsazzad
2
Musisz zainstalować Ghostscript, w przeciwnym razie cicho zawiedzie.
Pascal Klein,
2
Powinieneś wyjaśnić, co to właściwie robi. W rzeczywistości nie jest to sposób php do wykonania zadania, w php tylko przygotowujesz dane, a następnie wykonujesz skrypt powłoki, który wykonuje rzeczywiste zadanie. również powinieneś w swojej odpowiedzi zawrzeć, że gs (na Linuksie i prawdopodobnie na Macu) lub Ghostscript (na Windowsie) powinien być zainstalowany, aby to zadziałało .. Mimo to bardzo lubię to rozwiązanie, ponieważ gs jest domyślnie dołączone do Ubuntu , nie musiałem go instalować.
Vulgo Alias
39

Proponuję PDFMerger z github.com , tak prosty jak:

include 'PDFMerger.php';

$pdf = new PDFMerger;

$pdf->addPDF('samplepdfs/one.pdf', '1, 3, 4')
    ->addPDF('samplepdfs/two.pdf', '1-2')
    ->addPDF('samplepdfs/three.pdf', 'all')
    ->merge('file', 'samplepdfs/TEST2.pdf'); // REPLACE 'file' WITH 'browser', 'download', 'string', or 'file' for output options
AgelessEssence
źródło
3
To jest po prostu czyjaś implementacja odpowiedzi @ Christa (FPDF + FDPI), co jest świetne :) Dzięki!
Nahuel
5
Nie działa również z niektórymi typami kompresji niektórych plików PDF.
Theo Kouzelis
3
Używając tego z DOMPDF i działa to cudownie, dziękuję!
Matthew
1
Otrzymuję komunikat „Błąd FPDF: nie można znaleźć tabeli odnośników”. jakieś rozwiązanie?
Sameeraa4ever
1
Działa, ale czasami pokazuje błąd poniżej ... Błąd FPDF: Ten dokument (samplepdfs / four.pdf) prawdopodobnie używa techniki kompresji, która nie jest obsługiwana przez darmowy parser dostarczany z FPDI.
Nikhil,
12
$cmd = "gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -sOutputFile=".$new." ".implode(" ", $files);
shell_exec($cmd);

Uproszczona wersja odpowiedzi Chauhana

Svetoslav Genov
źródło
To działało dobrze dla mnie na serwerze dedykowanym hostgator centos, więc ghostscript musi być już zainstalowany
Mike Volmar
Powinien być również oznaczony jako poprawna odpowiedź, ponieważ było tak cholernie ŁATWE. Godziny zmagania się z nieprawidłowym ładowaniem FPDF i FPDI. Uratowałeś mi życie. Wystarczy zainstalować „imagemagick” (na debian / ubuntu: apt install imagemagick) na serwerze i wszystko jest w porządku. Twoje zdrowie !
Florian Doyen
9

Zarówno przyjęta odpowiedź, jak i strona główna FDPI wydają się zawierać nieudane lub niepełne przykłady. Oto moje, które działa i jest łatwe do wdrożenia. Zgodnie z oczekiwaniami wymaga bibliotek fpdf i fpdi:

require('fpdf.php');
require('fpdi.php');

$files = ['doc1.pdf', 'doc2.pdf', 'doc3.pdf'];

$pdf = new FPDI();

// iterate over array of files and merge
foreach ($files as $file) {
    $pageCount = $pdf->setSourceFile($file);
    for ($i = 0; $i < $pageCount; $i++) {
        $tpl = $pdf->importPage($i + 1, '/MediaBox');
        $pdf->addPage();
        $pdf->useTemplate($tpl);
    }
}

// output the pdf as a file (http://www.fpdf.org/en/doc/output.htm)
$pdf->Output('F','merged.pdf');
billynoah
źródło
Cześć @billynoah Podoba mi się to, ale nie działa w krajobrazie i wydaje się, że łączy tylko pierwsze strony.
Geraldo Isaaks
2
@GeraldoIsaaks - następnie dodałem obsługę dokumentów wielostronicowych we własnej aplikacji. Zaktualizowałem odpowiedź. Nie jestem pewien co do problemów z krajobrazem - nie napotkałem tego.
billynoah
Czego dotyczy ten przykład, który jest dostępny od pierwszych dni FPDI?
Jan Slabon
@Setasign - nigdy tego nie widziałem, ale dzięki za udostępnienie.
billynoah
@billynoah Dzięki za jasny i zaktualizowany prosty przykład kodu tutaj w SO. To mnie zaczęło. Skończyło się na tym, że użyłem więcej kodu z przykładu setasign ( setasign.com/products/fpdi/demos/concatenate-fake , link łatwo przeoczyć w komentarzu powyżej). Ich logika wewnątrz wywołania addPage sprawiła, że ​​moje konkretne połączone strony wyglądały lepiej. Prawdopodobnie lepiej radzi sobie również z portretem / krajobrazem, chociaż tego nie testowałem. Ale nie znalazłem przykładu z wyszukiwaniami i nie wiedziałem, że jestem zainteresowany, dopóki nie zobaczyłem twojej odpowiedzi.
Anne Gunn
5

Miałem podobny problem w swoim oprogramowaniu. Chcieliśmy połączyć kilka plików PDF w jeden plik PDF i przesłać go do usługi zewnętrznej. Używaliśmy rozwiązania FPDI, jak pokazano w rozwiązaniu Christa .

Jednak wejściowe pliki PDF, których używaliśmy, mogą być w wersji wyższej niż 1.7. Zdecydowaliśmy się ocenić komercyjny dodatek FPDI. Okazało się jednak, że niektóre dokumenty zeskanowane przez naszą biurową kserokopiarkę miały zniekształcone indeksy, co spowodowało awarię komercyjnego dodatku FPDI. Więc zdecydowaliśmy się użyć rozwiązania Ghostscript , jak w odpowiedzi Chauhana .

Ale potem otrzymaliśmy dziwne metadane we właściwościach wyjściowego pliku PDF.

Ostatecznie zdecydowaliśmy się połączyć dwa rozwiązania, aby pliki PDF zostały scalone i zdegradowane przez Ghostscript, ale metadane są ustalane przez FPDI. Nie wiemy jeszcze, jak by to działało z niektórymi zaawansowanymi formatowanymi plikami PDF, ale w przypadku skanów, których używamy, działa dobrze. Oto fragment naszej klasy:

class MergedPDF extends \FPDI
{
    private $documentsPaths = array();

    public function Render()
    {
        $outputFileName = tempnam(sys_get_temp_dir(), 'merged');

        // merge files and save resulting file as PDF version 1.4 for FPDI compatibility
        $cmd = "/usr/bin/gs -q -dNOPAUSE -dBATCH -dCompatibilityLevel=1.4 -sDEVICE=pdfwrite -sOutputFile=$outputFileName";
        foreach ($this->getDocumentsPaths() as $pdfpath) {
            $cmd .= " $pdfpath ";
        }
        $result = shell_exec($cmd);
        $this->SetCreator('Your Software Name');
        $this->setPrintHeader(false);
        $numPages = $this->setSourceFile($outputFileName);
        for ($i = 1; $i <= $numPages; $i++) {
            $tplIdx = $this->importPage($i);
            $this->AddPage();
            $this->useTemplate($tplIdx);
        }

        unlink($outputFileName);

        $content = $this->Output(null, 'S');

        return $content;
    }

    public function getDocumentsPaths()
    {
        return $this->documentsPaths;
    }

    public function setDocumentsPaths($documentsPaths)
    {
        $this->documentsPaths = $documentsPaths;
    }

    public function addDocumentPath($documentPath)
    {
        $this->documentsPaths[] = $documentPath;
    }
}

Zastosowanie tej klasy jest następujące:

$pdf = new MergedPDF();
$pdf->setTitle($pdfTitle);
$pdf->addDocumentPath($absolutePath1);
$pdf->addDocumentPath($absolutePath2);
$pdf->addDocumentPath($absolutePath3);
$tempFileName = tempnam(sys_get_temp_dir(), 'merged');
$content = $pdf->Render();
file_put_contents($tempFileName, $content);
Artur Karczmarczyk
źródło
Wystarczy wspomnieć, że użyłem tego samego kodu w środowisku Windows env. i nie zapomnij umieścić folderu programu w „ale nie parametrów.$cmd = "\"C:\\Program Files\\gs\\gs9.20\\bin\\gswin64c.exe\" -q -dNOPAUSE -dBATCH -dCompatibilityLevel=1.4 -sDEVICE=pdfwrite -sOutputFile=[....your parameters...]" ;
Frédéric Klee
3

Próbowałem podobnego problemu i działa dobrze, spróbuj. Może obsługiwać różne orientacje między plikami PDF.

    // array to hold list of PDF files to be merged
    $files = array("a.pdf", "b.pdf", "c.pdf");
    $pageCount = 0;
    // initiate FPDI
    $pdf = new FPDI();

    // iterate through the files
    foreach ($files AS $file) {
        // get the page count
        $pageCount = $pdf->setSourceFile($file);
        // iterate through all pages
        for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) {
            // import a page
            $templateId = $pdf->importPage($pageNo);
            // get the size of the imported page
            $size = $pdf->getTemplateSize($templateId);

            // create a page (landscape or portrait depending on the imported page size)
            if ($size['w'] > $size['h']) {
                $pdf->AddPage('L', array($size['w'], $size['h']));
            } else {
                $pdf->AddPage('P', array($size['w'], $size['h']));
            }

            // use the imported page
            $pdf->useTemplate($templateId);

            $pdf->SetFont('Helvetica');
            $pdf->SetXY(5, 5);
            $pdf->Write(8, 'Generated by FPDI');
        }
    }
Kevin Chui
źródło
To dajeUndefined index: w
senty
upewnij się, że masz poprawnie skonfigurowane FPDF
Kevin Chui
parametry dla mnie to $ size ['width'] i $ size ['height'] zamiast $ size ['w'] i $ size ['h']
gorillagoat
0

Stworzyłem warstwę abstrakcji nad FPDI (może pomieścić inne silniki). Opublikowałem go jako pakiet Symfony2 zależny od biblioteki i jako samą bibliotekę.

Pakiet

Biblioteka

stosowanie:

public function handlePdfChanges(Document $document, array $formRawData)
{
    $oldPath = $document->getUploadRootDir($this->kernel) . $document->getOldPath();
    $newTmpPath = $document->getFile()->getRealPath();

    switch ($formRawData['insertOptions']['insertPosition']) {
        case PdfInsertType::POSITION_BEGINNING:
            // prepend 
            $newPdf = $this->pdfManager->insert($oldPath, $newTmpPath);
            break;
        case PdfInsertType::POSITION_END: 
            // Append
            $newPdf = $this->pdfManager->append($oldPath, $newTmpPath);
            break;
        case PdfInsertType::POSITION_PAGE: 
            // insert at page n: PdfA={p1; p2; p3}, PdfB={pA; pB; pC} 
            // insert(PdfA, PdfB, 2) will render {p1; pA; pB; pC; p2; p3} 
            $newPdf = $this->pdfManager->insert(
                    $oldPath, $newTmpPath, $formRawData['insertOptions']['pageNumber']
                );
            break;
        case PdfInsertType::POSITION_REPLACE: 
            // does nothing. overrides old file.
            return;
            break;
    }
    $pageCount = $newPdf->getPageCount();
    $newPdf->renderFile($mergedPdfPath = "$newTmpPath.merged");
    $document->setFile(new File($mergedPdfPath, true));
    return $pageCount;
}
juanmf
źródło
0

To działało dla mnie w systemie Windows

  1. pobierz PDFtk bezpłatnie z https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/
  2. upuść folder (PDFtk) do katalogu głównego c:
  3. dodaj następujący fragment do swojego kodu php, gdzie $ file1 to lokalizacja i nazwa pierwszego pliku PDF, $ file2 to lokalizacja i nazwa drugiego, a $ newfile to lokalizacja i nazwa pliku docelowego

    $file1 = ' c:\\\www\\\folder1\\\folder2\\\file1.pdf';  
    $file2 = ' c:\\\www\\\folder1\\\folder2\\\file2.pdf';  
    $file3 = ' c:\\\www\\\folder1\\\folder2\\\file3.pdf';   
    
    $command =  'cmd /c C:\\\pdftk\\\bin\\\pdftk.exe '.$file1.$file2.$newfile;
    $result = exec($command);
    
Stewart Kirkpatrick
źródło
Istnieje opakowanie PHP, które czyni to znacznie czystszym. Zobacz github.com/mikehaertl/php-pdftk
Sean the Bean
Uwaga: PdfTK nie działa z RHEL 7 lub Cent OS 7
Ray
U mnie działało tylko tak: $command = "cmd /c C:\\pdftk\\bin\\pdftk.exe {$file1} {$file2} cat output {$new}";Zwróć uwagę na dodatkowe wyjście kota . Zobacz przykłady PDFtk
maxpower9000,
-1

Rozwiązanie myokyawhtun działało najlepiej dla mnie (używając PHP 5.4)

Nadal jednak będzie wyświetlany błąd - rozwiązałem go, korzystając z następującego rozwiązania:

Linia 269 pliku fpdf_tpl.php - zmieniono parametry funkcji na:

function Image($file, $x=null, $y=null, $w=0, $h=0, $type='', $link='',$align='', $resize=false, $dpi=300, $palign='', $ismask=false, $imgmask=false, $border=0) { 

Tę samą zmianę dokonałem również w linii 898 pliku fpdf.php

Scott
źródło