foliojs / pdfkit

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

Multi column layout with auto height #930

Open raesche opened 5 years ago

raesche commented 5 years ago

Question

Hi I need a multi column layout with variable amount of text. The text should be evenly distributed on all columns and the height of the columns should automatically grow when more text is entered.

Description

Setting the text properties columns=2 and height=200 creates an area with the height 200 where a two column layout is inserted. If the text gets to long it will be truncated

If the text is short (20 lines e.g.) it will be inserted on the first column only:

xxxxxxxxxx xxxxxxxxxx xxxxx

If the text gets longer (100 lines e.g.) it fills the first column completely and then continues on the second column:

xxxxxxxxxx xxxxxxxxxx
xxxxxxxxxx xxxxxxx
xxxxxxxxxx

If the text gets even longer, it fills both columns completely and then gets truncated:

xxxxxxxxxx xxxxxxxxxx
xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx

I'm looking for a solution where no height needs to be specified and where the text is always distributed on every column:

Short text:

The text fills every column as much as possible

xxxxxxxxxx xxxxxx

Longer text:

The text fills every column as much as possible, adding more lines

xxxxxxxxxx xxxxxxxxxx
xxxxxxxxxx xxxxxxxxxx
xxxxxxx

Very long text:

The number of lines is dynamically increased to make space for the whole text. If required a new page is inserted and the colums are continued. Text is never truncated

xxxxxxxxxx xxxxxxxxxx
xxxxxxxxxx xxxxxxxxxx
xxxxxxxxxx xxxxxxxxxx
xxxxxxxxxx xxxxxxxxxx
xxxxxxxxxx xxxxxxxxxx
xxxxxxxxxx xxxxxxxxxx
xxxxxxxxxx xxxxxxxxxx
xxxxxxxxxx xxxxxxx

Do you have an idea how to make this?

Thanks & best regards Rashid

Your environment

danthegoodman commented 5 years ago

I've done something similar in my project, not with columns, but needing my text measurement before it was written. To do this, I have two PDFDocuments, a real one (piped to a stream I use) and a fake one (piped to a throwaway stream)

For you, you can draw your text to the fake one in one column, and measure how tall the output will be. Then, use that height to determine how tall the block should be in your real document.

Here's an example you can put into the browser demo: http://pdfkit.org/demo/browser.html

var doc = new PDFDocument();
var stream = doc.pipe(blobStream());

var fake = new PDFDocument({size:[8.5*72,999999]});
fake.pipe(blobStream());

var text = reallyLongText();

var columnGap = 36;
var columns = 2;
var fullWidth = doc.page.width - doc.page.margins.right;
var columnWidth = (fullWidth - (columnGap*(columns-1))) / columns;
fake.y = 0;
fake.text(text, {width: columnWidth})
var fullHeight = fake.y;

doc.y = 0;
doc.text(text, {width: fullWidth, height: fullHeight/2, columns: columns, columnGap: columnGap});
doc.end();
fake.end();

stream.on('finish', function() {
  iframe.src = stream.toBlobURL('application/pdf');
});

function reallyLongText(){
    return ""
+ "Lorem 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. Maecenas et mattis leo, vehicula convallis odio. Etiam quis nisl ante. Aliquam augue nibh, viverra eget accumsan egestas, aliquet et ex. Maecenas vehicula nulla mi, id venenatis massa posuere eu. Maecenas facilisis risus non ipsum pretium tristique. Donec ut lorem et tortor vestibulum porttitor. Morbi volutpat orci lectus, quis dignissim diam feugiat nec. Integer vitae lacus sed risus mattis rhoncus. Proin dui orci, sagittis nec arcu eget, ultrices aliquet nisi. Donec feugiat vel tellus ut aliquam. Vivamus quis auctor urna. Quisque in felis nec dui rutrum sollicitudin.\n"
+ "Pellentesque convallis sed ante at tristique. Phasellus egestas diam magna, in efficitur dolor ultrices ut. Sed a augue id felis lacinia tempor et quis lectus. In porttitor nisl ultricies mi efficitur, vitae scelerisque urna laoreet. Duis id hendrerit nibh, et convallis elit. Vivamus sit amet lorem odio. Nulla massa dolor, hendrerit nec odio non, feugiat ornare dolor. Aenean viverra commodo augue, at facilisis augue elementum et.\n"
+ "Aliquam erat volutpat. Duis vel sem tempor, consectetur enim at, tincidunt velit. Sed aliquet ex ut nunc hendrerit, at auctor mauris cursus. In malesuada id velit a sollicitudin. In volutpat, metus nec aliquet volutpat, tortor ipsum luctus urna, non feugiat orci erat eu nulla. Sed ultrices sapien nec lorem dictum, nec facilisis lectus sagittis. Aliquam ut justo ornare, rhoncus turpis vel, aliquet orci. Integer in quam ac quam iaculis feugiat ac ac ante. Curabitur imperdiet, metus ut condimentum lacinia, felis elit interdum diam, sed sagittis elit purus ut quam. Suspendisse potenti. Donec at arcu urna. Proin eu mollis ex, et ornare sapien. Nunc interdum congue mi egestas volutpat. Cras eget placerat tortor. Pellentesque at felis velit.\n"
+ "Curabitur ornare elementum pulvinar. Morbi molestie varius lorem, et lacinia odio malesuada quis. Cras tincidunt neque vel ante luctus, a mattis libero laoreet. Ut aliquet enim eu nulla condimentum scelerisque. Cras vulputate ligula magna, non consectetur neque sagittis nec. Morbi pellentesque tempor est vitae volutpat. Praesent est ex, dapibus in eros vitae, dictum varius est. Etiam ultricies varius dignissim. Etiam justo leo, dapibus vitae volutpat ut, rutrum sit amet orci. Donec eu metus diam.\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"
//+ "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"
}

If you comment and uncomment that last line in the reallyLongText, you'll see the height adjust automatically.

modemmute commented 5 years ago

Could you also use heightOfString instead of creating a fake doc, or does that not accomplish what you're trying to do?

var doc = new PDFDocument();
var stream = doc.pipe(blobStream());

var text = reallyLongText();

var columnGap = 36;
var columns = 2;
var fullWidth = doc.page.width - doc.page.margins.right;
var columnWidth = (fullWidth - (columnGap*(columns-1))) / columns;

var fullHeight = doc.heightOfString(text, {width: columnWidth}) // <-------------------

doc.y = 0;
doc.text(text, {width: fullWidth, height: fullHeight/2, columns: columns, columnGap: columnGap});
doc.end();

stream.on('finish', function() {
  iframe.src = stream.toBlobURL('application/pdf');
});

function reallyLongText(){
    return ""
+ "Lorem 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. Maecenas et mattis leo, vehicula convallis odio. Etiam quis nisl ante. Aliquam augue nibh, viverra eget accumsan egestas, aliquet et ex. Maecenas vehicula nulla mi, id venenatis massa posuere eu. Maecenas facilisis risus non ipsum pretium tristique. Donec ut lorem et tortor vestibulum porttitor. Morbi volutpat orci lectus, quis dignissim diam feugiat nec. Integer vitae lacus sed risus mattis rhoncus. Proin dui orci, sagittis nec arcu eget, ultrices aliquet nisi. Donec feugiat vel tellus ut aliquam. Vivamus quis auctor urna. Quisque in felis nec dui rutrum sollicitudin.\n"
+ "Pellentesque convallis sed ante at tristique. Phasellus egestas diam magna, in efficitur dolor ultrices ut. Sed a augue id felis lacinia tempor et quis lectus. In porttitor nisl ultricies mi efficitur, vitae scelerisque urna laoreet. Duis id hendrerit nibh, et convallis elit. Vivamus sit amet lorem odio. Nulla massa dolor, hendrerit nec odio non, feugiat ornare dolor. Aenean viverra commodo augue, at facilisis augue elementum et.\n"
+ "Aliquam erat volutpat. Duis vel sem tempor, consectetur enim at, tincidunt velit. Sed aliquet ex ut nunc hendrerit, at auctor mauris cursus. In malesuada id velit a sollicitudin. In volutpat, metus nec aliquet volutpat, tortor ipsum luctus urna, non feugiat orci erat eu nulla. Sed ultrices sapien nec lorem dictum, nec facilisis lectus sagittis. Aliquam ut justo ornare, rhoncus turpis vel, aliquet orci. Integer in quam ac quam iaculis feugiat ac ac ante. Curabitur imperdiet, metus ut condimentum lacinia, felis elit interdum diam, sed sagittis elit purus ut quam. Suspendisse potenti. Donec at arcu urna. Proin eu mollis ex, et ornare sapien. Nunc interdum congue mi egestas volutpat. Cras eget placerat tortor. Pellentesque at felis velit.\n"
+ "Curabitur ornare elementum pulvinar. Morbi molestie varius lorem, et lacinia odio malesuada quis. Cras tincidunt neque vel ante luctus, a mattis libero laoreet. Ut aliquet enim eu nulla condimentum scelerisque. Cras vulputate ligula magna, non consectetur neque sagittis nec. Morbi pellentesque tempor est vitae volutpat. Praesent est ex, dapibus in eros vitae, dictum varius est. Etiam ultricies varius dignissim. Etiam justo leo, dapibus vitae volutpat ut, rutrum sit amet orci. Donec eu metus diam.\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"
// + "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"
}
raesche commented 5 years ago

Thanks! Both solutions from danthegoodman and modemmute worked for me

However I still need to find a way how to continue the text on a new page once it gets to long, but extending your code is now straight forward.

modemmute commented 5 years ago

I'm working on something like this right now. Essentially, I detect if the data is longer than the remaining space on the page, then .split() the data into two arrays, one to fit in the remaining space, and the second to start on the next page.