Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tables #29

Open
alFReD-NSH opened this issue Aug 24, 2011 · 61 comments
Open

tables #29

alFReD-NSH opened this issue Aug 24, 2011 · 61 comments
Labels

Comments

@alFReD-NSH
Copy link

I have an idea for it. We can pass a two-dimensional array of texts, to a method with presentation options like the following:

doc.table([
  ["cell11"],["cell21"],["cell31"],
  ["cell12"],["cell22"],["cell32"],
  ["cell13"],["cell23"],["cell33"]
  ],{
    width:20,
    height:40,
    x:30,
    y:40
});

Then in the module we make rectangles and texts for each cell.

@keyboard4444
Copy link

I work as programmer to do alot of customization for business. It will involve creating report in PDF format.
This thing already good but it need something to draw a table.

                            Well, it is not draw a table but more specifically draw a row (with several column inside it)
                            Then from there, looping the row result to create a table. This is the 'practical' use

                            so usually the code will end up like this
                            doc.setColumn('no', '1')
                            doc.setColumn('name', 'Bomb') 
                            doc.setColumn('price', '10.01')
                            doc.drawRow()

@wiz
Copy link

wiz commented Mar 9, 2012

Is there actual PDF markup to render a table, or is it just lots of lines?

@ForbesLindesay
Copy link

+1000 for this. Anyone know the answer to @jmaurice's question?

P.S. A two dimensional array would look like:

doc.table([
  ["cell11","cell21","cell31"],
  ["cell12","cell22","cell32"],
  ["cell13","cell23","cell33"]
  ],{
    width:20,
    height:40,
    x:30,
    y:40
});

@giuseppe-santoro
Copy link

Hi people!
I'm developing a function to generate tables, in general my idea is:

doc.table data options

Where data is an array of rows and each row is rappresented as an object.
For example, suppose you want to create an invoice, data should be something like this:

data =
    [
        { code: '0001', name: 'Black table', quantity: '10', price: '$ 19.20' }
        { code: '0005', name: 'White table', quantity: '8',  price: '$ 19.20' }
        { code: '0012', name: 'Red chair',   quantity: '40', price: '$ 12.00' }
    ]

In my opinion, this strategy is more useful than a two dimensional array when you get the data from a database.

The second parameter, options, contains a set of options that modify the rendering of the table.
An example of options should be:

options =

    columns:
    [
        { id: 'code',     width: 10, name: 'Code' }
        { id: 'name',     width: 40, name: 'Name' }
        { id: 'quantity', width: 25, name: 'Quantity' }
        { id: 'price',    width: 25, name: 'Price' }
    ]

    margins:
        left: 20
        top: 40
        right: 20
        bottom: 0

    padding:
        left: 10
        top: 10
        right: 10
        bottom: 10

options.columns.id is the is ID used to map the column with the corresponding data.
options.columns.width is the percentage of width that the column occupies; this meas that the sum of all width must be 100.
options.columns.name is the name of the column that will be render as an header of the table.
options.margins is the table margin, so it is the distance from the page borders to the table borders.
options.padding is the cell padding, so it is the distance from the cell borders to the text in the cell.

What do you think about?
any suggestion is appreciated!

@alvassin
Copy link

alvassin commented Sep 2, 2013

+1

@mrjonny2
Copy link

mrjonny2 commented Sep 2, 2013

Looks great! Can't wait to try it.

@johntom
Copy link

johntom commented Oct 28, 2013

+1 This would be great.

@alexvdev
Copy link

alexvdev commented Apr 2, 2014

Hi guys, I'd like to discuss table creation. @giuseppe-santoro made a sample 8 months ago, thanks him a lot. I think, this feature should be able to take parameters which can be set by html at some html table and generate pdf table as close as possible to source html table formatting. Like wkhtmltopdf does. Looking through the specifications, I found in pdf 1.4 mentioning about tables (9.7.4 > Block-Level Structure Elements > Table Elements). What do you think about implementation? What kind of API will be useful to generate tables?

