Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react-pdf not rendering images correctly during pdf.(<Document>).toBlob() #2842

Open
loizosv opened this issue Aug 13, 2024 · 2 comments
Open

Comments

@loizosv
Copy link

loizosv commented Aug 13, 2024

Describe the bug
Since react-pdf is not accepting SVG images i have created a function which first converts the svg into png and then to base64 string. This solution is working fine when rendering the pdf using the PDFViewer. However i do not want to render it on front end but im trying to convert the entire pdf to base64 so i can send it as email attachement.

To Reproduce
Steps to reproduce the behavior including code snippet (if applies):

  1. Hook for converting svg images to base64/png:
const convertSvgToPngBase64 = async (svgPath, width, height, scaleFactor = 2) => {
        const fetchSvgContent = async (svgPath) => {
            const response = await fetch(svgPath);
            if (!response.ok) {
                throw new Error('Failed to fetch SVG content');
            }
            return response.text();
        };

        try {
            const svgContent = await fetchSvgContent(svgPath);
            const encodedSvgString = encodeURIComponent(svgContent);

            const svgImage = new Image();
            svgImage.src = 'data:image/svg+xml;charset=utf-8,' + encodedSvgString;

            return new Promise((resolve, reject) => {
                svgImage.onload = () => {
                    const canvas = document.createElement('canvas');
                    canvas.width = width * scaleFactor;
                    canvas.height = height * scaleFactor;
                    const ctx = canvas.getContext('2d');

                    // Ensure the canvas is cleared before drawing
                    ctx.clearRect(0, 0, canvas.width, canvas.height);

                    // Draw the SVG onto the canvas at the higher resolution
                    ctx.drawImage(svgImage, 0, 0, canvas.width, canvas.height);

                    // Scale down the canvas to the desired size
                    const finalCanvas = document.createElement('canvas');
                    finalCanvas.width = width;
                    finalCanvas.height = height;
                    const finalCtx = finalCanvas.getContext('2d');
                    finalCtx.drawImage(canvas, 0, 0, width, height);

                    const pngDataUrl = finalCanvas.toDataURL('image/png');
                    if (pngDataUrl) {
                        const base64Png = pngDataUrl.split(',')[1];
                        resolve(`data:image/png;base64,${base64Png}`);
                    } else {
                        reject(new Error("PNG data URL generation failed"));
                    }
                };

                svgImage.onerror = (error) => {
                    reject(new Error("Failed to load SVG image: " + error.message));
                };
            });
        } catch (error) {
            console.error('Error converting SVG to PNG:', error);
            throw error;
        }
    };

2.React component to construct the Document:

import React, { useState, useEffect } from 'react';
import { Page, Text, View, Document, StyleSheet } from '@react-pdf/renderer';
import usePdfReportHooks from '/src/hooks/usePdfReportHooks';

import PDF_REPORT_VEHICLE_INFO from '/src/components/ReportPage/pdf/PDF_REPORT_VEHICLE_INFO';

import LogoSvg from "/media/AutoCheck 1.svg";
import CyPlateSvg from "/media/cyPlate.svg";

const PdfDocument = ({ vehicleData }) => {
    const { convertSvgToPngBase64 } = usePdfReportHooks();
    const [imagesBase64, setImagesBase64] = useState({});

    useEffect(() => {
        const fetchData = async () => {
            try {
                const images = await Promise.all([
                    convertSvgToPngBase64(LogoSvg, 700, 700, 1),
                    convertSvgToPngBase64(CyPlateSvg, 700, 700, 1),
                    ...
                ]);

                setImagesBase64({
                    logoBase64: images[0],
                    cyPlateBase64: images[1],
                    ...
                });

            } catch (error) {
                console.error('Error loading images:', error);
            }
        };

        fetchData();
    }, [convertSvgToPngBase64]);

    const styles = StyleSheet.create({
        ...
    });

    return (
        <Document>
            <Page style={styles.page}>
                <View>
                    <PDF_REPORT_VEHICLE_INFO
                        info={vehicleData.info}
                        critical_checks={vehicleData.critical_checks}
                        logoBase64={imagesBase64.logoBase64}
                        cyPlateBase64={imagesBase64.cyPlateBase64}
                        errorBase64={imagesBase64.errorBase64}
                        warningBase64={imagesBase64.warningBase64}
                        successBase64={imagesBase64.successBase64}
                    />
                </View>
            </Page>
        </Document>
    );
};

export default PdfDocument;
  1. Render this Document using PDFViewer:
    return (
          <PDFViewer width={'100%'} height={'700'} showToolbar={true}>
               <MyDocumentPdf json={json} />
         </PDFViewer>
    );

Until this point the code should work. Images should rendered on the PDF.

  1. Now instead of using step 3, we need to convert PDF to base64:
import { pdf } from '@react-pdf/renderer';
import PdfDocument from '/src/components/ReportPage/pdf/PdfDocument';

const MyReport = ({vehicleData}) => {
    return (
        <PdfDocument vehicleData={vehicleData} />
    );
};

