3-Heights™ PDF Toolbox – Entwicklerkomponente für die PDF Verarbeitung

Die 3-Heights™ PDF Toolbox ist eine hochperformante, plattformunabhängige Komponente für das Erzeugen und Assemblieren von PDF/A konformen Dokumenten.

Assemblieren

Setzen Sie PDF Dokumente aus beliebigen Quellen zusammen

Anreichern

Reichern Sie PDF Dokumente mit Zusatzinformationen, wie z.B. Stempel, an

Ergänzen

Ergänzen Sie PDF Dokumente durch neue Seiten mit beliebigen Inhalten

Zeit sparen dank Cloud-basierter PDF Dienste

Mit wachsender Nutzeranzahl wurden zudem mehr und mehr neue Funktionalitäten von der Community verlangt. Dazu gehörte das Komprimieren von PDF Dateien oder Konvertieren von Office Dokumenten nach PDF.

Smallpdf bei der Komprimierung ganz gross

Die Grösse einer Datei in einem E-Mail Anhang kann dazu führen, dass die E-Mail nicht versendet oder empfangen werden kann. Um dieses Problem zu lösen suchte Smallpdf eine Möglichkeit, die Grösse dieser Dateien zu reduzieren.

PDF Toolbox- Erweiterte PDF Bearbeitung - Bearbeiten und Zusammenstellen von PDF-Dokumenten

PDF toolbox - Funktionen

Dokumentenzusammenstellung

  • Kopieren von Seiten bestehender PDF Dokumente
  • Kopieren von Annotationen/Kommentaren, Formularfeldern, Links, Strukturinformationen, Lesezeichen, Sprungmarken
  • Auflösen von Annotationen, Formularfeldern, Signaturen
  • Ressourcenoptimierung
  • Seiten zuschneiden und drehen
  • Freies Zusammenstellen von Inhalten: Overlays, Underlays, Stempeln, Transformieren
  • Verschlüsselung: Benutzerpasswort, Eigentümerpasswort, Berechtigungen
  • Kopieren und Ändern von Dokument-Metadaten
  • Kopieren und Ändern von Seiten-Metadaten

Inhaltsgenerierung

  • Erstellen von neuen PDF Inhalten von Grund auf
  • Aufbringen von Inhalt auf existierende Seiten

Farben

  • RGB, CMYK und Graustufen
  • ICC Farbprofile
  • Transparenz: Alpha und Blend Modus

Pfade

  • Linien (Single and multi-segment)
  • Rechtecke, Kreise, Bézierkurven, Ellipsen
  • Füllen, zeichnen, clippen und Kombinationen davon
  • Linienbreite, -ende (cap), -verbindung (join), -strich (dash array, dash phase) und Gehrungsgrenze (miter limit)
  • Füllregel: Nonzero winding oder Even/odd

Text

  • Schriftgröße, Zeichenabstand, Wortabstand
  • Horizontale Skalierung, leading, hoch stellen
  • Einfaches Textlayout ermöglichen
  • Standard PDF Schriften, installierte Schriften
  • Fontmetriken: Italic angle, Ascent, Descent, Cap height, Character width
  • Unicode Zeichen
  • Textstrich: Linienbreite, Linienverbindung und Striche
  • Text Füllen und Markieren, unsichtbarer Text
  • Text als Beschneidungspfad verwenden

Bilder

  • Bi-Level: CCITT G3, G3 2D und G4, Flate, LZW, Packbits, unkomprimiert
  • 4 Bit und 8 Bit Graustufen: Flate, LZW, Packbits, JPEG und JPEG-6 (nur 8 Bit), unkomprimiert
  • RGB: Flate, JPEG und JPEG-6, LZW, Packbits, unkomprimiert

Transformationen

  • Übersetzung
  • Skalieren
  • Verzerren (Horizontal oder Vertikal)
  • Rotation

Änderung von Inhalt

  • Selektives Kopieren von Inhaltselementen (ohne Markup)
  • Geometrische Transformation von Inhaltselementen

