jsPDF muliple pages with div height - unable to determine the height - angularjs

I am trying to generate a pdf from html divs with dynamic height and width. Below is the code.
let pages = this.rootDataContainer.nativeElement.getElementsByClassName('pdfpage');
for (let i = 0; i < pages.length; i++) {
this.pdfDoc.addHTML(pages[i], 0, 0, options, () => {
let pxHeight = pages[i].offsetHeight / scaleFactor;
this.pdfDoc.addPage(0, pxHeight);
this.counter = this.counter - 1;
});
}
There are couple of issues I am facing.
As addHTML is async, pages are added to pdf in random way.
height of the pdf page is either getting more or less height.
is there any way to set pdf size and sync the order of pages.

To sync I would use a recursive approach as in:
EDITED:
var pages = document.getElementsByClassName("pdfpage");
var pdfDoc = new jsPDF('p', 'pt', [0, 0]);
pdfDoc.deletePage(1);
pdfDoc.addPage(pages[0].clientWidth, pages[0].clientHeight);
var addDivToPdf = function(pageNr) {
pdfDoc.addHTML(pages[pageNr], 0, 0, {background:"#FFF"}, () => {
if (++pageNr < pages.length) {
//New added DIV dimensions here
pdfDoc.addPage(pages[pageNr].clientWidth, pages[pageNr].clientHeight);
addDivToPdf(pageNr);
} else {
pdfDoc.save("sample-file.pdf");
}
});
};
Notice I haven't use a for loop. This way the next page is added only when the previous is complete.
For the height, I'm not sure, scaleFactor must have the wrong units. It is not really clear if you want all pages to have the same size or you want different sizes to match the DIV height.
UPDATE: To control the widht and height according with the DIVs sizes, I have indicated 'pt' in the pdfDoc constructor:
var pdfDoc = new jsPDF('p', 'pt', [0, 0]);
However the first page appears to have always an standard size, so I have deleted it and added again with the desired size before adding the first DIV. The other pages can follow the normal flow:
pdfDoc.deletePage(1);
pdfDoc.addPage(pages[0].clientWidth, pages[0].clientHeight);
The CodePen is updated:
https://codepen.io/zameb/pen/BdbEVr

Related

InvalidStateError: CanvasRenderingContext2D.createPattern: Passed-in canvas has height 0 (jsPDF, html2canvas, React)

I'm getting this error while I try to convert certain part of my page into a PDF (the page is like a group of charts and all of them are surrounded by a container called custom-view)
This is how my function looks like:
export function handleDownloadPdf() {
const element = document.getElementsByClassName("custom-view")[3];
html2canvas(element).then(canvas => {
const imgData = canvas.toDataURL("image/png");
const pdf = new jsPDF({
unit: "px",
format: [500, canvas.height],
});
pdf.addImage(imgData, "JPEG", 0, 0);
pdf.save("download.pdf");
});
}
I'm exporting this function from a component where all of my functions are to another component where the button to start the conversion exist. The reason why I put this: const element = document.getElementsByClassName("custom-view")[3]; as a index [3] is because I have other elements with custom-view class. The height of this custom-view container is dynamic, it gets changed based on the amount of charts there are in a custom view part.
The error I get is this one:
The browser I use is: Mozilla Firefox.

Why do I get both positions x and y to be 0 in the response of tensorflow.js posenet?

Initialization of posenet
const net = await posenet.load();
const pose = await net.estimateSinglePose(videoElement, {
flipHorizontal: false
});
Output
part: "leftEye"
position: {x: 0, y: 0}
score: 0.9931495785713196
My question is I always get position 0,0 for every body parts even though the score is high.
I figured out the solution. Make sure you add width and height to source element (image or video). Notice that if you add width and height from css it will not work, you need to add it programmatically.
video = document.getElementById("video");
//must add the following
video.width = 500;
video.height = 500;

how to fix this Error Cannot use the same canvas during multiple render() operations

