foliojs / pdfkit

A JavaScript PDF generation library for Node and the browser
http://pdfkit.org/
MIT License
9.91k stars 1.15k forks source link

tables #29

Open faridnsh opened 13 years ago

faridnsh commented 13 years ago

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 commented 13 years ago

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 commented 12 years ago

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

ForbesLindesay commented 11 years ago

+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 commented 11 years ago

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 commented 11 years ago

+1

mrjonny2 commented 11 years ago

Looks great! Can't wait to try it.

johntom commented 11 years ago

+1 This would be great.

alexvdev commented 10 years ago

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 commented 10 years ago

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 commented 10 years ago

+1 What is the status for this?

giuseppe-santoro commented 10 years ago

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

mscdex commented 10 years ago

+1000

diogoAntunes commented 10 years ago

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

aszharite commented 10 years ago

@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 commented 10 years ago

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 commented 10 years ago

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

giuseppe-santoro commented 10 years ago
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 commented 10 years ago

thanx a lot!

jsprog commented 10 years ago

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 commented 9 years ago

Do you have any progress on this feature?

kbanman commented 9 years ago

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

andresalves commented 9 years ago

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 commented 9 years ago

+1

gr2m commented 9 years ago

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 commented 9 years ago

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

sanderboom commented 9 years ago

+1

sanderboom commented 9 years ago

@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? :)

tom2strobl commented 9 years ago

+1 Currently in the process of writing a markdown-to-pdfkit parser and as 50% of the PDFs I encounter contain tables - would be very handy!

LinusU commented 9 years ago

@tom2strobl that's very interesting, is it open source?

tom2strobl commented 9 years ago

@LinusU Well it's quick'n'dirty, currently absolutely not generic and tailored specifically to a certain case (with fixed sizes etc.), but simple headings, lists and paragraphs as an impulse:

/**
 * Parses an input String with the help of the "marked"-package and writes the corresponding elements to the supplied document
 * @param doc - the PDFDocument to write to
 * @param input - the input String
 * @returns {object} - the document for chaining
 */
PDFDocument.parseMarkdown = function (doc, input){
  // Array of markdown parts as objects
  var markdown = marked.lexer(input);
  // loop through the Array, going over each markdown-part individually
  markdown.forEach(function(part, index){
    if(part.type === 'paragraph'){
      doc.font('Normal').fontSize(8).text(part.text, {lineGap: 1.3}).moveDown();
    }else if(part.type === 'heading'){
      var fontsize = 8;
      // depth defines if it's a h1, h2, h3 etc.
      switch(part.depth){
        case 1: fontsize = 12; break;
        case 2: fontsize = 10; break;
        default: fontsize = 8;
      }
      doc.fontSize(4).moveDown().font('Heading2').fontSize(fontsize).text(part.text).fontSize(8).moveDown();
    }else if(part.type === 'text' && markdown[index-1].type === 'list_item_start'){
      doc.font('Normal').fontSize(8).list([part.text], {lineGap: 1.3});
    }else if(part.type === 'list_end'){
      doc.fontSize(8).moveDown();
    }
  });
  return doc;
};

It's obviously using the (npm) marked library as a lexer, which is the fastest implementation I'm aware of. Not sure if I find time to make it more generic and fully markdown compatible.

Chris0lsen commented 8 years ago

+1 over here too :) Is that tables.coffee file still the closest thing to a native tables API? Just started using pdfkit today and came across this thread.

TDu commented 8 years ago

To generate tables in pdf, there is also pdfmake which is build on top of pdfkit.

danielpacak commented 8 years ago

+1

manuelphdev commented 8 years ago

+1

anderson-tyler commented 8 years ago

+1

RPGillespie6 commented 8 years ago

+1 This is the one thing pdfMake has that pdfkit doesn't have, and it's very frustrating because pdfmake seems to be a dead project now.

awerlang commented 7 years ago

@RPGillespie6 it's interesting that pdfmake has pulse again https://github.com/bpampuch/pdfmake/graphs/contributors?from=2016-11-15&to=2017-03-04&type=c

Jaspreet-Sian commented 7 years ago

@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 commented 7 years ago

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

Jaspreet-Sian commented 7 years ago

@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 commented 7 years ago

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 commented 7 years ago

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

MedinaGitHub commented 6 years ago
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]

ghost commented 6 years ago

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 :+1: Feel free to improve/customize it

ninasaveljeva commented 5 years ago

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 commented 5 years ago

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 commented 5 years ago

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

blikblum commented 5 years ago

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 commented 5 years ago

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

blikblum commented 5 years ago

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