diegomura / react-pdf

📄 Create PDF files using React
https://react-pdf.org
MIT License
14.63k stars 1.16k forks source link

Support SVG Base64 Images #1250

Open OliverLeighC opened 3 years ago

OliverLeighC commented 3 years ago

Is your feature request related to a problem? Please describe. A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

One of the ways we have tried to render SVG charts (generated from highcharts or other react charting libraries) in react-pdf is to use a base64 encoded data URI. This is easier than trying to convert an svg element to a react-pdf Svg component, because the browser has built in methods for encoding dataURI's (see code example bellow).

export function encodeSvgString(svg: string) {
  const decoded = unescape(encodeURIComponent(svg));
  const b64String = btoa(decoded);
  const imgSource = `data:image/svg+xml;base64,${b64String}`;
  return imgSource;
}

However, the <Image /> component doesn't support dataURI sources when the type is svg+xml instead of jpeg or png. A plain html <img /> element can render both svg and jpg/png dataURI's, so it would be helpful if the <Image /> component could support svg dataURI's as well.

At first I thought it was maybe the length of the base64 string, since charts are complex the string can get pretty long, but I tried with a simplified one and it still doesn't render, and the error I see (with both the large and small dataURIs) is a warning Not valid image extension coming from resolveBase64Image because isValidFormat is false.

Describe the solution you'd like A clear and concise description of what you want to happen.

I'm not sure how complicated it would be, but if possible it would be helpful to support base64 images with the data:image/svg+xml;base64 format because it's a simpler way (at least as a user) to incorporate charts from react charting libraries without having to convert an svg element into the <Svg /> component.

Describe alternatives you've considered A clear and concise description of any alternative solutions or features you've considered.

I have tried converting the svg element into the Svg component ( still working out some bugs #1234 ) I also tried using canvas to convert the svg into a png or jpeg data URI which works but we loose a lot of image quality because it's rasterized instead of a vector image.

Additional context Add any other context or screenshots about the feature request here.

Here is an example svg converted into base64 (it's just 2 rectangles from a bar chart) it renders in the browser data:image/svg+xml;base64,PHN2ZyB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBjbGFzcz0iaGlnaGNoYXJ0cy1yb290IiBzdHlsZT0iZm9udC1mYW1pbHk6SGVsdmV0aWNhO2ZvbnQtc2l6ZToxMnB4OyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNjAwIiBoZWlnaHQ9IjYwMCIgdmlld0JveD0iMCAwIDYwMCA2MDAiPgogIDxnIGNsYXNzPSJoaWdoY2hhcnRzLXNlcmllcyBoaWdoY2hhcnRzLXNlcmllcy0wIGhpZ2hjaGFydHMtYmFyLXNlcmllcyBoaWdoY2hhcnRzLWNvbG9yLTAiIGRhdGEtei1pbmRleD0iMC4xIiBvcGFjaXR5PSIxIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1OTAsNTA5KSByb3RhdGUoOTApIHNjYWxlKC0xLDEpIHNjYWxlKDEgMSkiIGNsaXAtcGF0aD0idXJsKCNoaWdoY2hhcnRzLW8xa2JoMHotMzEzLSkiPgo8cmVjdCB4PSIzNDQuNSIgeT0iMjkxLjUiIHdpZHRoPSI1MCIgaGVpZ2h0PSIyMDQiIGZpbGw9IiNBOURBQ0MiIHN0cm9rZT0iI2ZmZmZmZiIgc3Ryb2tlLXdpZHRoPSIxIiBvcGFjaXR5PSIxIiBjbGFzcz0iaGlnaGNoYXJ0cy1wb2ludCBoaWdoY2hhcnRzLWNvbG9yLTAiPjwvcmVjdD4KPHJlY3QgeD0iMTIwLjUiIHk9IjUxLjUiIHdpZHRoPSI1MCIgaGVpZ2h0PSI0NDQiIGZpbGw9IiNBOURBQ0MiIHN0cm9rZT0iI2ZmZmZmZiIgc3Ryb2tlLXdpZHRoPSIxIiBvcGFjaXR5PSIxIiBjbGFzcz0iaGlnaGNoYXJ0cy1wb2ludCBoaWdoY2hhcnRzLWNvbG9yLTAiPjwvcmVjdD4KPC9nPgo8L3N2Zz4=

rocketman101 commented 3 years ago

I have seen a few discussions on the SVG/charting issue. Adding Base64 support may make these integrations easier if it is possible - but what is the current recommended approach to using react charting libraries (react-fusioncharts in my case)?

The announcement for version 2 states "...also building integrations with pre-existing libraries that output SVG...".

My current thinking is:

1: render my components that output SVG outside the viewport, or otherwise hidden from view 2: get reference to the chart with useRef and extract SVG 3: ??? (a bit stuck here)... convert to react-pdf svg somehow... maybe parse to react element then convert to react-pdf primitives... 4: render the PDFViewer and document with the parsed SVG

Anyone have other ideas? It seems a bit convoluted but at least I can reuse my chart components this way (for the initial render).

If Base64 SVG images were supported (as requested here) would the process be the same but step 3 become "convert svg string to base 64 svg image"? or is there a better way to get the svg in the first place?

Maybe a section in the readme pointing people in the right direction for charting integration (with or without Base64 svg support) would be useful. All charting libraries I've seen work in essentially the same way - rendering svg to a DOM element so unless I'm missing something the charts would need to be rendered first before rendering the pdf.

OliverLeighC commented 3 years ago

I have been doing this to convert svg to a jpeg image and using it with the react-pdf Image tag as a base64 encoded jpg image.

Yes you do have to render the chart component somewhere first, we have ours rendering under a div set to display: none It depends on your charting library, some have a built in way to extract the svg. I use highCharts which has a getSVG method

rocketman101 commented 3 years ago

That's nice - thanks. I had wondered if converting to jpg was the way to go until SVG support is a bit better. Fusioncharts has a get svg method as well so that simplifies things slightly I guess.

But yeah - Base64 svg support would be cool - is this a pdfkit limitation? Their readme states "Image embedding: Supports JPEG and PNG files" https://github.com/foliojs/pdfkit.