Open faridnsh opened 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()
Is there actual PDF markup to render a table, or is it just lots of lines?
+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
});
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!
+1
Looks great! Can't wait to try it.
+1 This would be great.
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?
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.
+1 What is the status for this?
it works pretty well, but it contains some bugs so it is not suitable for production use
+1000
Please add this to pdfkit! I need to insert a table in the pdf and i have no ideia how
@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.
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?
@giuseppe-santoro how can i use your mixin in a node.js file? can you give a simple example?
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);
thanx a lot!
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...
Do you have any progress on this feature?
+1 for a progress report. Also, if more hands are needed, I'm willing to pitch in.
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.
+1
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?
doc.table() function not working, so any suggestions ?
+1
@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? :)
+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!
@tom2strobl that's very interesting, is it open source?
@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.
+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.
To generate tables in pdf, there is also pdfmake which is build on top of pdfkit.
+1
+1
+1
+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.
@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
@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 ?
@Jaspreet-Sian I just skimmed the discussion, it sounds like the function is still in development and not released in master?
@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?
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
Has there been any progress? I'd be willing to help.
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]
Here's another way inspired by the above.
//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);
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;
});
}
Hope it helps some of you :+1: Feel free to improve/customize it
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"
}
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);
@s-kris thanks for your functional example. Probably you would create PR with this function?
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.
@blikblum Okay, and what do you think about all this discussion and developers who happy to see this feature in pdfkit?
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
I have an idea for it. We can pass a two-dimensional array of texts, to a method with presentation options like the following:
Then in the module we make rectangles and texts for each cell.