Extrahieren

  • Dokument und Seiten
    • Dokumentinformationen: Titel, Autor, Betreff, Schlagwörter, Ersteller, Produzent, Erstellungsdatum, Änderungsdatum
    • Dokument XMP Metadaten
    • Seiten Bounding-Boxen: Media box, Crop box, Bleed box, Trim box, Art box
    • Seite XMP Metadaten
  • Inhalt
    • Inhaltselemente der Seiten und Group-Elemente, einschließlich der folgenden Attribute
      • Begrenzungsbox
      • Affine Transformation
    • Extrahierte Elementtypen:
      • Group-Element
      • Bildelement
        • Breite und Höhe in Pixel
        • Bits pro Farbkomponente
        • Farbraum
    • Bildmaskenelement
      • Breite und Höhe in Pixel
      • Farbe zum Füllen der Maske
    • Pfadelement
      • Ausrichtungsbox
      • Füllparameter einschließlich Farbe und Füllregel
      • Strichparameter einschließlich Linienfarbe und Linienstil
    • Shading-Element
    • Textelement
      • Textfragmente
        • Begrenzungsbox
        • Affine Transformation
        • Unicode String
        • Füllparameter einschließlich Farbe und Füllregel
        • Strichparameter einschließlich Linienfarbe und Linienstil
  • Annotationen
    • Signaturfelder: Name, Ort, Grund, Kontaktinformationen, Datum, Sichtbarkeit

Conformance

  • ISO 32000-1 (PDF 1.7)
  • ISO 32000-2 (PDF 2.0)
  • ISO 19005-1 (PDF/A-1)
  • ISO 19005-2 (PDF/A-2)
  • ISO 19005-3 (PDF/A-3)

Unterstützte Formate

Unterstützte PDF-Formate

  • PDF 1.0 bis 1.7
  • PDF 2.0
  • PDF/A-1, PDF/A-2, PDF/A-3

Unterstützte Bildformate

  • BMP
  • DIB
  • JPEG
  • JPEG2000
  • JBIG2
  • PNG
  • GIF
  • TIFF

Unterstützte Schriftformate

  • Type1
  • TrueType
  • OpenType
  • OpenType (CFF)
Vergrößerungslinse für unsere PDF-Handbücher und PDF-Beispielcode

HANDBUCH

API

Einsatzgebiete - Bearbeiten und zusammenstellen von PDF Dokumenten

PDF Erstellung

Programmatische Erstellung von beliebigen PDF/A konformen Dokumenten, wobei die Inhalte aus beliebigen Quellen stammen können, wie z. B. einer Datenbank oder einem Webseitenformular.

Personalisierung

PDF Dokumente werden für den Vertrieb von E-Books verwendet sowie für das Erstellen von Rechnungen, Verträgen und Bedingungen u. a. in Versicherungsanträgen. Diese Dokumente haben alle eine gemeinsame Anforderung: Sie müssen um empfängerspezifische Informationen ergänzt werden.

Automatisierte Berichterstellung

Grosse Informationsmengen werden auf Datenbank- und DMS-Systemen gespeichert. Die PDF Toolbox eignet sich, um diese Informationen entgegen zu nehmen und daraus verteilbare und standardisierte PDF Dokumente zu erstellen. Somit wird auch die Distribution über Infrastrukturen mit verschiedensten Technologien und Betriebssystemen ermöglicht.

Weitere Einsatzgebiete

  • Erzeugung von benutzerdefinierten Dokumenten wie Angebote, Policendossiers etc.
  • Massenerzeugung von Rechnungen, Berichten usw.
  • PDF Erzeugung „on-the-fly“ in Webserveranwendungen
  • „Save as PDF/A“ Funktion in Anwenderprogrammen
  • Importieren von Bildern und Texten in PDF/A Dokumente

Stempel zu PDF hinzufügen

Füge jeder Seite eines PDF Dokuments einen transparenten Stempeltext hinzu. Optional können die Farbe und die Deckkraft des Stempels festgelegt werden.

C# Beispiel:
// Open input document
using (Stream inStream = new FileStream(inPath, FileMode.Open, FileAccess.Read))
using (Document inDoc = Document.Open(inStream, null))

// Create output document
using (Stream outStream = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite))
using (Document outDoc = Document.Create(outStream, Conformance.Unknown, null))
using (Font font = outDoc.CreateSystemFont("Arial", "Italic", true))
{
    // Set output intent
    if (inDoc.OutputIntent != null)
        using (ColorSpace outputIntent = outDoc.CopyColorSpace(inDoc.OutputIntent))
            outDoc.OutputIntent = outputIntent;

    // Copy metadata
    using (Metadata metadata = outDoc.CopyMetadata(inDoc.Metadata))
        outDoc.Metadata = metadata;

    // Set copy options
    CopyOption copyOptions = CopyOption.CopyLinks | CopyOption.CopyAnnotations | 
        CopyOption.CopyFormFields | CopyOption.CopyOutlines | CopyOption.CopyLogicalStructure;

    // Get the device color space
    using (ColorSpace colorspace = outDoc.CreateDeviceColorSpace(DeviceColorSpaceType.RGB))
    // Create paint object with the choosen RGB color
    using (paint = outDoc.CreateAlphaPaint(colorspace, alpha, 1.0, 0.0, 0.0))
    {
        // Loop through all pages of input
        foreach (Page inPage in inDoc.Pages)
        {
            // Copy page from input to output
            using (Page outPage = outDoc.CopyPage(inPage, copyOptions))
            {
                // Add text to page
                AddStamp(outDoc, outPage, stampString, font, 50);

                // Add page to document
                outDoc.Pages.Add(outPage);
            }
        }
    }
}
private static void AddStamp(Document outputDoc, Page outPage, string stampString, 
    Font font, double fontSize)
{
    // Create content generator and text object
    using (ContentGenerator gen = new ContentGenerator(outPage.Content, false))
    using (Text text = outputDoc.CreateText())
    {
        // Create text generator
        using (TextGenerator textgenerator = new TextGenerator(text, font, fontSize, null))
        {
            // Calculate point and angle of rotation
            Point rotationCenter = new Point
            {
                X = outPage.Size.Width / 2.0,
                Y = outPage.Size.Height / 2.0
            };
            double rotationAngle = Math.Atan2(outPage.Size.Height,
                outPage.Size.Width) / Math.PI * 180.0;

            // Rotate textinput around the calculated position
            Transformation trans = new Transformation();
            trans.RotateAround(rotationAngle, rotationCenter);
            gen.Transform(trans);

            // Calculate position
            Point position = new Point
            {
                X = (outPage.Size.Width - textgenerator.GetWidth(stampString)) / 2.0,
                Y = (outPage.Size.Height - font.Ascent * fontSize) / 2.0
            };

            // Move to position
            textgenerator.MoveTo(position);
            // Set text rendering 
            textgenerator.SetRendering(paint, null, false);
            // Add given stamp string
            textgenerator.ShowLine(stampString);
        }
        // Paint the positioned text
        gen.PaintText(text);
    }
}
Java Beispiel:
// Open input document
inStream = new FileStream(inPath, "r");
inDoc = Document.open(inStream, null);

// Create output document
outStream = new FileStream(outPath, "rw");
outStream.setLength(0); 
outDoc = Document.create(outStream, Conformance.UNKNOWN, null);

// Create embedded font in output document
Font font = outDoc.createSystemFont("Arial", "Italic", true);

// Set output intent
if (inDoc.getOutputIntent() != null) 
    outDoc.setOutputIntent(outDoc.copyColorSpace(inDoc.getOutputIntent()));

// Copy metadata
Metadata metadata =  outDoc.copyMetadata(inDoc.getMetadata());
outDoc.setMetadata(metadata);