const blobToBase64 = (blob) => {
    return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onerror = reject;
        reader.onloadend = () => resolve(reader.result.split(',')[1]);
        reader.readAsDataURL(blob);
    });
};

const generatePdfBase64 = async (vehicleData) => {
    try {
        // Create PDF document
        const document = <MyReport vehicleData={vehicleData} />;
        console.log("document", document);

        // Convert PDF document to blob 
        const blob = await pdf(document).toBlob();
        console.log("blob", blob);

        // Convert blob to base64 string
        const base64String = await blobToBase64(blob);

        return base64String;  // Return the base64 string
    } catch (error) {
        console.error('Error generating PDF:', error);
        return null;  // Return null to indicate an error
    }
};

export default generatePdfBase64;

  1. Try to get base64 from pdf:
const getPdfBase64 = async (json) => {
        try {
            //GET base64String from  PdfGenerator
            const base64Pdf = await generatePdfBase64(json);

            if (base64Pdf) {
                console.log('Generated PDF base64 string:', base64Pdf);
                // You can now use the base64Pdf string as needed
            } else {
                console.error('Failed to generate PDF');
            }

            return { success: false, data: "" };
        } catch (error) {
            return { success: false, error: `Error while generating PDF: ${error}` };
        }
    };
  1. see error:
PdfGenerator.jsx:26 false 'Image should receive either a "src" or "source" prop'
_callee$ @ @react-pdf_renderer.js?v=3c5419d1:91491
tryCatch @ @react-pdf_renderer.js?v=3c5419d1:13809
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13901
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13836
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14092
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14109
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14101
fetchImage2 @ @react-pdf_renderer.js?v=3c5419d1:91530
fetchAssets2 @ @react-pdf_renderer.js?v=3c5419d1:91545
_callee$ @ @react-pdf_renderer.js?v=3c5419d1:91571
tryCatch @ @react-pdf_renderer.js?v=3c5419d1:13809
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13901
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13836
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14092
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14109
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14101
resolveAssets2 @ @react-pdf_renderer.js?v=3c5419d1:91583
_callee$ @ @react-pdf_renderer.js?v=3c5419d1:28571
tryCatch @ @react-pdf_renderer.js?v=3c5419d1:13809
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13901
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13836
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14092
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14109
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14101
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:28587
_callee$ @ @react-pdf_renderer.js?v=3c5419d1:97630
tryCatch @ @react-pdf_renderer.js?v=3c5419d1:13809
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13901
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13836
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14092
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14109
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14101
render6 @ @react-pdf_renderer.js?v=3c5419d1:97645
_callee2$ @ @react-pdf_renderer.js?v=3c5419d1:97665
tryCatch @ @react-pdf_renderer.js?v=3c5419d1:13809
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13901
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13836
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14092
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14109
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14101
toBlob2 @ @react-pdf_renderer.js?v=3c5419d1:97696
generatePdfBase64 @ PdfGenerator.jsx:26
getPdfBase64 @ SendingPdf.jsx:37
updateCredits @ SendingPdf.jsx:147
await in updateCredits
(anonymous) @ SendingPdf.jsx:195
commitHookEffectListMount @ chunk-GVSLPJMJ.js?v=3c5419d1:16904
commitPassiveMountOnFiber @ chunk-GVSLPJMJ.js?v=3c5419d1:18152
commitPassiveMountEffects_complete @ chunk-GVSLPJMJ.js?v=3c5419d1:18125
commitPassiveMountEffects_begin @ chunk-GVSLPJMJ.js?v=3c5419d1:18115
commitPassiveMountEffects @ chunk-GVSLPJMJ.js?v=3c5419d1:18105
flushPassiveEffectsImpl @ chunk-GVSLPJMJ.js?v=3c5419d1:19486
flushPassiveEffects @ chunk-GVSLPJMJ.js?v=3c5419d1:19443
commitRootImpl @ chunk-GVSLPJMJ.js?v=3c5419d1:19412
commitRoot @ chunk-GVSLPJMJ.js?v=3c5419d1:19273
performSyncWorkOnRoot @ chunk-GVSLPJMJ.js?v=3c5419d1:18891
flushSyncCallbacks @ chunk-GVSLPJMJ.js?v=3c5419d1:9135
(anonymous) @ chunk-GVSLPJMJ.js?v=3c5419d1:18623
Show 74 more frames
Show lessUnderstand this warning
9PdfGenerator.jsx:26 false 'Image should receive either a "src" or "source" prop'
_callee$ @ @react-pdf_renderer.js?v=3c5419d1:91491
tryCatch @ @react-pdf_renderer.js?v=3c5419d1:13809
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13901
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13836
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14092
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14109
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14101
fetchImage2 @ @react-pdf_renderer.js?v=3c5419d1:91530
fetchAssets2 @ @react-pdf_renderer.js?v=3c5419d1:91545
_callee$ @ @react-pdf_renderer.js?v=3c5419d1:91571
tryCatch @ @react-pdf_renderer.js?v=3c5419d1:13809
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13901
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13836
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14092
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14109
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14101
resolveAssets2 @ @react-pdf_renderer.js?v=3c5419d1:91583
_callee$ @ @react-pdf_renderer.js?v=3c5419d1:28571
tryCatch @ @react-pdf_renderer.js?v=3c5419d1:13809
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13901
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13836
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14092
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
Promise.then
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14096
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14109
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14101
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:28587
_callee$ @ @react-pdf_renderer.js?v=3c5419d1:97630
tryCatch @ @react-pdf_renderer.js?v=3c5419d1:13809
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13901
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13836
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14092
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14109
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14101
render6 @ @react-pdf_renderer.js?v=3c5419d1:97645
_callee2$ @ @react-pdf_renderer.js?v=3c5419d1:97665
tryCatch @ @react-pdf_renderer.js?v=3c5419d1:13809
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13901
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:13836
asyncGeneratorStep @ @react-pdf_renderer.js?v=3c5419d1:14092
_next @ @react-pdf_renderer.js?v=3c5419d1:14104
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14109
(anonymous) @ @react-pdf_renderer.js?v=3c5419d1:14101
toBlob2 @ @react-pdf_renderer.js?v=3c5419d1:97696
generatePdfBase64 @ PdfGenerator.jsx:26
getPdfBase64 @ SendingPdf.jsx:37
updateCredits @ SendingPdf.jsx:147
await in updateCredits
(anonymous) @ SendingPdf.jsx:195
commitHookEffectListMount @ chunk-GVSLPJMJ.js?v=3c5419d1:16904
commitPassiveMountOnFiber @ chunk-GVSLPJMJ.js?v=3c5419d1:18152
commitPassiveMountEffects_complete @ chunk-GVSLPJMJ.js?v=3c5419d1:18125
commitPassiveMountEffects_begin @ chunk-GVSLPJMJ.js?v=3c5419d1:18115
commitPassiveMountEffects @ chunk-GVSLPJMJ.js?v=3c5419d1:18105
flushPassiveEffectsImpl @ chunk-GVSLPJMJ.js?v=3c5419d1:19486
flushPassiveEffects @ chunk-GVSLPJMJ.js?v=3c5419d1:19443
commitRootImpl @ chunk-GVSLPJMJ.js?v=3c5419d1:19412
commitRoot @ chunk-GVSLPJMJ.js?v=3c5419d1:19273
performSyncWorkOnRoot @ chunk-GVSLPJMJ.js?v=3c5419d1:18891
flushSyncCallbacks @ chunk-GVSLPJMJ.js?v=3c5419d1:9135
(anonymous) @ chunk-GVSLPJMJ.js?v=3c5419d1:18623
Show 86 more frames
Show lessUnderstand this warning
PdfGenerator.jsx:34 Error generating PDF: TypeError: Cannot read properties of undefined (reading 'width')

