I have a form in my project where I enter user data and generate JSPDF file with these data and I need to make send the PDF generated file to the 'report' FormControl to send it to the API so I can store it in the database.
I have a blob variable that will carry the blob, when I submit the form I am calling the exportHtmlToPDF() function the converts the div to PDF and output it as blob to the blob variable
blob: any;
createUserForm: FormGroup = new FormGroup({
'name': new FormControl(null, [Validators.required, Validators.minLength(3), Validators.maxLength(100)]),
'gender': new FormControl(null, [Validators.required]),
'date_of_birth': new FormControl(null, [Validators.required]),
'identity': new FormControl(null, [Validators.required, Validators.minLength(9), Validators.maxLength(14)]),
'patient_id': new FormControl(null, [Validators.required, Validators.minLength(3), Validators.maxLength(100)]),
'test': new FormControl(null, [Validators.required, Validators.minLength(3), Validators.maxLength(100)]),
'result': new FormControl(null, [Validators.required]),
'collect_date': new FormControl(null, [Validators.required]),
'report_date': new FormControl(null, [Validators.required]),
'branch': new FormControl(null, [Validators.required, Validators.minLength(3), Validators.maxLength(100)]),
'reference_range': new FormControl(null, [Validators.required]),
'lab_director': new FormControl(null, [Validators.required, Validators.minLength(3), Validators.maxLength(100)]),
'qr_url': new FormControl(this.qrCode),
'qr_link': new FormControl(this.qrLink),
'report': new FormControl(null)
});
onSubmit() {
this.exportHtmlToPDF();
let formData = new FormData();
formData.append('name', this.createUserForm.get('name')?.value);
formData.append('gender', this.createUserForm.get('gender')?.value);
formData.append('date_of_birth', this.createUserForm.get('date_of_birth')?.value);
formData.append('identity', this.createUserForm.get('identity')?.value);
formData.append('patient_id', this.createUserForm.get('patient_id')?.value);
formData.append('test', this.createUserForm.get('test')?.value);
formData.append('result', this.createUserForm.get('result')?.value);
formData.append('collect_date', this.createUserForm.get('collect_date')?.value);
formData.append('report_date', this.createUserForm.get('report_date')?.value);
formData.append('branch', this.createUserForm.get('branch')?.value);
formData.append('reference_range', this.createUserForm.get('reference_range')?.value);
formData.append('lab_director', this.createUserForm.get('lab_director')?.value);
formData.append('qr_url', this.createUserForm.get('qr_url')?.value);
formData.append('qr_link', this.createUserForm.get('qr_link')?.value);
formData.append('report', this.blob);
this._UsersService.storeUser(formData).subscribe(data => {
if (data == "Customer Added Successfully") {
alert('customer added!')
}
});
}
public exportHtmlToPDF() {
let data = document.getElementById('pdf')!;
html2canvas(data).then(canvas => {
let docWidth = 208;
let docHeight = canvas.height * docWidth / canvas.width;
const contentDataURL = canvas.toDataURL('image/png');
let doc = new jsPDF('p', 'mm', 'a4');
let position = 0;
doc.addImage(contentDataURL, 'PNG', 0, position, docWidth, docHeight);
this.blob = doc.output('blob');
});
}
CodePudding user response:
I hate to have to repeat myself, but: DO NOT USE toDataURL
FOR ANYTHING LARGER THAN A FEW KILOBYTES:
- Generating
data:
URIs is sloooow and blocks the browser's UI/render thread (e.g. my browser on my beefy modern machines freezes for about 5-7 seconds when it saves aBlob
containing a 4K-wallpaper-sized JPEG image to a Base64data:
URI, whereas usingURL.createObjectURL
is instantaneous. - Base64 encoding increases the size of your binary data by 33% (as it uses 4 bytes of ASCII text to represent 3 raw binary bytes).
- Every time a
data:
URI is used it has to be validated, parsed, decoded, and its inner binary data copied into a new destination buffer (internal to the browser). This is a whole slew of inefficencies. UsingBlob
avoids that entirely. - You cannot use
data:
URIs to directly upload binary data unless your server-side or back-end logic is specifically programmed to handle it (also there's no official MIME type that represents adata:
URI alone, and the old fallback ofapplication/octet-stream
isn't appropriate either), whereas if you pass aBlob
tofetch
orXMLHttpRequest
it will perfectly upload/transfer the binary data with the correct MIME Content-Type too!
...with that out of the way:
<canvas>
supports exporting image data as a Blob
object which you can pass directly into a FormData
object for multipart/form-data
binary uploads (just like <input type="file" />
) - as well as for direct PUT
requests where the request body's payload is the raw Blob
data.
Apparently jsPDF's addImage
function accepts a HTMLCanvasElement
directly, so there is no need to use toBlob
(and especially not toDataURL
, eww).
async function exportHtmlToPDF() {
const pdfElement = document.getElementById('pdf') as /* What element is it? */ | null;
if( !pdfElement ) throw new Error( "Couldn't find 'pdf' element." );
const canvas = await html2canvas( pdfElement );
let docWidth = 208;
let docHeight = canvas.height * docWidth / canvas.width;
const doc = new jsPDF( 'p', 'mm', 'a4' );
doc.addImage(canvas, 'PNG', /*x:*/ 0, /*y:*/ 0, docWidth, docHeight);
const pdfBlob = doc.output( 'blob' );
const uploadResponse = await fetch( '/pdf-upload-handler', { method: 'PUT', body: pdfBlob } );
console.log( uploadResponse );
}
However I noticed that your program doesn't actually need to generate a PDF. The PDF you're generating is just wrapping an existing PNG image, so why not just upload the PNG image and skip the (slooowwww) client-side PDF generation?
async function exportHtmlToPng() {
const pdfElement = document.getElementById('pdf') as /* What element is it? */ | null;
if( !pdfElement ) throw new Error( "Couldn't find 'pdf' element." );
const canvas = await html2canvas( pdfElement );
canvas.toBlob( gotBlob, 'image/png' );
}
async function gotBlob( pngBlob: Blob | null ) {
if( !pngBlob ) return;
const uploadResponse = await fetch( '/png-image-upload-handler', { method: 'PUT', body: pngBlob } );
console.log( uploadResponse );
}
Better yet, here's a version with a Promise
adapter for toBlob
:
async function exportHtmlToPng() {
const pdfElement = document.getElementById('pdf') as /* What element is it? */ | null;
if( !pdfElement ) throw new Error( "Couldn't find 'pdf' element." );
const canvas = await html2canvas( pdfElement );
const pngBlob = await getBlob( canvas, 'image/png' );
const uploadResponse = await fetch( '/png-image-upload-handler', { method: 'PUT', body: pngBlob } );
console.log( uploadResponse );
}
function getBlob( canvas: HTMLCanvasElement, type: string ): Promise<Blob> {
return new Promise( function( resolve, reject ) {
canvas.toBlob( function( blob ) => {
if( blob ) resolve( blob );
else reject( "Failed to create Blob from Canvas." );
}, type );
} );
}