// Set copy options
EnumSet<CopyOption> copyOptions = EnumSet.of(CopyOption.COPY_LINKS, CopyOption.COPY_ANNOTATIONS,
        CopyOption.COPY_FORM_FIELDS, CopyOption.COPY_OUTLINES, CopyOption.COPY_LOGIGAL_STRUCTURE);

// Get the device color space
ColorSpace colorSpace = outDoc.createDeviceColorSpace(DeviceColorSpaceType.RGB);

// Choose the RGB color value
double[] color = {1.0, 0.0, 0.0};

// Create paint object
paint = outDoc.createAlphaPaint(colorSpace, alpha, color);

// Loop throw all pages of input
for (Page inPage : inDoc.getPages())
{
    // Copy page from input to output
    Page outPage = outDoc.copyPage(inPage, copyOptions);

    // Add text to page 
    addStamp(outDoc, outPage, stampString, font, 50);

    // Add page to document
    outDoc.getPages().add(outPage);

    //Cleanup
    outPage.close();
    inPage.close();
}
private static void addStamp(Document outputDoc, Page outPage, String stampString, 
Font font, double fontSize) throws ErrorCodeException 
{

    ContentGenerator generator = null;

    try 
    {
        // Create content generator 
        generator = new ContentGenerator(outPage.getContent(), false);

        // Create text object
        Text text = outputDoc.createText();

        // Create text generator
        TextGenerator textgenerator = new TextGenerator(text, font, fontSize, null);

        // Calculate point and angle of rotation
        Point rotationCenter = new Point(
                outPage.getSize().width / 2.0,
                outPage.getSize().height / 2.0
                );

        // Calculate rotation angle
        double rotationAngle = Math.atan2(outPage.getSize().height, 
            outPage.getSize().width) / Math.PI * 180.0;

        // Rotate text input around the calculated position
        Transformation trans = new Transformation();
        trans.rotateAround(rotationAngle, rotationCenter);
        generator.transform(trans);

        // Calculate position 
        Point position = new Point(
                (outPage.getSize().width - textgenerator.getWidth(stampString)) / 2.0,
                (outPage.getSize().height - font.getAscent() * fontSize) / 2.0
                );

        // Move to position 
        textgenerator.moveTo(position);

        // Set text rendering 
        textgenerator.setRendering(paint, null, false);

        // Add given stamp string 
        textgenerator.showLine(stampString);
        textgenerator.close(); 

        // Paint the positioned text
        generator.paintText(text);
    } 
    finally
    {
        // Close content generator
        if (generator != null)
            generator.close();
    }
}
C Beispiel:
// Open input document
pInStream = _tfopen(szInPath, _T("rb"));
GOTO_CLEANUP_IF_NULL(pInStream, _T("Failed to open input file \"%s\".\n"), szInPath);
PdfCreateFILEStreamDescriptor(&inDescriptor, pInStream, FALSE);
pInDoc = PdfDocumentOpen(&inDescriptor, _T(""));
GOTO_CLEANUP_IF_NULL_PRINT_ERROR(pInDoc, _T("Input file \"%s\" cannot be opened. %s (ErrorCode: 0x%08x).\n"), szInPath, szErrorBuff, PdfGetLastError());

// Create output document
pOutStream = _tfopen(szOutPath, _T("wb+"));
GOTO_CLEANUP_IF_NULL(pOutStream, _T("Failed to open output file %s.\n"), szOutPath);
PdfCreateFILEStreamDescriptor(&outDescriptor, pOutStream, 0);
pOutDoc = PdfDocumentCreate(&outDescriptor, PdfDocumentGetConformance(pInDoc), NULL);
GOTO_CLEANUP_IF_NULL_PRINT_ERROR(pOutDoc, _T("Output file %s cannot be closed. %s (ErrorCode: 0x%08x).\n"), szOutPath, szErrorBuff, PdfGetLastError());