Expected behavior
The code should return a base64 string of type pdf of the PDF Document

Any feedback is appreciated!

@aagatsharma
Copy link

have you found the solution?

@loizosv
Copy link
Author

loizosv commented Sep 11, 2024

I ended up using PNG directly instead of SVG conversion! Much easier and efficient. here is an example:

const VehiclePdfReport = (props) => {
    return (
    <Document
      title={`pdf_title_(${getRandom})`}
      author={`Author Name`}
      subject={`Doc Subject`}
      creator={`Creator Name`}
      producer={`Producer Name`}
      pdfVersion={`1.2024`}
      lang={`en`}
      style={{ fontFamily: 'Segoe UI' }} //Supports Greek Characters
    >
      <View style={styles.view}>
         <Image src={"/media/myPngImage.png"} style={styles.image} />
         <Text style={styles.text}>Some Text</Text>
      </View>
    </Document>
);
}

export default VehiclePdfReport;

Example with PDFViewer and :

import { pdf, PDFViewer } from '@react-pdf/renderer';

//This function converts blob to base64 (I'm not sure why i have to remove data:application/pdf;base64 and then add it again, is not working without this, at least for me)
const blobToBase64 = (blob) => {
      return new Promise((resolve, reject) => {
          const reader = new FileReader();
          reader.onloadend = () => {
              resolve(reader.result.split(',')[1]); // Remove the "data:application/pdf;base64," prefix
          };
          reader.onerror = (error) => {
              reject(`Error converting pdf blob to base64: ${error}`);
          };
          reader.readAsDataURL(blob);
      });
  };

//Function to download a pdf as base64 on new tab (im using downloadPdf to onclick event of a button)
const downloadPdf = async (...) => {
      try {
          const blobPdf = await pdf(<VehiclePdfReport ...props />).toBlob();
          const pdfBase64 = await blobToBase64(blobPdf);

          // Trigger download
          const link = document.createElement('a');
          link.href = `data:application/pdf;base64,${pdfBase64}`;
          link.download = `pdfTitle.pdf`;
          link.click();

      } catch (err) {
          console.error("An error occured while downloading PDF, " + err);
      }
  };

....
//In return :
<PDFViewer width="100%" height="90%" style={{ m: 3 }} showToolbar={true}>
    <VehiclePdfReport
      ...my props
    />
</PDFViewer>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants