Generate a PDF from HTML and merge another existing PDF - javascript

What I have now
An Angular - NestJS app. The client makes a request to the backend, the response is a blob and it's a whole HTML page formatted using Paper CSS (multiple pages). The blob url is used to set the src of an iframe. The iframe is a sort of preview. The user checks that everything is okay and triggers the native print functionality:
print(): void {
this.iframe.nativeElement.contentWindow?.focus();
this.iframe.nativeElement.contentWindow?.print();
}
Saves it as PDF and everything is fine.
New requirement
Generate programmatically this PDF and add a page from another PDF file. This other PDF is a byte array from the database.
What I tried
I used jsPDF, simply
fetch(this.iframe.nativeElement.src)
.then((response) => response.text())
.then(async (html) => {
const doc = new jsPDF();
doc.html(html, {
callback: function (doc) {
doc.save('file.pdf');
},
});
});
But the result is a giant PDF (font size wise), the table borders and the formatting are not maintained. An 8 pages doc becomes a 50 pages one.
I also tried html2cavans but I get some error because I have to create the HTML element, it's not an actual DOM.
I would like to avoid to do it in the backend via headless browser or similar because I am not sure I can install other software on the production server (customer managed server).
Do you a suggestion about how to achieve this?

Related

Create and download (html/css/js) files client-side

I've built a code playground (similar to liveweave) using react. the users can save their "code playgrounds" and access them later. (using firebase db) ,this "code playgrounds"are just made of HTML, CSS, and js. I´m trying to add a functionality which allow the user to download a playground, the idea is that it will generate 3 separate files (one for each language in the playground)
1) is there a way to generate (HTML CSS and js) files and populate them with content client-side?
2) if so, would there be any chance to group those files inside a .rar also client-side?
3) if generating these files client-side is not the optimal solution/not possible, how would you approach this problem?
I was thinking maybe in an express server that queries the data from the db and then response with those files, but I would like to try a client-sided solution
Finally decided to use fileSaver.js package
import { saveAs } from "file-saver";
const saveFiles = () => {
var blob = new Blob([makeHtml(getDocumentCode())], {
type: "text/plain;charset=utf-8",
});
saveAs(blob, `${getFileName()}.html`);
};
first, you need to make a blob out of the content of the file, in this case, it was HTML +css +js code, makeHtml function handles how the HTML document is constructed, then just pass that blob to the saveAs function along the name and extension for the file, then you can use saveFiles in response to any event like onClick
<button onClick={saveFile}>Download<button/>

Use React Components to generate a pdf on Server Side

I have an application on React that shows some information based on a json stored in my Server. So when the user opens my website it gets the JSON and renders it. I want to implement a new function that enables the user also to download a PDF (related to that specific JSON). And my idea is when the JSON is available on my server I generate the PDF (completely server side) and store it. When someone goes on the front and pushes the button to Download the PDF, it goes on my storage and downloads the PDF. If I can generate the pdf based on React Components it would be much easier (because they are already implemented on the React Application).
I am newbie to node but little experienced with React and don't know if it is possible to solve the problem like this but basically my idea was providing React a JSON, then it will generate the MainComponent based on it, then I get the html of this Component and finally generate the PDF to store on the server. On pseudo code:
const fs = require('fs');
let required_data = JSON.parse(fs.readFileSync('myJson.json'));
html = renderToStaticMarkup(<MainComponent initialJson={required_data})
And then with this html I create the pdf with some library like html-pdf, jspdf etc.. and save it to the server. Is it possible somehow to use this approach to solve my problem?
I could solve my problem using Puppeteers headless Chrome.
First I generated the html of my application
html = ReactDOMServer.renderToStaticMarkup(<MainComponent initialJson={required_data} />)
then I opened Puppeteers' Chrome, used the html on page, and printed it to PDF. I got really satisfatory results:
const browser = await Puppeteer.launch(....)
let page = await browser.newPage()
await page.setContent(html)
let pdf = await page.pdf(...); ///This is the PDF File
await browser.close();

How to Launch a PDF from a UWP (Universal Windows Platform) Web Application