// Create embedded font in output document
pFont = PdfDocumentCreateSystemFont(pOutDoc, _T("Arial"), _T("Italic"), TRUE);
GOTO_CLEANUP_IF_NULL_PRINT_ERROR(pFont, _T("Embedded font cannot be created. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

// Set output intent
if (PdfDocumentGetOutputIntent(pInDoc) != NULL)
    PdfDocumentSetOutputIntent(pOutDoc, PdfDocumentCopyColorSpace(pOutDoc, PdfDocumentGetOutputIntent(pInDoc)));

// Copy metadata
pMetadata = PdfDocumentCopyMetadata(pOutDoc, PdfDocumentGetMetadata(pInDoc));
GOTO_CLEANUP_IF_NULL_PRINT_ERROR(pMetadata, _T("Failed to copy metadata from input file. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());
GOTO_CLEANUP_IF_FALSE_PRINT_ERROR(PdfDocumentSetMetadata(pOutDoc, pMetadata), _T("Failed to set metadata. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

// Set copy options
TPdfCopyOption pCopyOptions = ePdfCopyLinks | ePdfCopyAnnotations | ePdfCopyFormFields | ePdfCopyOutlines | ePdfCopyLogicalStructure;

// Get the device color space
TPdfColorSpace* pColorSpace = PdfDocumentCreateDeviceColorSpace(pOutDoc, ePdfColorSpaceRGB);
GOTO_CLEANUP_IF_NULL_PRINT_ERROR(pColorSpace, _T("Failed to get the device color space. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

// Chose the RGB color values
double color[] = { 1.0, 0.0, 0.0 };
size_t nColor = sizeof(color) / sizeof(double);

// Create paint object
pPaint = PdfDocumentCreateAlphaPaint(pOutDoc, pColorSpace, dAlpha, color, nColor);
GOTO_CLEANUP_IF_NULL_PRINT_ERROR(pPaint, _T("Failed to create a transparent paint. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

pInPageList = PdfDocumentGetPages(pInDoc);
GOTO_CLEANUP_IF_NULL_PRINT_ERROR(pInPageList, _T("Failed to get the pages of the input document. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());
pOutPageList = PdfDocumentGetPages(pOutDoc);
GOTO_CLEANUP_IF_NULL_PRINT_ERROR(pOutPageList, _T("Failed to get the pages of the output document. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

// Loop through all pages of input
for (int i = 0; i < PdfPageListGetCount(pInPageList); i++)
{
    // Get a list of pages
    pInPage = PdfPageListGet(pInPageList, i);

    // Copy page from input to output
    pOutPage = PdfDocumentCopyPage(pOutDoc, pInPage, pCopyOptions);
    GOTO_CLEANUP_IF_NULL_PRINT_ERROR(pOutPage, _T("Failed to copy pages from input to output. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

    // Add stamp to page
    if (addStamp(pOutDoc, pOutPage, szStampString, pFont, 50) == 1)
    {
        goto cleanup;
    }

    // Add page to output document
    GOTO_CLEANUP_IF_FALSE_PRINT_ERROR(PdfPageListAppend(pOutPageList, pOutPage), _T("Failed to add page to output document. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

    if (pOutPage != NULL)
    {
        PdfClose(pOutPage);
        pOutPage = NULL;
    }

    if (pInPage != NULL)
    {
        PdfClose(pInPage);
        pInPage = NULL;
    }
}
int addStamp(TPdfDocument* pOutDoc, TPdfPage* pOutPage, TCHAR* szStampString, TPdfFont* pFont, double dFontSize)
{
    TPdfContentGenerator* pGenerator = NULL;
    TPdfText* pText = NULL;
    TPdfTextGenerator* pTextGenerator = NULL;
    TPdfTransformation* pTrans = NULL;

    TPdfContent* pContent = PdfPageGetContent(pOutPage);

    // Create content generator
    pGenerator = PdfNewContentGenerator(pContent, FALSE);
    GOTO_CLEANUP_IF_NULL_PRINT_ERROR(pGenerator, _T("Failed to create a content generator. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

    // Create text object
    pText = PdfDocumentCreateText(pOutDoc);
    GOTO_CLEANUP_IF_NULL_PRINT_ERROR(pText, _T("Failed to create a text object. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

    // Create a text generator 
    pTextGenerator = PdfNewTextGenerator(pText, pFont, dFontSize, NULL);
    GOTO_CLEANUP_IF_NULL_PRINT_ERROR(pTextGenerator, _T("Failed to create a text generator. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

    // Get output page size
    TPdfSize size;
    GOTO_CLEANUP_IF_FALSE_PRINT_ERROR(PdfPageGetSize(pOutPage, &size), _T("Failed to read page size. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

    // Calculate point and angle of rotation
    TPdfPoint rotationCenter;
    rotationCenter.dX = size.dWidth / 2.0;
    rotationCenter.dY = size.dHeight / 2.0;
    double dRotationAngle = atan2(size.dHeight, size.dWidth) / M_PI * 180.0;

    // Rotate textinput around the calculated position
    pTrans = PdfNewTransformationIdentity();
    GOTO_CLEANUP_IF_FALSE_PRINT_ERROR(PdfTransformationRotateAround(pTrans, dRotationAngle, &rotationCenter), _T("Failed to rotate textinput around the calculated position. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());
    GOTO_CLEANUP_IF_FALSE_PRINT_ERROR(PdfContentGeneratorTransform(pGenerator, pTrans), _T("Failed to modify the current transformation. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

    // Calculate position
    TPdfPoint position;
    double dTextWidth = PdfTextGeneratorGetWidth(pTextGenerator, szStampString);
    GOTO_CLEANUP_IF_ZERO_PRINT_ERROR(dTextWidth, _T("%s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());
    double dFontAscent = PdfFontGetAscent(pFont);
    GOTO_CLEANUP_IF_ZERO_PRINT_ERROR(dFontAscent, _T("%s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());
    position.dX = (size.dWidth - dTextWidth) / 2.0;
    position.dY = (size.dHeight - dFontAscent * dFontSize) / 2.0;

    // Move to position
    GOTO_CLEANUP_IF_FALSE_PRINT_ERROR(PdfTextGeneratorMoveTo(pTextGenerator, &position), _T("Failed to move to position. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());
    // Set text rendering
    GOTO_CLEANUP_IF_FALSE_PRINT_ERROR(PdfTextGeneratorSetRendering(pTextGenerator, pPaint, NULL, FALSE), _T("Failed to set rendering. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());
    // Add given stamp string
    GOTO_CLEANUP_IF_FALSE_PRINT_ERROR(PdfTextGeneratorShowLine(pTextGenerator, szStampString), _T("Failed to add stamp. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

    // Close text generator
    if (pTextGenerator != NULL)
    {
        PdfClose(pTextGenerator);
        pTextGenerator = NULL;
    }

    // Paint the positioned text
    GOTO_CLEANUP_IF_FALSE_PRINT_ERROR(PdfContentGeneratorPaintText(pGenerator, pText), _T("Failed to paint the positioned text. %s (ErrorCode: 0x%08x).\n"), szErrorBuff, PdfGetLastError());

cleanup:
    if (pTrans != NULL)
        PdfClose(pTrans);
    if (pTextGenerator != NULL)
        PdfClose(pTextGenerator);
    if (pText != NULL)
        PdfClose(pText);
    if (pContent != NULL)
        PdfClose(pContent);
    if (pGenerator != NULL)
        PdfClose(pGenerator);

    return iReturnValue;
}
PDF Expert blog - the caveats of assembling PDF/A documents

The caveats of assembling PDF/A documents

Assembling PDF documents from various sources is a crucial part of an output management system. And, as the document needs to be archived in most cases, it should conform to the PDF/A standard. Is there a way to assemble a document and accomplish PDF/A conformance in one step?


PDF Expert Blog - embedded fonts and PDF mass printing application

The problem with embedded fonts in PDF mass printing applications

PDF is more and more finding its way into mass printing applications. However, PDF spool files often ask too much from a print engine resulting in aborts or, even worse, incomplete prints which may not be noticed. What is special about PDF mass printing...