I am using canvas in react and rendering pdf's as images using the canvas.
Now, when I get new data i.e another pdf get's added, then
again have to use the canvases for that.
I am not sure how to fix this error or how to remove the canvas or even clear the canvas before using it again.
Here's the relevant code:
pdfLoop = (item,index) => {
var that = this;
PDFJS.getDocument(item).then(function getPdfHelloWorld(pdf) {
//
// Fetch the first page
console.log('url is : ',item);
pdf.getPage(1).then(function getPageHelloWorld(page) {
var scale = 0.5;
var viewport = page.getViewport(scale);
let cref = 'canvas'+index;
let imgref ='img'+index;
console.log('cref no : ',cref);
console.log('img no : ',imgref);
// Prepare canvas using PDF page dimensions
//
var canvas = that.canvasRefs[cref];
//let imagez = that.imageRefs[imgref];
var context = canvas.getContext('2d');
context.globalcompositeoperation = 'source-over';
// context.fillStyle = "#fff";
//draw on entire canvas
//context.fillRect( 0, 0, canvas.width, canvas.height );
canvas.height = viewport.height;
canvas.width = viewport.width;
//imagez.src = canvas.toDataURL("image/png");
//
// Render PDF page into canvas context
//
//page.render({canvasContext: context, viewport: viewport});
var task = page.render({canvasContext: context, viewport: viewport})
task.promise.then(function(){
//console.log(canvas.toDataURL('image/png'));
let imgItem = {imgref:canvas.toDataURL('image/png'),page:index+1,rotate:0}
let newState = that.state.imgsrc;
newState[index] = imgItem;
//let newState = that.state.imgsrc.concat(imgItem);
that.setState({
imgsrc:newState
});
//imagez.src = canvas.toDataURL('image/png')
});
});
});
}
In case someone stumbles across this, the error message states the following Use different canvas or ensure previous operations were cancelled or completed.
When getting the document, if there already is a document, it has to be destroyed. I.e:
PDFJS.getDocument({ url: pdf_url }).promise
.then((pdf_doc) => {
if (this.pdf_doc) {
this.pdf_doc.destroy();
}
this.pdf_doc = pdf_doc;
this.total_pages = this.pdf_doc.numPages;
})
I have no idea if this is a good solution, but at least it worked for me.
I've had the same exact problem as you, but my solution was a bit different than the answers previously posted.
For me the problem was the fact that I was unnecessarily re-rendering with a state change. Check if you aren't re-rendering the component without properly clearing the canvas, or if you even need to re-render at all (in my case I didn't).
Hopefully this could help
In order to avoid this situation, put your canvas object in a div object as is shown below:
<div id="div_canvas">
<canvas id="cnv"></canvas>
</div>
Then, before to call pdf.js functions, remove the "div_canvas" content and recreate it:
$("#div_canvas").html("");
$("#div_canvas").html("<canvas id='cnv'></canvas>");
I had exactly the same issue when working with PDF.js, the solution to this is,
You will have to clear your context after the render completes.
if (context) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
}
This will make sure that the context is cleared and ready for the next render and the error disappears.
this worked for me.
In some cases, this error will be displayed when the pdf has action buttons such as next/previous or scaling.
In this cases, often you have a function for rendering pdf page such as:
renderPage(num) {
// Using promise to fetch the page
pdfDoc.getPage(num).then(function (page) {
const viewport = page.getViewport({scale: scale});
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
const renderContext = {
canvasContext: ctx,
viewport: viewport
};
page.render(renderContext);
});
}
To fix the error just perform following changes:
Add two variable for controlling conflict and cache waiting page number:
pageRendering = false; // Check conflict
pageNumPending = null; // Cache waiting page number
Use these variables as follow:
renderPage(num) {
if (this.pageRendering) { // Check if other page is rendering
this.pageNumPending = num; // Cache waited page number until previous page rendering completed
} else {
this.pageRendering = true;
// Using promise to fetch the page
pdfDoc.getPage(num).then(function (page) {
const viewport = page.getViewport({scale: scale});
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
const renderContext = {
canvasContext: ctx,
viewport: viewport
};
const renderTask = page.render(renderContext);
// Wait for rendering to finish
renderTask.promise.then(function () {
this.pageRendering = false;
if (pageNumPending !== null) {
// Waited page must be rendered
this.renderPage(pageNumPending);
// Must be set to null to prevent infinite loop
this.pageNumPending = null;
}
});
});
One possible cause: React Strict mode renders twice

Responsive sticky sidebar, need to update width

I've got a responsive sticky sidebar started here: http://codepen.io/cmegown/pen/fjqzH. I've got the sticky part down, and it's responsive in relation to the width of the original viewport width. However, if you scroll down and then change the viewport size you'll see that the width of the sidebar does not change.
I know I need to update the sidebarWidth variable, but I'm sure exact how/where to do that.
Any help is appreciated, thanks!
EDIT:
This one kind of got left behind amid some other projects, but I'm back to finish this. I got a little further, but still can't seem to figure out how to update the sidebar width if the user scrolls down the page then expands their browser (or rotates their device). I have some commented code in the JS panel where I left off.
Just put the width calculation in your scroll function.
$(function () {
// cache selectors
var wrapper = $('.wrapper');
var sidebar = $('.sidebar');
// get some maths
var sidebarTop = sidebar.offset().top;
var sidebarOffset = 25; // is .wrapper padding
// sticky logic
$(window).on('scroll', function () {
var windowTop = $(window).scrollTop();
var sidebarWidth = Math.round(wrapper.width() * 0.3); // is .sidebar width
if (sidebarTop < (windowTop + sidebarOffset)) {
sidebar.css({
position: 'fixed',
top: sidebarOffset,
width: sidebarWidth
})
} else {
sidebar.css({
position: 'static',
width: '30%' // original CSS value
})
}
})
})
There's a little bit of overhead there, since it has to calculate the width every time you scroll. The alternative would be to put it into a $(window).resize() function so it figures out the width when you resize the window.

angularjs directive tutorials

I am new to angularjs and I would like to understand what the directives do but I can't find a tutorial with different example by complexity and I was curios if I could move the following code in a directive.
// hide the url bar
var page = document.getElementById('page'),
ua = navigator.userAgent,
iphone = ~ua.indexOf('iPhone') || ~ua.indexOf('iPod'),
ipad = ~ua.indexOf('iPad'),
ios = iphone || ipad,
// Detect if this is running as a fullscreen app from the homescreen
fullscreen = window.navigator.standalone,
android = ~ua.indexOf('Android'),
lastWidth = 0;
if (android) {
// Android's browser adds the scroll position to the innerHeight.
// Thus, once we are scrolled, the page height value needs to be corrected in case the page is loaded
// when already scrolled down. The pageYOffset is of no use, since it always
// returns 0 while the address bar is displayed.
window.onscroll = function () {
page.style.height = window.innerHeight + 'px'
}
}
var setupScroll = window.onload = function () {
// Start out by adding the height of the location bar to the width, so that
// we can scroll past it
if (ios) {
// iOS reliably returns the innerWindow size for documentElement.clientHeight
// but window.innerHeight is sometimes the wrong value after rotating
// the orientation
var height = document.documentElement.clientHeight;
// Only add extra padding to the height on iphone / ipod, since the ipad
// browser doesn't scroll off the location bar.
if (iphone && !fullscreen) height += 60;
page.style.height = height + 'px';
} else if (android) {
// The stock Android browser has a location bar height of 56 pixels, but
// this very likely could be broken in other Android browsers.
page.style.height = (window.innerHeight + 56) + 'px'
}
// Scroll after a timeout, since iOS will scroll to the top of the page
// after it fires the onload event
setTimeout(scrollTo, 0, 0, 1);
};
(window.onresize = function () {
var pageWidth = page.offsetWidth;
// Android doesn't support orientation change, so check for when the width
// changes to figure out when the orientation changes
if (lastWidth == pageWidth) return;
lastWidth = pageWidth;
setupScroll();
})();
I wrote a blog entry not too long ago about the basics of directives if you're interested in that.
As far as converting what you have there into a directive, it's not too crazy.
All you would do is use the code you already have, but inject $window instead of using window. (Mostly for testing purposes). I also added a check to make sure it didn't get applied twice.
So it would look a little something like this:
app.directive('windowResizeThingy', function($window) {
return {
restrict: 'A',
link: function(scope, elem, attr) {
// make sure this doesn't get applied twice.
if($window.windowResizeThingyApplied) return;
$window.windowResizeThingyApplied = true;
// hide the url bar
var page = elem[0],
ua = $window.navigator.userAgent,
iphone = ~ua.indexOf('iPhone') || ~ua.indexOf('iPod'),
ipad = ~ua.indexOf('iPad'),
ios = iphone || ipad,
// Detect if this is running as a fullscreen app from the homescreen
fullscreen = $window.navigator.standalone,
android = ~ua.indexOf('Android'),
lastWidth = 0;
if (android) {
// Android's browser adds the scroll position to the innerHeight.
// Thus, once we are scrolled, the page height value needs to be corrected in case the page is loaded
// when already scrolled down. The pageYOffset is of no use, since it always
// returns 0 while the address bar is displayed.
window.onscroll = function () {
page.style.height = window.innerHeight + 'px'
}
}
var setupScroll = $window.onload = function () {
// Start out by adding the height of the location bar to the width, so that
// we can scroll past it
if (ios) {
// iOS reliably returns the innerWindow size for documentElement.clientHeight
// but window.innerHeight is sometimes the wrong value after rotating
// the orientation
var height = document.documentElement.clientHeight;
// Only add extra padding to the height on iphone / ipod, since the ipad
// browser doesn't scroll off the location bar.
if (iphone && !fullscreen) height += 60;
page.style.height = height + 'px';
} else if (android) {
// The stock Android browser has a location bar height of 56 pixels, but
// this very likely could be broken in other Android browsers.
page.style.height = (window.innerHeight + 56) + 'px'
}
// Scroll after a timeout, since iOS will scroll to the top of the page
// after it fires the onload event
setTimeout(scrollTo, 0, 0, 1);
};
($window.onresize = function () {
var pageWidth = page.offsetWidth;
// Android doesn't support orientation change, so check for when the width
// changes to figure out when the orientation changes
if (lastWidth == pageWidth) return;
lastWidth = pageWidth;
setupScroll();
})();
}
};
});
And to apply it, you'd find your #page element you were applying it to before:
<div id="page" window-resize-thingy></div>
... and that should be it really. Presuming the code you have works, it should be run pretty much the same way.

Resources