I've converted an existing web application (HTML5, JS, CSS, etc.) into a Windows UWP app so that (hopefully) I can distribute it via the Windows Store to Surface Hubs so it can run offline. Everything is working fine, except PDF viewing. If I open a PDF in a new window, the Edge-based browser window simply crashes. If I open an IFRAME and load PDFJS into it, that also crashes. What I'd really like to do is just hand off the PDF to the operating system so the user can view it in whatever PDF viewer they have installed.
I've found some windows-specific Javascript APIs that seem promising, but I cannot get them to work. For example:
Windows.System.Launcher.launchUriAsync(
new Windows.Foundation.Uri(
"file:///"+
Windows.ApplicationModel.Package.current.installedLocation.path
.replace(/\//g,"/")+"/app/"+url)).then(function(success) {
if (!success) {
That generates a file:// URL that I can copy into Edge and it shows the PDF, so I know the URL stuff is right. However, in the application it does nothing.
If I pass an https:// URL into that launchUriAsync function, that works. So it appears that function just doesn't like file:// URLs.
I also tried this:
Windows.ApplicationModel.Package.current.installedLocation.getFileAsync(url).then(
function(file) { Windows.System.Launcher.launchFileAsync(file) })
That didn't work either. Again, no error. It just didn't do anything.
Any ideas of other things I could try?
-- Update --
See the accepted answer. Here is the code I ended up using. (Note that all my files are in a subfolder called "app"):
if (location.href.match(/^ms-appx:/)) {
url = url.replace(/\?.+/, "");
Windows.ApplicationModel.Package.current.installedLocation.getFileAsync(("app/" + url).replace(/\//g,"\\")).then(
function (file) {
var fn = performance.now()+url.replace(/^.+\./, ".");
file.copyAsync(Windows.Storage.ApplicationData.current.temporaryFolder,
fn).then(
function (file2) {
Windows.System.Launcher.launchFileAsync(file2)
})
});
return;
}
Turns out you have to turn the / into \ or it won't find the file. And copyAsync refuses to overwrite, so I just use performance.now to ensure I always use a new file name. (In my application, the source file names of the PDFs are auto-generated anyway.) If you wanted to keep the filename, you'd have to add a bunch of code to check whether it's already there, etc.
LaunchFileAsync is the right API to use here. You can't launch a file directly from the install directory because it is protected. You need to copy it first to a location that is accessible for the other app (e.g. your PDF viewer). Use StorageFile.CopyAsync to make a copy in the desired location.
Official SDK sample: https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/AssociationLaunching
I just thought I'd add a variation on this answer, which combines some details from above with this info about saving a blob as a file in a JavaScript app. My case is that I have a BLOB that represents the data for an epub file, and because of the UWP content security policy, it's not possible simply to force a click on a URL created from the BLOB (that "simple" method is explicitly blocked in UWP, even though it works in Edge). Here is the code that worked for me:
// Copy BLOB to downloads folder and launch from there in Edge
// First create an empty file in the folder
Windows.Storage.DownloadsFolder.createFileAsync(filename,
Windows.Storage.CreationCollisionOption.generateUniqueName).then(
function (file) {
// Open the returned dummy file in order to copy the data to it
file.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (output) {
// Get the InputStream stream from the blob object
var input = blob.msDetachStream();
// Copy the stream from the blob to the File stream
Windows.Storage.Streams.RandomAccessStream.copyAsync(input, output).then(
function () {
output.flushAsync().done(function () {
input.close();
output.close();
Windows.System.Launcher.launchFileAsync(file);
});
});
});
});
Note that CreationCollisionOption.generateUniqueName handles the file renaming automatically, so I don't need to fiddle with performance.now() as in the answer above.
Just to add that one of the things that's so difficult about UWP app development, especially in JavaScript, is how hard it is to find coherent information. It took me hours and hours to put the above together from snippets and post replies, following false paths and incomplete MS documentation.
You will want to use the PDF APIs https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/PdfDocument/js
https://github.com/Microsoft/Windows-universal-samples/blob/master/Samples/PdfDocument/js/js/scenario1-render.js
Are you simply just trying to render a PDF file?

Save HTML page to PDF with print layout

I want to allow my users to save my page as a PDF. I have created a print stylesheet and I can generate the PDF by using Javascript's print function. Here's my problem:
In Chrome, the browser generates a preview and shows it to the user. The user can then either save it or print it.
However, in IE and FF, print calls up a complex menu, and while it can generate a PDF by "printing" to PDFCreator, it's a complex process that many users won't understand.
What I want to do is to somehow duplicate the Chrome functionality for non-Chrome users. Options I have considered:
Screenshot the HTML page and render that image into a PDF with Javascript. There are libraries that do this, but I want my PDF to have the print layout.
Generate the PDF on the server and send it to the user's browser. This can be done, but it seems difficult to use the same HTML as the standard page.
My server is running PHP and the Zend framework. I can't use NodeJS or any headless browser to render on the server. Do I have any options?
I've done exactly what you want to do before using a library called dompdf (https://github.com/dompdf/dompdf). Here is how I used it:
I dropped the dompdf library into the "library" folder in my ZF project. Then, in my application when I'm ready to render the page I created a new Zend_View() object and set it up with whatever view script variables it needed. Then I called the render() function and stored the rendered output into a variable I then provided to dompdf. The code looks like this:
$html = new Zend_View();
$html->setScriptPath(APPLICATION_PATH . '/views/scripts/action_name/');
$html->assign($data); //$data contains the view variables I wanted to populate.
$bodyText = $html->render('pdf_template.phtml');
require_once(APPLICATION_PATH."/../library/dompdf/dompdf_config.inc.php");
$dompdf = new DOMPDF();
$dompdf->load_html($bodyText);
// Now you can save the rendered pdf to a file or stream to the browser:
$dompdf->stream("sample.pdf");
// Save to file:
$dompdf->render();
$pdf = $dompdf->output();
file_put_contents($filename, $pdf);

Save a pdf file with TideSDK

I have a decodification problem.
I have an offline desktop application, where I have to generate a pdf file and save at his open.
To generate the PDF file I use BytescoutPDF library createpdf.js.
This returns a document variable that I have to save.
I tried with:
//this calls the createPDF to BytescoutPDF library
//and returns the variable into 'doc'
var doc = generaStaticPartBolla_2();
//take the stream
var bolla = Ti.Filesystem.getFileStream(billPath);
//open in write mode
bolla.open(Ti.Filesystem.MODE_WRITE);
//write the doc file decodified in Base 64
bolla.write(doc.getBase64Text());
//close the stream
bolla.close();
Now, the file generated is currupted.
I'm not able to open this. How can I do this? The file must be converted in Base 64 or other?
I don't know if you have solved your issue now, but I had the same requirements : offline app, generating pdf from HTML, and in my case, styling the generated pdf with CSS.
After trying many solutions, the main problem was to style with CSS.
Finally I used WkhtmlToPdf (http://wkhtmltopdf.org/). Basically I embed the binaries (for mac os and for windows) in the app, and regarding the platform, I execute them with the Ti.Process method.
WkhtmlToPdf generates a pdf in the specified path, so in this way, you will be able to open this pdf.
(In order to set the path for the pdf, i use openSaveAsDialog (http://tidesdk.multipart.net/docs/user-dev/generated/#!/api/Ti.UI.UserWindow-method-openFileChooserDialog) which allows the user to set the path and the name of the generated pdf).

Categories