0

I'm developing a windows form application for generating invoices in PDF files.

This Winform application is using a PDF template for creating a PDF file.

This is the screenshot of the PDF template (this template was created using Adobe Acrobat XI Lite Portable):

PDF Template

I'm using (version 5.5.13) in this code for generate the PDF file:

private void GenerateInvoice(DataTable tbl_template_variables, DataTable tbl_details_invoice)
{
    using (PdfReader pdfReader = new PdfReader(plantilla__Invoice__manual))
    {
        try
        {
            PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileStream(location_output_file, FileMode.Create));
            AcroFields pdfFormFields = pdfStamper.AcroFields;

            // Loop DataTable and set the value in the specified field.
            for (int i = 0; i < tbl_template_variables.Rows.Count; i++)
            {
                pdfFormFields.SetField(tbl_template_variables.Rows[i][0].ToString(), tbl_template_variables.Rows[i][1].ToString(), true);// set form pdfFormFields 
            }

            #region Details's table Invoice

            PdfPCell cell = null;
            PdfPTable table = null;

            table = new PdfPTable(9);
            table.HorizontalAlignment = Element.ALIGN_LEFT;
            table.SetWidths(new float[] { 22f, 22f, 22f, 22f, 22f, 22f, 22f, 22f, 22f });
            //table.SpacingBefore = 5;
            table.TotalWidth = 800f;

            for (int i = 0; i < tbl_details_invoice.Rows.Count; i++)
            {
                DataRow row = tbl_details_invoice.Rows[i];
                object Invoice_PDFColumn0_value = row.Field<string>("PROVIDER") == null ? string.Empty : row.Field<string>("PROVIDER").ToString();
                object Invoice_PDFColumn1_value = row.Field<string>("DESCRIPTION") == null ? string.Empty : row.Field<string>("DESCRIPTION").ToString();
                object Invoice_PDFColumn2_value = row.Field<string>("PPTO") == null ? string.Empty : row.Field<string>("PPTO").ToString();
                object Invoice_PDFColumn3_value = row.Field<string>("JOB_MEDIA_TYPE") == null ? string.Empty : row.Field<string>("JOB_MEDIA_TYPE").ToString();
                object Invoice_PDFColumn4_value = row.Field<string>("VEND_INV_NO") == null ? string.Empty : row.Field<string>("VEND_INV_NO").ToString();
                //object Invoice_PDFColumn5_value = row.Field<string>("ORDER_MEDIA") == null ? string.Empty : row.Field<string>("ORDER_MEDIA").ToString();
                //object Invoice_PDFColumn6_value = row.Field<string>("ACTIVITY_MONTH") == null ? string.Empty : row.Field<string>("ACTIVITY_MONTH").ToString();
                object Invoice_PDFColumn7_value = row.Field<string>("COMMISSIONABLE") == null ? string.Empty : row.Field<string>("COMMISSIONABLE").ToString();
                object Invoice_PDFColumn8_value = row.Field<string>("NON_COMMISSIONABLE") == null ? string.Empty : row.Field<string>("NON_COMMISSIONABLE").ToString();
                string Invoice_PDFColumn9_value = row.Field<string>("IVA_PROVEEDOR") == null ? string.Empty : row.Field<string>("IVA_PROVEEDOR").ToString();
                string Invoice_PDFColumn10_value = row.Field<string>("TOTAL") == null ? string.Empty : row.Field<string>("TOTAL").ToString();

                //Columns table
                cell = PhraseCell(new Phrase(Invoice_PDFColumn0.ToString(), GettypeStyle()));
                table.AddCell(cell);

                cell = PhraseCell(new Phrase(Invoice_PDFColumn1.ToString(), GettypeStyle()));
                table.AddCell(cell);

                cell = PhraseCell(new Phrase(Invoice_PDFColumn2.ToString(), GettypeStyle()));
                table.AddCell(cell);

                cell = PhraseCell(new Phrase(Invoice_PDFColumn3.ToString(), GettypeStyle()));
                table.AddCell(cell);

                cell = PhraseCell(new Phrase(Invoice_PDFColumn4.ToString(), GettypeStyle()));
                table.AddCell(cell);

                //cell = PhraseCell(new Phrase(Invoice_PDFColumn5.ToString(), GettypeStyle()));
                //table.AddCell(cell);

                //cell = PhraseCell(new Phrase(Invoice_PDFColumn6.ToString(), GettypeStyle()));
                //table.AddCell(cell);

                cell = PhraseCell(new Phrase(Invoice_PDFColumn7.ToString(), GettypeStyle()));
                table.AddCell(cell);

                cell = PhraseCell(new Phrase(Invoice_PDFColumn8.ToString(), GettypeStyle()));
                table.AddCell(cell);

                cell = PhraseCell(new Phrase(Invoice_PDFColumn9.ToString(), GettypeStyle()));
                table.AddCell(cell);

                cell = PhraseCell(new Phrase(Invoice_PDFColumn10.ToString(), GettypeStyle()));
                table.AddCell(cell);
            }

            ColumnText ct = new ColumnText(pdfStamper.GetOverContent(1));
            ct.AddElement(table);
            //iTextSharp.text.Rectangle rect = new iTextSharp.text.Rectangle(18, 370, 800, 36);
            iTextSharp.text.Rectangle rect = new iTextSharp.text.Rectangle(16, 320, 900, 16);
            rect.Border = iTextSharp.text.Rectangle.LEFT_BORDER | iTextSharp.text.Rectangle.RIGHT_BORDER;
            rect.BorderWidth = 15;
            rect.BorderColor = new BaseColor(0, 0, 0);
            rect.Border = iTextSharp.text.Rectangle.BOX;
            ct.SetSimpleColumn(rect);
            ct.Go();

            #endregion

            // flatten the form to remove editting options, set it to false
            // to leave the form open to subsequent manual edits
            pdfStamper.FormFlattening = true;
            pdfStamper.FreeTextFlattening = true;
            pdfStamper.Writer.CloseStream = true;
            pdfStamper.Close();// close the pdf
        }
        catch (Exception ex)
        {
            // No errors (yet).
        }
    }
}

And this is the results (with just a few details):

PDF result sample

The problem I'm facing with this code is that, if the details table has more rows, the rows are overwritten in the generated page (and no more pages are generated).

This is the result with overwritten data:

Result with overwritten data

I'm looking the way for generating the PDF invoice correctly (if possible) using this PDF template.

This is the closest favorable result I got so far after tried:

  • Modifying the PDF template for add variables under the detail headers and updating the field's value for add line breaks each time, but, it seems the size of the variable in the PDF matters and so, the line breaks aren't expanding the variable (and forcing) to generate more pages.
  • Using HTMLWorker (deprecated) for force an HTML template (similar to PDF template's structure). This approach works well for generating multiple pages when printing the Details table's rows, but CSS is not applied.
  • Using the combination of PdfDocument, PdfParagraph and similar classes (tried to follow the guidelines I found here), but I honestly have no idea how set in the specific coordinates and using the correct measure points for a set each element in the resulting PDF file.
  • Download iText7 for use HtmlConverter.ConvertToPdf functionality, but in this case, I'm having problems generating the PDF rotated. The documentation is in java which I'm not familiar.
  • I found this documentation in Oracle for Create a PDF Template - focusing on "Defining Groups of Repeating Fields" section, but I can't find how to edit the source code of the PDF template neither if this can be used in a C# WinForms application.

All the mentioned attempts generate me more problems than solutions, hence, I want to focus one more time using the PDF template approach.

I'm running the whole week with this assignment and I'm out of ideas.

I'll edit my question for add related questions with the same problem I'm facing:


I'm hardly trying to avoid creating a give me the code question, but, any suggestion or any idea about how can I generate PDF files correctly are welcome.

Mauricio Arias Olave
  • 2,259
  • 4
  • 25
  • 70

1 Answers1

1

Due to time and other work-related issues, after search during a entirely week, I get the following solution:

  • Instead of keep focus in add pages in the PDF file in construction, I decided to generate X quantity of temporal PDF files (each on, with related names like: PDF_File_0.pdf, PDF_File_1.pdf, PDF_File_2.pdf, etc).
  • Once the temporal PDF files are generated, I used the code I found in this answer for merge all the PDF files in a single PDF file.

If anyone is interested in know how to add multiple pages in a PDF file (while is been created), you can use this code:

string PDF_filePath = @"C:\New Folder\myPdfTest.pdf";
Document doc = new Document();
PdfSmartCopy copy = new PdfSmartCopy(doc, new FileStream(PDF_filePath, FileMode.Create));
doc.Open();

double qtyPages = 8; // it will be added eight pages.

// In each loop iteration a page will be added "which is a Rectangle, actually"
// with the standard size of a LETTER paper format - landscape orientation.
for (int pag = 0; pag < qtyPages; pag++)
{
    iTextSharp.text.Rectangle rect1 = new iTextSharp.text.Rectangle(PageSize.LETTER.Rotate());
    rect1.Border = iTextSharp.text.Rectangle.BOX;
    copy.AddPage(rect1, 0);
}

// Close the document with the changes made.
doc.Close();

I'm using the code from this answer for "merge" PDF temp files in a single PDF file:

/// <summary>
/// Merge PDF's in a single PDF file.
/// Source: https://stackoverflow.com/a/26883360/4092887
/// </summary>
/// <param name="fileNames">List with (filepath & filename) of PDF temp files.</param>
/// <param name="targetPdf">Path and filename of the PDF unified file.</param>
/// <returns>bool</returns>
public bool MergePDFs(IEnumerable<string> fileNames, string targetPdf)
{
    bool merged = true;

    try
    {
        using (FileStream stream = new FileStream(targetPdf, FileMode.Create))
        {
            Document document = new Document();
            PdfCopy pdf = new PdfCopy(document, stream);
            PdfReader reader = null;
            try
            {
                document.Open();
                foreach (string file in fileNames)
                {
                    reader = new PdfReader(file);
                    pdf.AddDocument(reader);
                    reader.Close();
                }
            }
            catch (Exception)
            {
                merged = false;
                if (reader != null)
                {
                    reader.Close();
                }
            }
            finally
            {
                if (document != null)
                {
                    document.Close();
                }
            }
        }
    }
    catch (Exception ex)
    {
        // Log error in the log file - omitted here for clarity's sake.
        MessageBox.Show("An error ocurred at merginf the PDF files: " + SALTO_DE_LINEA +
            "Check the application log file for more details", TITLE, MessageBoxButtons.OK, MessageBoxIcon.Error);
    }

    return merged;
}

The PDF file to create is actually a invoice that uses a PDF template.

Here, I need to divide the Details "stored in a DataTable variable" in chunks of 10 records/rows.

For set the chunks (I need set chunks of 10 records/rows), I added a new DataColumn to the DataTable variable called "PAGE_SEPARATOR" and update the value of "PAGE_SEPARATOR" column with result of this division:

// Set chunk separator.
for (int r = 0; r < items.Rows.Count; r++)
{
    items.Rows[r]["SEPARADOR"] = r/10;
}

The full explanation of this code can be found here

For each chunk of records I have to add a new page with the PDF template's structure.

This is the full code for generate the PDF invoice file using the PDF template and generate the PDF files for each chunk of records; at the end of the process, call the MergePdfs method and delete the temp PDF files:

NOTE: You may find comments in spanish, but I hope the code is clear enough for understand and modify according to your purposes.

/// <summary>
/// Generate PDF invoice file.
/// It wuill create X PDF temp files "with consecutive file names" for - at the end
/// of the process - merge those PDF temp files in a single one PDF file.
/// Temp PDF files will be deleted after creating the PDF merged file.
/// </summary>
/// <param name="formFactura">DataTable with the values of the PDF template.</param>
/// <param name="DetalleFactura">DataTable with the JSON - DataTable (known as details or detalles).</param>
/// <param name="ruta_archivo_salida">File name and path of the PDF unified file.</param>
/// <returns>string</returns>
private string GenerateInvoice(DataTable formFactura, DataTable DetalleFactura, string ruta_archivo_salida)
{
    // Inicializar variables.
    string msg = "";
    List<string> rutas_archivos = new List<string>();

    try
    {
        if (File.Exists(plantilla_factura_manual))
        {
            try
            {
                // Crear X cantidad de archivos.
                // "PAGE_SEPARATOR" es el nombre de la columna que posee los valores separados por bloques.
                DataView view = new DataView(DetalleFactura);
                DataTable distinctValues = view.ToTable(true, "PAGE_SEPARATOR");
                double cantPaginas = distinctValues.Rows.Count;
                for (int pagina = 0; pagina < cantPaginas; pagina++)
                {
                    using (PdfReader pdfReader = new PdfReader(plantilla_factura_manual))
                    {
                        // Agregar la ruta del archivo temporal PDF a generar.
                        rutas_archivos.Add(ruta_factura_generada.Replace(".pdf", "(" + pagina + ").pdf"));

                        PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileStream(ruta_factura_generada.Replace(".pdf", "(" + pagina + ").pdf"), FileMode.OpenOrCreate));
                        AcroFields pdfFormFields = pdfStamper.AcroFields;

                        // Llenar las variables de la plantilla en el archivo PDF en construcción.
                        for (int i = 0; i < formFactura.Rows.Count; i++)
                        {
                            pdfFormFields.SetField(formFactura.Rows[i][0].ToString(), formFactura.Rows[i][1].ToString(), true);// set form pdfFormFields
                        }

                        #region Diseño grid factura

                        PdfPCell cell = null;
                        PdfPTable table = null;

                        table = new PdfPTable(9);
                        table.HorizontalAlignment = Element.ALIGN_LEFT;
                        table.SetWidths(new float[] { 22f, 22f, 22f, 22f, 22f, 22f, 22f, 22f, 22f });
                        //table.SpacingBefore = 5;
                        table.TotalWidth = 800f;

                        DataRow[] filas_a_usar = DetalleFactura.Select("PAGE_SEPARATOR = " + pagina);
                        foreach (DataRow r in filas_a_usar)
                        {
                            DataRow row = r;
                            object valorFacturaPDFColumna0 = row.Field<string>("PROVIDER") == null ? string.Empty : row.Field<string>("PROVIDER").ToString();
                            object valorFacturaPDFColumna1 = row.Field<string>("DESCRIPTION") == null ? string.Empty : row.Field<string>("DESCRIPTION").ToString();
                            object valorFacturaPDFColumna2 = row.Field<string>("PPTO") == null ? string.Empty : row.Field<string>("PPTO").ToString();
                            object valorFacturaPDFColumna3 = row.Field<string>("JOB_MEDIA_TYPE") == null ? string.Empty : row.Field<string>("JOB_MEDIA_TYPE").ToString();
                            object valorFacturaPDFColumna4 = row.Field<string>("VEND_INV_NO") == null ? string.Empty : row.Field<string>("VEND_INV_NO").ToString();
                            //object valorFacturaPDFColumna5 = row.Field<string>("ORDER_MEDIA") == null ? string.Empty : row.Field<string>("ORDER_MEDIA").ToString();
                            //object valorFacturaPDFColumna6 = row.Field<string>("ACTIVITY_MONTH") == null ? string.Empty : row.Field<string>("ACTIVITY_MONTH").ToString();
                            object valorFacturaPDFColumna7 = row.Field<string>("COMMISSIONABLE") == null ? string.Empty : row.Field<string>("COMMISSIONABLE").ToString();
                            object valorFacturaPDFColumna8 = row.Field<string>("NON_COMMISSIONABLE") == null ? string.Empty : row.Field<string>("NON_COMMISSIONABLE").ToString();
                            string valorFacturaPDFColumna9 = row.Field<string>("IVA_PROVEEDOR") == null ? string.Empty : row.Field<string>("IVA_PROVEEDOR").ToString();
                            string valorFacturaPDFColumna10 = row.Field<string>("TOTAL") == null ? string.Empty : row.Field<string>("TOTAL").ToString();

                            //Columnas table
                            cell = PhraseCell(new Phrase(valorFacturaPDFColumna0.ToString(), GettypeStyle()));
                            table.AddCell(cell);

                            cell = PhraseCell(new Phrase(valorFacturaPDFColumna1.ToString(), GettypeStyle()));
                            table.AddCell(cell);

                            cell = PhraseCell(new Phrase(valorFacturaPDFColumna2.ToString(), GettypeStyle()));
                            table.AddCell(cell);

                            cell = PhraseCell(new Phrase(valorFacturaPDFColumna3.ToString(), GettypeStyle()));
                            table.AddCell(cell);

                            cell = PhraseCell(new Phrase(valorFacturaPDFColumna4.ToString(), GettypeStyle()));
                            table.AddCell(cell);

                            //cell = PhraseCell(new Phrase(valorFacturaPDFColumna5.ToString(), GettypeStyle()));
                            //table.AddCell(cell);

                            //cell = PhraseCell(new Phrase(valorFacturaPDFColumna6.ToString(), GettypeStyle()));
                            //table.AddCell(cell);

                            cell = PhraseCell(new Phrase(valorFacturaPDFColumna7.ToString(), GettypeStyle()));
                            table.AddCell(cell);

                            cell = PhraseCell(new Phrase(valorFacturaPDFColumna8.ToString(), GettypeStyle()));
                            table.AddCell(cell);

                            cell = PhraseCell(new Phrase(valorFacturaPDFColumna9.ToString(), GettypeStyle()));
                            table.AddCell(cell);

                            cell = PhraseCell(new Phrase(valorFacturaPDFColumna10.ToString(), GettypeStyle()));
                            table.AddCell(cell);
                        }

                        ColumnText ct = new ColumnText(pdfStamper.GetOverContent(1));
                        ct.AddElement(table);
                        //iTextSharp.text.Rectangle rect = new iTextSharp.text.Rectangle(18, 370, 800, 36);
                        iTextSharp.text.Rectangle rect = new iTextSharp.text.Rectangle(16, 320, 900, 16);
                        rect.Border = iTextSharp.text.Rectangle.LEFT_BORDER | iTextSharp.text.Rectangle.RIGHT_BORDER;
                        rect.BorderWidth = 15;
                        rect.BorderColor = new BaseColor(0, 0, 0);
                        rect.Border = iTextSharp.text.Rectangle.BOX;
                        ct.SetSimpleColumn(rect);
                        ct.Go();

                        #endregion

                        // flatten the form to remove editting options, set it to false
                        // to leave the form open to subsequent manual edits
                        pdfStamper.FormFlattening = true;
                        pdfStamper.FreeTextFlattening = true;
                        pdfStamper.Writer.CloseStream = true;
                        pdfStamper.Close();// close the pdf
                    }
                }

                // Unir los archivos PDF's en uno solo.
                MergePDFs(rutas_archivos, ruta_archivo_salida);

                #region Eliminar archivos PDF temporales.

                try
                {
                    foreach (string archivo in rutas_archivos)
                    {
                        File.Delete(archivo);
                    }
                }
                catch (Exception ex)
                {
                    RegistrarEventosDelPrograma("Error al eliminar archivos PDF temporales: " + ex.ToString(), "Error al eliminar archivos PDF temporales");
                }

                #endregion
            }
            catch (Exception ex)
            {
                msg += "- Hay un error con la plantilla. Consulte el log de eventos." + SALTO_DE_LINEA;
                RegistrarEventosDelPrograma("Error al usar la plantilla (" + Path.GetFileName(plantilla_factura_manual) + "): " + ex.ToString(), "Error al usar la plantilla (" + Path.GetFileName(plantilla_factura_manual) + ")");
            }
        }
    }
    catch (Exception ex)
    {
        msg += "- Hubo un error inesperado al generar el archivo PDF. Consulte el log de eventos.";
        RegistrarEventosDelPrograma("Error al generar el archivo PDF. Detalles: " + ex.ToString(), "Error al generar PDF - Plantilla");
    }

    return msg;
}

This is my closely related entry in Stack Overflow in spanish.

Mauricio Arias Olave
  • 2,259
  • 4
  • 25
  • 70