@giuseppe-santoro
Copy link

Hi all, when 9 months ago I have done the sample, I also started to implement it in a fork found here: https://github.com/giuseppe-santoro/pdfkit/blob/master/js/mixins/tables.js.
Since there are some bugs in it I never asked a pull request.
Unfortunatelly now I have no time to work on this but if you want you can take advantage from the code in my fork.
I really like this project and I hope I can contribute in it in the future.

@MrScruffyish
Copy link

+1
What is the status for this?

@giuseppe-santoro
Copy link

it works pretty well, but it contains some bugs so it is not suitable for production use

@mscdex
Copy link

mscdex commented May 24, 2014

+1000

@diogoAntunes
Copy link

Please add this to pdfkit! I need to insert a table in the pdf and i have no ideia how

@aszharite
Copy link

@giuseppe-santoro have you made any progress with it? could you specify some specifications as in how you thought an example of table. i really need this and i might try to work out the bugs and fix it but i`ll need your base concept for direction.

@giuseppe-santoro
Copy link

Hi @aszharite, sorry for the late replay...
As you can see in table.cooffe, I have created a new mixin for tables.
Basically the core function is 'table' that render the table. It render the header row and than all the body rows according to options. The options format is the format I wrote in previous comment.
At the beginning of the file there are some simple TODOs to improve the code but the big bug is the rendering of the table over multiple pages.
Consider that PDFkit is a low-level library (you can specify the exact position of any element) and you don't have e real concept of flow (like HTML) instead a table is an high-level element that need a flow.
So this is my question: how can we render a high-level element inside a page that use a low-level library?

@volkanongun
Copy link

@giuseppe-santoro how can i use your mixin in a node.js file? can you give a simple example?

@giuseppe-santoro
Copy link

var tableOptions = {
    columns:[
        {
            id: 'description',
            width: 45,
            name: 'Descrizione'
        },
        {
            id: 'quantity',
            width: 10,
            name: 'Q.tà' },
        {
            id: 'unitCost',
            width: 15,
            name: 'Prezzo',
            renderer: currencyRenderer
        },
        {
            id: 'discount',
            width: 10,
            name: 'Sconto',
            renderer: function(item){
                return item && item + ' %' || ''
            }
        },
        {
            id: 'total',
            width: 20,
            name: 'Totale',
            renderer: currencyRenderer
        }
    ],
    y: 250,
    noVerticalLines: true,
    margins: {
        left: 40,
        top: 0,
        right: 40,
        bottom: 20
    },
    padding: {
        left: 10,
        top: 10,
        right: 10,
        bottom: 10
    },
    font: "liberation-serif",
    boldFont: "liberation-serif-bold"
};

doc.table(products, tableOptions);

@volkanongun
Copy link

thanx a lot!

@jsprog
Copy link

jsprog commented Oct 10, 2014

I think that to have full control on rendering the table we have to mimic both html and css in javascript and I'm suggesting this format:

doc.table(tableOptions, function(){
    doc.tableHeaderRow(); // the same as tableRow but may auto repeat header on page break
    doc.tableRow(rowOptions); // internal code should allow the page to break
    doc.tableRow(rowOptions, function(){
        doc.tableCell(cellOptions); 
    });
});

example for table options

var tableOptions = {
    width: 100, // other units should be allowed.
                      // If not specified then (page width - left page margin - right page margin)
    columns: [
        {width: 20}, // 20 %. other possibilities for widthare '20pt', '14px', '10cm', '1in'
        {}, // auto calculated column width if space remained
        {}
    ]
    //other options
};

example for table row options:

var rowOptions = {
    fill: 'red',
    margin: {top: 3},
    padding: 4, // or event {top: 4, bottom: 2, left: 5, right: 5}
    border: {top: 2},
    borderWidth: 4, // or object for different sides
    height: 20
};

cellOptions accept row options plus:
text, textColor, textAlign, textVAlign, font, colSpan, etc...

@lvarayut
Copy link

lvarayut commented Jan 8, 2015

Do you have any progress on this feature?

@kbanman
Copy link

kbanman commented Jan 12, 2015

+1 for a progress report. Also, if more hands are needed, I'm willing to pitch in.

@andresalves
Copy link

There is already some progress on this new feature?
I'm starting from the node js and I'm using your module.
I'm happy with the result and I am waiting to see the tables.

I can help. I used a powerful module in PHP (FPdf). But I want to bet on node js.

@scottmcpherson
Copy link

+1

@gr2m
Copy link

gr2m commented Jan 25, 2015

I'm looking into existing solutions to generate PDFs, and PDFkit looks like the best fit. Table support would be great though. I've also found http://ma.rkusa.st/pdfjs/ which seems to have good table support, maybe we can use that code as a starting point?

@maheshsetti
Copy link

doc.table() function not working, so any suggestions ?

@sanderboom
Copy link

+1

@sanderboom
Copy link

@giuseppe-santoro thanks for your work, looks good, only does not work out of the box anymore with the latest version of PDFKIT. Any plans to update?
@devongovett Any plans to merge? :)

@Jaspreet-Sian
Copy link

Jaspreet-Sian commented May 14, 2017

@devongovett I am facing error doc.table() is not a function, when I try to execute the following piece of code (picked from this discussion).

doc.table([
  ["cell11","cell21","cell31"],
  ["cell12","cell22","cell32"],
  ["cell13","cell23","cell33"]
  ],{
    width:20,
    height:40,
    x:30,
    y:40
});

so any suggestions / hints,that why I am facing it ?

@omairvaiyani
Copy link

@Jaspreet-Sian I just skimmed the discussion, it sounds like the function is still in development and not released in master?

@Jaspreet-Sian
Copy link

@omairvaiyani, Some Users are already using it. My opinion it should be released as per now. If not, can you recommend me any another solution?

@idevN
Copy link

idevN commented Jun 15, 2017

I would like to switch from jspdf to pdfkit. pdfkit is so well made but lack of tables is the only issue for me. Is there any way to use jspdf autotable with pdfkit?

p.s: I dont want pdfmake

@draganmarjanovic
Copy link

Has there been any progress? I'd be willing to help.

@MedinaGitHub
Copy link

MedinaGitHub commented Feb 5, 2018

function example(){    
var doc = new PDFDocument();

var writeStream = fs.createWriteStream('filename.pdf');
doc.pipe(writeStream);
//line to the middle
doc.lineCap('butt')
  .moveTo(270, 90)
  .lineTo(270, 230)
  .stroke()

row(doc, 90);
row(doc, 110);
row(doc, 130);
row(doc, 150);
row(doc, 170);
row(doc, 190);
row(doc, 210);

textInRowFirst(doc, 'Nombre o razón social', 100);
textInRowFirst(doc, 'RUT', 120);
textInRowFirst(doc, 'Dirección', 140);
textInRowFirst(doc, 'Comuna', 160);
textInRowFirst(doc, 'Ciudad', 180);
textInRowFirst(doc, 'Telefono', 200);
textInRowFirst(doc, 'e-mail', 220);
doc.end();

writeStream.on('finish', function () {
  // do stuff with the PDF file
  return res.status(200).json({
    ok: "ok"
  });

});
}

function textInRowFirst(doc, text, heigth) {
  doc.y = heigth;
  doc.x = 30;
  doc.fillColor('black')
  doc.text(text, {
    paragraphGap: 5,
    indent: 5,
    align: 'justify',
    columns: 1,
  });
  return doc
}


function row(doc, heigth) {
  doc.lineJoin('miter')
    .rect(30, heigth, 500, 20)
    .stroke()
  return doc
}

RESULT: https://i.stack.imgur.com/GJMNK.png)[https://i.stack.imgur.com/GJMNK.png]

@LanguageAgnostic
Copy link

Here's another way inspired by the above.

Call

//Simple
createTable(doc, [[1, 2], [1, 2], [1, 2, 3, 4]]);


//Setting the width of the table
createTable(doc, [[1, 2], [1, 2], [1, 2, 3, 4]], 500);

Method

function createTable(doc, data, width = 500) {
  const startY = doc.y,
    startX = doc.x,
    distanceY = 15,
    distanceX = 10;

  doc.fontSize(12);

  let currentY = startY;

  data.forEach(value => {
    let currentX = startX,
      size = value.length;

    let blockSize = width / size;

    value.forEach(text => {
      //Write text
      doc.text(text, currentX + distanceX, currentY);

      //Create rectangles
      doc
        .lineJoin("miter")
        .rect(currentX, currentY, blockSize, distanceY)
        .stroke();

      currentX += blockSize;
    });

    currentY += distanceY;
  });
}

Result

exemple 2

Hope it helps some of you 👍
Feel free to improve/customize it

@ninasaveljeva
Copy link

Here is my example of simple table with one row. Use http://pdfkit.org/demo/browser.html to test.
Main idea is to wrote text on fixed positions and draw table lines around the text.

// create a document and pipe to a blob
var doc = new PDFDocument();
var stream = doc.pipe(blobStream());

var aatext = aaLongText();

let pageWidth = Math.round(doc.page.width - doc.page.margins.left - doc.page.margins.right);

var txt1 = 'Title 1';
var txt2 = 'Title 2';
var txt3 = 'Title 3';
var textSpacer = 10;

//set columns width
let width1 = 0.3*pageWidth;
let width2 = 0.5*pageWidth;
let width3 = 0.2*pageWidth

let y = doc.y;
let x = doc.x;

//table border
let arr = [doc.heightOfString(txt1, {width: width1}), 
        doc.heightOfString(txt2, {width: width2}), 
        doc.heightOfString(txt3, {width: width3})];
var cellHeight = Math.max(...arr) + textSpacer*2; 

doc.lineWidth(0.5);
doc.strokeColor('lightgrey')

doc.lineJoin('miter')
   .rect(x, y, pageWidth, cellHeight)
   .stroke();

//first vertical line
doc.lineCap('butt')
   .moveTo(x + width1 + textSpacer, y)
   .lineTo(x + width1 + textSpacer, y + cellHeight)
   .stroke();

//second vertical line   
doc.lineCap('butt')
   .moveTo(x + width1 + width2 + textSpacer, y)
   .lineTo(x + width1 + width2 + textSpacer, y + cellHeight)
   .stroke();


//table text
y = doc.y;
x = doc.x;
doc.font('Helvetica-Bold', 14);

doc.text(txt1, x + textSpacer, y + textSpacer, {continued: false, width: width1}) 
doc.font('Helvetica', 12);
doc.text(txt2, x + width1 + 2*textSpacer, y + textSpacer, {continued: false, width: width2})
doc.text(txt3, x + width1 + width2 + 2*textSpacer, y + textSpacer, {continued: false, width:width3})

doc.text('', x + textSpacer, y, {continued: false,})
doc.moveDown(3);

//another text here
doc .text('And here is some text...');

// end and display the document in the iframe to the right
doc.end();
stream.on('finish', function() {
  iframe.src = stream.toBlobURL('application/pdf');
});

function aaLongText(){
    return ""
+ "111Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque pellentesque neque quis risus tincidunt feugiat. Praesent eros diam, sodales ut lacus et, aliquet pulvinar orci. Donec suscipit, magna eu egestas accumsan, dolor augue semper elit, a mollis nisi neque nec nibh. Nullam malesuada imperdiet ligula in lacinia. sollicitudin22.\n"
// + "Cras consequat massa sed urna hendrerit, eu cursus diam imperdiet. Fusce nec tellus ante. Praesent vitae pharetra elit. Sed varius ipsum justo, a rhoncus metus blandit eget. Suspendisse ac convallis enim. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Vestibulum eleifend odio risus, ut sagittis turpis interdum vel. Morbi id urna velit. Vestibulum lobortis tincidunt nibh condimentum dapibus. Aenean eget eleifend massa, non semper nisl. Vivamus sed maximus tellus, nec porttitor tortor.\n"
}

@s-kris
Copy link

s-kris commented Jun 18, 2019

I liked @ninasaveljeva 's table format and quickly hacked it to implement multiple rows and added few params by converting it to a function. Someone feel free to optimise it.

Here's the code:

function createTable(doc, rows, fontName, fontSize) {
  doc.font(fontName || 'Helvetica', fontSize || 10);

  const pageWidth = Math.round(doc.page.width - doc.page.margins.left - doc.page.margins.right);
  const textSpacer = 10;

  let { y } = doc;
  const { x } = doc;

  rows.forEach(row => {
    // table border
    const arr = row.map(column => doc.heightOfString(column.text, { width: column.width * pageWidth }));

    const cellHeight = Math.max(...arr) + textSpacer * 2;
    doc.lineWidth(0.3);
    doc.strokeColor('lightgrey');

    doc
      .lineJoin('miter')
      .rect(x, y, pageWidth, cellHeight)
      .stroke();

    let writerPos = x;
    for (let i = 0; i < row.length - 1; i++) {
      writerPos += row[i].width * pageWidth;

      doc
        .lineCap('butt')
        .moveTo(writerPos + textSpacer, y)
        .lineTo(writerPos + textSpacer, y + cellHeight)
        .stroke();
    }

    // table text
    let textWriterPos = x;
    for (let i = 0; i < row.length; i++) {
      doc.text(row[i].text, textWriterPos + textSpacer, y + textSpacer, {
        continued: false,
        width: row[i].width * pageWidth - (textSpacer + 5),
      });
      textWriterPos += row[i].width * pageWidth + (textSpacer - 5);
    }

    y += cellHeight;
  });

  doc.moveDown(2);
  doc.text('', doc.page.margins.left);
}

usage

const testData = [
  [
    {
      text: 'row1 column1',
      width: 0.3,
    },
    {
      text: 'row1 column2',
      width: 0.7,
    },
  ],
  [
    {
      text: 'row2 column1',
      width: 0.3,
    },
    {
      text: 'row2 column2',
      width: 0.7,
    },
  ],
];

  createTable(doc, testData);

@alexandrtovmach
Copy link

@s-kris thanks for your functional example. Probably you would create PR with this function?

@blikblum
Copy link
Member

Creating a table is possible in many ways. None of them is generic enough, so IMO this should not live in pdfkit but in a separated project.

@alexandrtovmach
Copy link

@blikblum Okay, and what do you think about all this discussion and developers who happy to see this feature in pdfkit?

@blikblum
Copy link
Member

In the discussion above saw different proposed API (i stopped to count at 4) for table creation, if we stick with one, will not satisfy other needs.
Also no one comes with a PR accompanied with use cases (from simple needs to complex ones) and tests. If someone does i will happily review

@s-kris
Copy link

s-kris commented Jul 13, 2019

I'll try to create but if you or someone else willing to add this feature ASAP, please go ahead.
Thank you.

@s-kris thanks for your functional example. Probably you would create PR with this function?

@ruk91
Copy link

ruk91 commented Jul 17, 2019

This 'https://www.andronio.me/2017/09/02/pdfkit-tables/' saved my day..

@ruucci
Copy link

ruucci commented Jun 23, 2020

@s-kris is there an easy way to add color to the header cells or some extra styling, if i pass header=true in the data array? I couldn't get it to work

@dellwatson
Copy link

@s-kris thankyou for the function, but sadly it has a bug when the table is too long, and create new page, the table become 1 cell /page

@danielmahon
Copy link

Just ran across this myself, here is a typescript version of Andronio's version:
https://www.andronio.me/2017/09/02/pdfkit-tables/

Class Extension

import PDFDocument from 'pdfkit';

export type PDFTable = {
  headers: string[];
  rows: string[][];
};

export type PDFTableOptions = {
  columnSpacing?: number;
  rowSpacing?: number;
  width?: number;
  prepareHeader?: () => void;
  prepareRow?: (row: string[], index: number) => void;
};

export class PDFDocumentWithTables extends PDFDocument {
  constructor(options: PDFKit.PDFDocumentOptions) {
    super(options);
  }

  table(
    table: PDFTable,
    arg0?: number | PDFTableOptions,
    arg1?: number | PDFTableOptions,
    arg2?: number | PDFTableOptions,
  ) {
    let startX = this.page.margins.left,
      startY = this.y;
    let options = {} as PDFTableOptions;

    if (typeof arg0 === 'number' && typeof arg1 === 'number') {
      startX = arg0;
      startY = arg1;

      if (typeof arg2 === 'object') options = arg2;
    } else if (typeof arg0 === 'object') {
      options = arg0;
    }

    const columnCount = table.headers.length;
    const columnSpacing = options.columnSpacing || 15;
    const rowSpacing = options.rowSpacing || 5;
    const usableWidth =
      options.width ||
      this.page.width - this.page.margins.left - this.page.margins.right;

    const prepareHeader = options.prepareHeader || (() => {});
    const prepareRow = options.prepareRow || (() => {});
    const computeRowHeight = (row: string[]) => {
      let result = 0;

      row.forEach((cell) => {
        const cellHeight = this.heightOfString(cell, {
          width: columnWidth,
          align: 'left',
        });
        result = Math.max(result, cellHeight);
      });

      return result + rowSpacing;
    };

    const columnContainerWidth = usableWidth / columnCount;
    const columnWidth = columnContainerWidth - columnSpacing;
    const maxY = this.page.height - this.page.margins.bottom;

    let rowBottomY = 0;

    this.on('pageAdded', () => {
      startY = this.page.margins.top;
      rowBottomY = 0;
    });

    // Allow the user to override style for headers
    prepareHeader();

    // Check to have enough room for header and first rows
    if (startY + 3 * computeRowHeight(table.headers) > maxY) this.addPage();

    // Print all headers
    table.headers.forEach((header, i) => {
      this.text(header, startX + i * columnContainerWidth, startY, {
        width: columnWidth,
        align: 'left',
      });
    });

    // Refresh the y coordinate of the bottom of the headers row
    rowBottomY = Math.max(startY + computeRowHeight(table.headers), rowBottomY);

    // Separation line between headers and rows
    this.moveTo(startX, rowBottomY - rowSpacing * 0.5)
      .lineTo(startX + usableWidth, rowBottomY - rowSpacing * 0.5)
      .lineWidth(2)
      .stroke();

    table.rows.forEach((row, i) => {
      const rowHeight = computeRowHeight(row);

      // Switch to next page if we cannot go any further because the space is over.
      // For safety, consider 3 rows margin instead of just one
      if (startY + 3 * rowHeight < maxY) startY = rowBottomY + rowSpacing;
      else this.addPage();

      // Allow the user to override style for rows
      prepareRow(row, i);

      // Print all cells of the current row
      row.forEach((cell, i) => {
        this.text(cell, startX + i * columnContainerWidth, startY, {
          width: columnWidth,
          align: 'left',
        });
      });

      // Refresh the y coordinate of the bottom of this row
      rowBottomY = Math.max(startY + rowHeight, rowBottomY);

      // Separation line between rows
      this.moveTo(startX, rowBottomY - rowSpacing * 0.5)
        .lineTo(startX + usableWidth, rowBottomY - rowSpacing * 0.5)
        .lineWidth(1)
        .opacity(0.7)
        .stroke()
        .opacity(1); // Reset opacity after drawing the line
    });

    this.x = startX;
    this.moveDown();

    return this;
  }
}

Usage

    // Data table
    const table0 = {
      headers: ['Word', 'Comment', 'Summary'],
      rows: [
        [
          'Apple',
          'Not this one',
          'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla viverra at ligula gravida ultrices. Fusce vitae pulvinar magna.',
        ],
        [
          'Tire',
          'Smells like funny',
          'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla viverra at ligula gravida ultrices. Fusce vitae pulvinar magna.',
        ],
      ],
    };

    doc.table(table0, {
      prepareHeader: () => doc.font('Helvetica-Bold'),
      prepareRow: (row, i) => doc.font('Helvetica').fontSize(12),
    });

@hinsxd
Copy link

hinsxd commented Jan 17, 2021

@danielmahon Thanks for saving my day!

This was referenced Feb 27, 2021
@priyanka041095
Copy link

Hello,
i have a table with custom css attached..
table1
I'm able to construct the table using the following code,

let table = { width: 180, fontSize: 10, font: 'Courier', rows: [{ height: 10, // color: '#FFFFFF', fillColor: "#0D345A", columns: [{ text: "N°", textOptions: { "fill": "#FFFFFF" // tried "color"/"textColor"/"fillColor": "#FFFFFF" but nothing worked }, width: 20 }, { text: "Description", textOptions: { "fill": "#FFFFFF" }, width: 40 }, { text: "Qté", textOptions: { "fill": "#FFFFFF" }, width: 20 }, { text: "Unité", textOptions: { "fill": "#FFFFFF" }, width: 20 }, { text: "Prix", textOptions: { "fill": "#FFFFFF" }, width: 20 }, { text: "Rabais", textOptions: { "fill": "#FFFFFF" }, width: 20 }, { text: "TVA", textOptions: { "fill": "#FFFFFF" }, width: 20 }, { text: "Total", textOptions: { "fill": "#FFFFFF" }, width: 25 }] }] }; pdf.addTable(table);

However, i'm having an issue with the header/last row - the fillColor is used to change the background color... but what about the color of text? i want to display the text in white color in the blue row.. It's being in black in blue background instead of on white on blue background

please advise the way forward.
many thanks

@RadekKpc
Copy link

RadekKpc commented Sep 7, 2021

Here is modified Andronio's version with possibility to set custom column widths:

class PDFDocumentWithTables extends PDFDocument {
    constructor(options) {
        super(options);
    }

    table(table, arg0, arg1, arg2) {
        let startX = this.page.margins.left, startY = this.y;
        let options = {};

        if ((typeof arg0 === 'number') && (typeof arg1 === 'number')) {
            startX = arg0;
            startY = arg1;

            if (typeof arg2 === 'object')
                options = arg2;
        } else if (typeof arg0 === 'object') {
            options = arg0;
        }

        const columnCount = table.headers.length;
        const columnSpacing = options.columnSpacing || 15;
        const rowSpacing = options.rowSpacing || 5;
        const usableWidth = options.width || (this.page.width - this.page.margins.left - this.page.margins.right);

        const prepareHeader = options.prepareHeader || (() => { });
        const prepareRow = options.prepareRow || (() => { });
        const columnWidthsDistribution = options.columnWidthsDistribution || null;
        const computeRowHeight = (row) => {
            let result = 0;

            row.forEach((cell, i) => {
                const cellHeight = this.heightOfString(cell, {
                    width: columnWidthsDistribution ? (columnWidthsDistribution[i] * usableWidth) - columnSpacing : columnWidth,
                    align: 'left'
                });
                result = Math.max(result, cellHeight);
            });

            return result + rowSpacing;
        };

        const columnContainerWidth = usableWidth / columnCount;
        const columnWidth = columnContainerWidth - columnSpacing;
        const maxY = this.page.height - this.page.margins.bottom;

        let rowBottomY = 0;

        this.on('pageAdded', () => {
            startY = this.page.margins.top;
            rowBottomY = 0;
        });

        // Allow the user to override style for headers
        prepareHeader();

        // Check to have enough room for header and first rows
        if (startY + 3 * computeRowHeight(table.headers) > maxY)
            this.addPage();

        // Print all headers
        table.headers.forEach((header, i) => {
            this.text(header, startX + (columnWidthsDistribution ? (columnWidthsDistribution.filter((e, j) => j < i).reduce((acc, v) => acc + v, 0) * usableWidth) : i * columnContainerWidth), startY, {
                width: columnWidthsDistribution ? (columnWidthsDistribution[i] * usableWidth) - columnSpacing : columnWidth,
                align: 'left'
            });
        });

        // Refresh the y coordinate of the bottom of the headers row
        rowBottomY = Math.max(startY + computeRowHeight(table.headers), rowBottomY);

        // Separation line between headers and rows
        this.moveTo(startX, rowBottomY - rowSpacing * 0.5)
            .lineTo(startX + usableWidth, rowBottomY - rowSpacing * 0.5)
            .lineWidth(2)
            .stroke();

        table.rows.forEach((row, i) => {
            const rowHeight = computeRowHeight(row);

            // Switch to next page if we cannot go any further because the space is over.
            // For safety, consider 3 rows margin instead of just one
            if (startY + 3 * rowHeight < maxY)
                startY = rowBottomY + rowSpacing;
            else
                this.addPage();

            // Allow the user to override style for rows
            prepareRow(row, i);

            // Print all cells of the current row
            row.forEach((cell, i) => {
                this.text(cell, startX + (columnWidthsDistribution ? (columnWidthsDistribution.filter((e, j) => j < i).reduce((acc, v) => acc + v, 0) * usableWidth) : i * columnContainerWidth), startY, {
                    width: columnWidthsDistribution ? (columnWidthsDistribution[i] * usableWidth) - columnSpacing : columnWidth,
                    align: 'left'
                });
            });

            // Refresh the y coordinate of the bottom of this row
            rowBottomY = Math.max(startY + rowHeight, rowBottomY);

            // Separation line between rows
            this.moveTo(startX, rowBottomY - rowSpacing * 0.5)
                .lineTo(startX + usableWidth, rowBottomY - rowSpacing * 0.5)
                .lineWidth(1)
                .opacity(0.7)
                .stroke()
                .opacity(1); // Reset opacity after drawing the line
        });

        this.x = startX;
        this.moveDown();

        return this;
    }
}

To set column widths you need to pass the 'columnWidthsDistribution' parameter as following:

doc.table({
  headers: [
    "col1",
    "col2",
    "col3",
    "col4",
    "col5",
  ],
  rows,
  {
    columnsWidthDistribution: [0.1, 0.1, 0.5, 0.15, 0.15],
  });

The sum of the 'columnWidthsDistribution' has to be 1. Each number means how much of the space the column will contain (0.1 => 10%, 0.5 => 50% etc).

@YovanggaAnandhika
Copy link

+1 this is awesome library project. any updates ? i so need table function

@MowglyOrizon
Copy link

Loving this lib - this is the only feature I can see that it's missing. Keep up the great work @Owners
For my Table work around I used node-html-to-image.

I wrote the HTML table - exported it as an image and then added the image to the pdf -> Hope this helps others

@shierro
Copy link

shierro commented Jun 6, 2023

you might want to consider using jsPDF :)

https://github.com/parallax/jsPDF
https://rawgit.com/MrRio/jsPDF/master/docs/module-cell.html#~table

Switching there bacause of this issue 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests