Michiel van Vaardegem
Michiel van Vaardegem

Reputation: 2035

draw table with filled cells in a PDF using iText for .NET

I want to draw a schedule, based on a table (including borders), where some of the cells are (partly) filled with a color. This color is based on PdfTemplate which is added as an image to the cell.

I've create a test project for it, and attached placed the output at: PDF output

In the example, I expect it to draw the filled line, everytime 2 blocks larger. As you can see, it is a bit more than the 2 blocks.

The template I create, should be the exact the size of the table cells. So I don't understand, why it keeps getting bigger every iteration.

Any ideas how to fix this?

    public Test()
    {
        int cellWidth = 23;
        int usernameCellWith = 86;

        float[] scheduleTableColumnWidths = new float[33];
        scheduleTableColumnWidths[0] = usernameCellWith;
        for (int i = 1; i <= 32; i++)
            scheduleTableColumnWidths[i] = cellWidth;

        using (MemoryStream stream = new MemoryStream())
        {
            Rectangle rect = PageSize.GetRectangle("A4");
            rect = new Rectangle(rect.Height, rect.Width, 90);
            using (Document document = new Document(rect, 10f, 10f, 10f, 10f))
            {
                PdfWriter writer = PdfWriter.GetInstance(document, stream);
                if (!document.IsOpen())
                {
                    document.NewPage();
                    document.Open();
                }

                PdfPTable containerTable = new PdfPTable(1);
                containerTable.DefaultCell.Border = Rectangle.NO_BORDER;
                containerTable.WidthPercentage = 100;

                PdfPTable scheduleTable = new PdfPTable(scheduleTableColumnWidths);

                for (int iRow = 0; iRow <= 23; iRow++)
                {
                    PdfPCell usernameCell = new PdfPCell(new Phrase("user "+ iRow));
                    usernameCell.FixedHeight = cellWidth;
                    scheduleTable.AddCell(usernameCell);

                    for (int iColumn = 1; iColumn <= 32; iColumn++)
                    {
                        PdfPCell cell = new PdfPCell();
                        cell.FixedHeight = cellWidth;
                        if (iColumn == 1 && iRow % 2 == 0)
                        {
                            float width = (float)(iRow + 1) * cellWidth;
                            PdfTemplate template = writer.DirectContent.CreateTemplate(width, cellWidth);
                            template.SetColorFill(new BaseColor(System.Drawing.ColorTranslator.FromHtml("#255C8A")));
                            template.Rectangle(0, 0, width, cellWidth);
                            template.Fill();
                            cell = new PdfPCell(Image.GetInstance(template));
                        }
                        scheduleTable.AddCell(cell);
                    }
                    scheduleTable.CompleteRow();                        
                }
                containerTable.AddCell(scheduleTable);
                containerTable.CompleteRow();

                document.Add(containerTable);
                document.CloseDocument();
            }
        }
    }

Upvotes: 1

Views: 2106

Answers (1)

Chris Haas
Chris Haas

Reputation: 55457

Do you absolutely need to use a PdfTemplate? Unless you've just posted a simplified version of your code it is screaming to use a colspan instead and then iText will just calculate everything for you.

Below is a modified version of what you posted. I moved some of your "magic numbers" to variables just to help my own mental process. They might not be named ideally but they do the job. Also, when working in a double for loop I recommend always looping over rows and columns and then performing logic. Your code loops over rows, does some logic for a "first cell" and then loops over a subset of the columns and performs more logic. Although absolutely 100% valid I personally find it confusing, especially when having to jump between 1 and 0 as a starting value.

Also, also, I very strongly recommend avoiding CompleteRow(). It exists to solve a certain problem but it also literally means "my logic above might be wrong so iText, could you just fudge the numbers for me?"

I commented most of what I changed so hopefully this is helpful.

public void Test() {
    int cellWidth = 23;
    int usernameCellWith = 86;
    int maxNumberOfColumns = 32;
    int maxNumberOfRows = 24;

    float[] scheduleTableColumnWidths = new float[maxNumberOfColumns];
    scheduleTableColumnWidths[0] = usernameCellWith;
    for (int i = 1; i < maxNumberOfColumns; i++)
        scheduleTableColumnWidths[i] = cellWidth;

    using (MemoryStream stream = new MemoryStream()) {
        iTextSharp.text.Rectangle rect = PageSize.GetRectangle("A4");
        rect = new iTextSharp.text.Rectangle(rect.Height, rect.Width, 90);
        using (Document document = new Document(rect, 10f, 10f, 10f, 10f)) {
            PdfWriter writer = PdfWriter.GetInstance(document, stream);

            //Unless this code is just a sample, a new document is never open and NewPage() is implied so these four lines could just be document.Open();
            if (!document.IsOpen()) {
                document.NewPage();
                document.Open();
            }

            PdfPTable containerTable = new PdfPTable(1);
            containerTable.DefaultCell.Border = iTextSharp.text.Rectangle.NO_BORDER;
            containerTable.WidthPercentage = 100;

            PdfPTable scheduleTable = new PdfPTable(scheduleTableColumnWidths);

            //Loop through each row
            for (int iRow = 0; iRow < maxNumberOfRows; iRow++) {

                //Loop through each column
                for (int iColumn = 0; iColumn < maxNumberOfColumns; iColumn++) {

                    //Placeholder variable that will be instantiated within the if statements below
                    PdfPCell cell;

                    //On the first column (of every row)
                    if (0 == iColumn) {
                        cell = new PdfPCell(new Phrase("user " + iRow));

                    //On the second column of every other row starting with the first (zeroth) row
                    } else if ((iColumn == 1) && (iRow % 2 == 0)) {
                        cell = new PdfPCell();
                        //Have the cell span based on the current row number
                        cell.Colspan = iRow + 1;
                        //Set the color
                        cell.BackgroundColor = new BaseColor(System.Drawing.ColorTranslator.FromHtml("#255C8A"));
                        //Tell the inner for loop that we've accounted for some cells already
                        iColumn += iRow;

                    //Every other cell
                    } else {
                        cell = new PdfPCell();
                    }

                    //This is weird
                    cell.FixedHeight = cellWidth;

                    //Regardless of the above, add a cell
                    scheduleTable.AddCell(cell);
                }
            }
            containerTable.AddCell(scheduleTable);

            document.Add(containerTable);
            document.CloseDocument();
        }
    }
}

Upvotes: 1

Related Questions