Reputation: 740
I have long content (multiple content with imgaes, lists, divs, text, etc...). I want to show this content to user as virtual PDF pages. I dont want to generate PDF, just show this HTML content as pages (defined by width/height) with same header/footer. It should look like in image bellow, as you can see on first page, I want to split that text and show in next page:
I´m working this app on React. I dont know what will be in this content, every render will be different (with different content based on user activity).
Do you have any suggestions how to do this? (CSS solutions, or JS, or I dont know maybe some React lib ...)
Upvotes: 3
Views: 5388
Reputation: 1
As an addition to the other answer by remix23, I was trying to do something along the same lines by formatting an html document before turning into pdf to print out on paper. I found CSS formatting to be my answer https://www.smashingmagazine.com/2015/01/designing-for-print-with-css/
I am not sure if this is the solution you are looking for but using
<style>
@media print {
div { break-inside: avoid; }
}
</style>
was all I needed before I could open the html document in my browser and use its inbuilt ctrl-P to see a "virtual PDF". This method also let me modify margins and specify width/height of the paper.
Upvotes: 0
Reputation: 3019
Here is a naive but working implementation.
The idea is to mount the html into an offscreen div which has the same dimensions as the pages we're trying to render.
Then iterate over the elements (ie children from parsed html) of this offscreen div and query the dom using getBoundingClientRect to find the first overflowing element.
We then remove all elements before the overflowing one from the offscreen div and cache them in an array.
Start over for a new chunk until there is no more elements in the offscreen div.
Adapting this to React is just a matter of using dangerouslySetInnerHTML with the html content of each page.
(the display flex is just there to force flowing of elements, but any layout would do provided it's the same in offscreenDiv and in page)
function generateRandomContent() {
var alph = "abcdefghijklmnopqrstuvwxyz";
var content = "";
// we will generate 100 random elements displaying their index to keep track of what's happening
for (var i = 0; i < 100; i++) {
var type = parseInt(Math.random() * 2, 10);
switch (type) {
case 0: // text, generates and random p block
content = content + "<p>" + i + " ";
var numWords = 10 + parseInt(Math.random() * 50, 10);
for (var j = 0; j < numWords; j++) {
var numLetters = 2 + parseInt(Math.random() * 15, 10);
if (j > 0) {
content = content + " ";
}
for (var k = 0; k < numLetters; k++) {
content = content + alph[parseInt(Math.random() * 26, 10)];
}
}
content = content + "</p>";
break;
case 1: // colored div, generates a div of random size and color
var width = 30 + parseInt(Math.random() * 20, 10) * 10;
var height = 30 + parseInt(Math.random() * 20, 10) * 10;
var color = "rgb(" + parseInt(Math.random() * 255, 10) + ", " + parseInt(Math.random() * 255, 10) + ", " + parseInt(Math.random() * 255, 10) + ")";
content = content + '<div style="width: ' + width + 'px; height: ' + height + 'px; background-color: ' + color + '">' + i + '</div>';
break;
}
}
return content;
}
function getNodeChunks(htmlDocument) {
var offscreenDiv = document.createElement('div');
offscreenDiv.className = 'page';
offscreenDiv.style.position = 'absolute';
offscreenDiv.style.top = '-3000px';
offscreenDiv.innerHTML = htmlDocument;
offscreenDiv.display = 'flex';
offscreenDiv.flexWrap = 'wrap';
document.body.appendChild(offscreenDiv);
offscreenRect = offscreenDiv.getBoundingClientRect();
// console.log('offscreenRect:', offscreenRect);
var chunks = [];
var currentChunk = []
for (var i = 0; i < offscreenDiv.children.length; i++) {
var current = offscreenDiv.children[i];
var currentRect = current.getBoundingClientRect();
currentChunk.push(current);
if (currentRect.bottom > (offscreenRect.bottom)) {
// current element is overflowing offscreenDiv, remove it from current chunk
currentChunk.pop();
// remove all elements in currentChunk from offscreenDiv
currentChunk.forEach(elem => elem.remove());
// since children were removed from offscreenDiv, adjust i to start back at current eleme on next iteration
i -= currentChunk.length;
// push current completed chunk to the resulting chunklist
chunks.push(currentChunk);
// initialise new current chunk
currentChunk = [current];
offscreenRect = offscreenDiv.getBoundingClientRect();
}
}
// currentChunk may not be empty but we need the last elements
if (currentChunk.length > 0) {
currentChunk.forEach(elem => elem.remove());
chunks.push(currentChunk);
}
// offscreenDiv is not needed anymore
offscreenDiv.remove();
return chunks;
}
function appendChunksToPages(chunks) {
var container = document.getElementsByClassName('root_container')[0];
chunks.forEach((chunk, index) => {
// ex of a page header
var header = document.createElement('div');
header.innerHTML = '<h4 style="margin: 5px">Page ' + (index + 1) + '</h4>';
container.appendChild(header);
var page = document.createElement('div');
page.className = 'page';
chunk.forEach(elem => page.appendChild(elem));
container.appendChild(page);
});
}
// generateRandom content outputs raw html, getNodeChunks returns
// an array of array of elements, the first dimension is the set of
// pages, the second dimension is the set of elements in each page
// finally appendChunks to pages generates a page for each chunk
// and adds this page to the root container
appendChunksToPages(getNodeChunks(generateRandomContent()));
.page {
border: 1px solid;
display: flex;
flex-wrap: wrap;
height: 700px;
width: 50%;
margin-bottom: 20px;
}
<div class="root_container"></div>
Upvotes: 2