Using Puppeteer, how to generate a PDF with automatic height? - pdf-generation

I use Puppeteer to generate receipts in PDF.
For each receipt, width is always the same, but height changes, according to the article count in the order.
For now, I'm using the article count to approximate the required height for my receipt. It kind of works, but it's not perfect and is a dirty way to do (you can notice useless blank space at the bottom of the picture bellow).
Is there way to tell Puppeteer : "Please automatically find the right PDF height, according to the HTML content, in order to NOT generate useless blank space" ?

As I understand correctly you are using width and height options of page.pdf(options) method (unfortunately you didn't provide your code). You may try to get height value of the loaded web page by evaluating JS function on the page. For example ...
const pageHeight = await page.evaluate(() => { window.innerHeight; });
const result = await page.pdf({ height: pageHeight + 'px' });

Related

Tracking d3.zoom transform across page refresh

I'm having a problem with tracking Transforms across page loads, any help much appreciated.
'workspaceDiv' is a full page outer div
'squaregroup' is a g that contains all page elements and can be moved around
For this example I've added a single circle to the squaregroup
workspaceDiv = d3.select("#workspaceDiv")
squaregroup = workspaceDiv.append("g")
.attr("id", "squaregroup")
squaregroup.append("circle").attr("cx", 20).attr("cy", 20).attr("r", 10);
To allow the user to move the g around the page I've attached a d3.zoom.
workspaceDiv.call(zoom);
var zoom = d3.zoom()
.on("zoom", zoomed)
function zoomed(){
squaregroup.attr("transform", d3.event.transform)
}
You might have noticed that I want to transform squaregroup but I have attached the d3.zoom to the workspaceDiv. This is so you can transform it by clicking anywhere on the page (and not only by clicking in the small squaregroup).
On initial page load, this works perfect. Any transforms are also saved as a string in the URL successfully.
On a page reload, the transform is taken from the URL and applied to the sqauregroup:
squaregroup.attr("transform", d3.zoomIdentity.translate(url.x,url.y).scale(url.scale))
Chrome devtools showing the custom transform applied after page reload
[2]: https://i.stack.imgur.com/H93Nf.png
The problem
After a page reload, squaregroup is transformed (see image above), but the d3.event.transform of workspaceDiv is reset, meaning the first drag (of 1 pixel), resets transform (to 0,0) and not with the transform I've applied (200,400).
So the 2nd+ drag is fine, but the first drag throws all data off the page meaning you have to drag around until you find it.
Approaches
Attaching ".call(zoom)" on g means the draggable area is too small, and completely changes the behaviour for the user
I can't find a way to force update the tracking of a .event to be in sync after a page reload
I'm not sure if my approach is wrong, or if there is a function of d3.zoom I just can't find. Any input welcomed!
Many Thanks
Here is the solution (see it in a fiddle)
Make sure you are using zoomIdentity according to the D3 V5 and higher (the sample you tried to use is probably done for the previous versions):
const svg = d3.select('svg');
const group = svg.append('g');
group.append('circle').attr('r', 20);
const zoom = d3.zoom();
const onZoom = () => group.attr('transform', d3.event.transform);
zoom.on("zoom", onZoom);
svg.call(zoom);
const transform = d3.zoomIdentity;
transform.x = 100;
transform.y = 50;
group.call(zoom.transform, transform);

Plotly.js, show tooltips outside of chart container

I need to implement a plotly.js chart on a page with a very restricted width. As a result, a tooltip is partially cut. Is it possible to cause tooltip not to be limited by plotly.js container size?
My code example at codepen: https://codepen.io/anatoly314/pen/gOavXzZ?editors=1111
//my single trace defined as following but it's better to see example at codepen
const yValue1 = [1000];
const trace1 = {
x: [1],
y: yValue1,
name: `Model 1`,
text: yValue1.map(value => Math.abs(value)),
type: 'bar',
textposition: 'outside'
};
It is, by design, not possible for any part of the chart to overflow its container.
I would say it is wrong to say that by design this is not possible! It is a bit hacky, but when you add the following lines, it shows the label outside of svg:
svg.main-svg,svg.main-svg *
{
overflow:visible !important;
}
The answer given by rokdd works. However the css selector should be more specific, otherwise it's natural that you will introduce subtle bugs (particularly if you need to scroll the content where the plotly chart is contained).
If we look at the DOM tree constructed by Plotly, we find that the tooltips are created inside the <g class="hoverlayer"></g> element (which is a direct child of one of the three <svg class="main-svg"></svg>). So that parent (that svg.main-svg element) is only one that needs to affected.
The ideal css selector in this case would be the :has selector. However it's still not supported (as of 2022): https://css-tricks.com/the-css-has-selector/
So the next simplest thing is to use a little bit of javascript right after we call Plotly.newPlot:
// get the correct svg element
var mainSvgEl = document.querySelector('#positive g.hoverlayer').parentElement;
mainSvgEl.style['overflow'] = 'visible';
Or in a more generic way (works for any chart):
Array.from(document.querySelectorAll('g.hoverlayer')).forEach(hoverEl => {
let mainSvgEl = hoverEl.parentElement;
mainSvgEl.style['overflow'] = 'visible';
});

How to improve display quality in pdf.js

I'm using open source library for PDF documents from mozilla(pdf.JS).
When i'm trying to open pdf documents with bad quality, viewer displays it with VERY BAD quality.
But if I open it in reader, or in browser (drag/drop into new window), whis document displays well
Is it possible to change?
Here is this library on github mozilla pdf.js
You just have to change the scaling of your pdf i.e. when rendering a page:
pdfDoc.getPage(num).then(function(page) {
var viewport = page.getViewport(scale);
canvas.height = viewport.height;
canvas.width = viewport.width;
...
It is the scale value you have to change. Then, the resulting rendered image will fit into the canvas given its dimensions e.g. in CSS. What this means is that you produce a bigger image, fit it into the container you had before and so you effectively improve the resolution.
There is renderPage function in web/viewer.js and print resolution is hard-coded in there as 150 DPI.
function renderPage(activeServiceOnEntry, pdfDocument, pageNumber, size) {
var scratchCanvas = activeService.scratchCanvas;
var PRINT_RESOLUTION = 150;
var PRINT_UNITS = PRINT_RESOLUTION / 72.0;
To change print resolution to 300 DPI, simply change the line below.
var PRINT_RESOLUTION = 300;
See How to increase print quality of PDF file with PDF.js viewer for more details.
Maybe it's an issue related with pixel ratio, it used to happen to me when device pixel ratio is bigger than 1 (for example iPhone, iPad, etc.. you can read this question for a better explanation.
Just try that file on PDF.js Viewer. If it works like expected, you must check how PDF.js works with pixel ratio > 1 here. What library basically does is:
canvas.width = viewport.width * window.devicePixelRatio;
canvas.styles.width = viewport.width + 'px'; // Note: The px unit is required here
But you must check how PDF.js works for better perfomance
I ran into the same issue and I used the intent option of renderContent to fix that.
const renderContext = {
intent: 'print',
// ....
}
var renderTask = page.render(renderContext);
As per docs renderContext accepts intent which supports three values - display, print or any. The default is display. When I used print instead the render quality was extremely good, at par with any desktop app.

Div Height doesnt get resized after AJAX call completion

I wanted to increase the height of my div (id = scroller) and set it at the end of the success call back function. In the success callback function I dynamically inject some inner div's which are contained in the "scroller" div.
Default height of scroller div is 400 px. Dynamically it comes e.g. as 900px, then it should be set as 900px.
height = $('#content').innerHeight();
alert("outside: " + height);
$('#scroller').css('height', height + "px");
So far so good. Now, this resizing works if I have an alert statement. If I remove the alert, it doesnt resize itself.
What might be the problem ?
Thanks in advance.
P.S. height: auto doesnt work maybe because I am expecting this functionality on android emulator/cellphone and i am developing my mobile app using Phonegap.
how about creating a new div, loading your data into it, then using the jQuery replaceWith() method to replace the old content div with the new one. That might be one way to force the browser to recalculate the height of the (new) content div and everything around it.
Something like:
var newContent = $('<div id="content"></div>').load(toLoad,'',showNewContent());
$('#content').replaceWith(newContent);

Draw Element's Contents onto a Canvas Element / Capture Website as image using (?) language

I asked a question on SO about compiling an image file from HTML. Michaƫl Witrant responded and told me about the canvas element and html5.
I'm looked on the net and SO, but i haven't found anything regarding drawing a misc element's contents onto a canvas. Is this possible?
For example, say i have a div with a background image. Is there a way to get this element and it's background image 'onto' the canvas? I ask because i found a script that allows one to save the canvas element as a PNG, but what i really want to do is save a collection of DOM elements as an image.
EDIT
It doesn't matter what language, if it could work, i'm willing to attempt it.
For the record, drawWindow only works in Firefox.
This code will only work locally and not on the internet, using drawWindow with an external element creates a security exception.
You'll have to provide us with a lot more context before we can answer anything else.
http://cutycapt.sourceforge.net/
CutyCapt is a command line utility that uses Webkit to render HTML into PNG, PDF, SVG, etc. You would need to interface with it somehow (such as a shell_exec in PHP), but it is pretty robust. Sites render exactly as they do in Webkit browsers.
I've not used CutyCapt specifically, but it came to me highly recommended. And I have used a similar product called WkHtmlToPdf, which has been awesome in my personal experience.
After many attempts using drawWindow parameters, that were drawing wrong parts or the element, I managed to do it with a two steps processing : first capture the whole page in a canvas, then draw a part of this canvas in another one.
This was done in a XUL extension. drawWindow will not work in other browsers, and may not work in a non-privileged context due to security reasons.
function nodeScreenshot(aSaveLocation, aFileName, aDocument, aCSSSelector) {
var doc = aDocument;
var win = doc.defaultView;
var body = doc.body;
var html = doc.documentElement;
var selection = aCSSSelector
? Array.prototype.slice.call(doc.querySelectorAll(aCSSSelector))
: [];
var coords = {
top: 0,
left: 0,
width: Math.max(body.scrollWidth, body.offsetWidth,
html.clientWidth, html.scrollWidth, html.offsetWidth),
height: Math.max(body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight)
var canvas = document.createElement("canvas");
canvas.width = coords.width;
canvas.height = coords.height;
var context = canvas.getContext("2d");
// Draw the whole page
// coords.top and left are 0 here, I tried to pass the result of
// getBoundingClientRect() here but drawWindow was drawing another part,
// maybe because of a margin/padding/position ? Didn't solve it.
context.drawWindow(win, coords.top, coords.left,
coords.width, coords.height, 'rgb(255,255,255)');
if (selection.length) {
var nodeCoords = selection[0].getBoundingClientRect();
var tempCanvas = document.createElement("canvas");
var tempContext = tempCanvas.getContext("2d");
tempCanvas.width = nodeCoords.width;
tempCanvas.height = nodeCoords.height;
// Draw the node part from the whole page canvas into another canvas
// void ctx.drawImage(image, sx, sy, sLargeur, sHauteur,
dx, dy, dLargeur, dHauteur)
tempContext.drawImage(canvas,
nodeCoords.left, nodeCoords.top, nodeCoords.width, nodeCoords.height,
0, 0, nodeCoords.width, nodeCoords.height);
canvas = tempCanvas;
context = tempContext;
}
var dataURL = canvas.toDataURL('image/jpeg', 0.95);
return dataURL;
}

Resources