Printing generated QR codes on a PDF file
Asked Answered
B

4

8

I'm creating a ReactJS app that uses QR codes and I want to be able to print a PDF document with a batch of codes at once. I'm currently using both react-qr-svg to generate the codes and @react-pdf/renderer to create the document. The problem is that I haven't been able to show these QR Codes on the document.

First I tried using the Image tag from @react-pdf/renderer like this:

<Image
  src={<QRCode
    level="Q"
    style={{width: 256, marginBottom: 50 }}
    value={'hello world'}
  />}
/>

Whick of course didn't work, after that I tried to convert the SVG to a Data Buffer and had no results.

Is there any straightforward solution for this? Should I use other libraries for this project?

Baguio answered 31/1, 2020 at 15:30 Comment(0)
C
3

Directly from the docs: https://www.npmjs.com/package/react-qr-svg

You don’t need to put it in an image tag.

My assumption is this library outputs <svg> ... </svg> (I.e. valid HTML tags to output directly)

You can just output the tag/element out directly?

  import React from "react";
  import { QRCode } from "react-qr-svg";

  class Demo extends React.Component {
      render() {
          return (
            <QRCode
            bgColor="#FFFFFF"
            fgColor="#000000"
            level="Q"
            style={{ width: 256 }}
            value="some text"
            />
          );
      }
  } 

UPDATE

If I understand correctly, we cannot simply output <svg>...</svg> on the page as the library @react-pdf/renderer will not work with svg tags. So I propose we serialize the svg to a base-64 string, assigning that to the src of the image tag and then it should work.

I put a simplified demo together without libraries:https://codepen.io/Alexander9111/pen/QWweYXO

HTML (simplified):

<svg>    
    <ellipse class="ground" fill="##787f6a" cx="283.5" cy="487.5" rx="259" ry="80"/>
    <path class="kiwi" fill="#94d31b" d="M210.333,65.331C104.367,...,203.01z"/>
</svg>
<div id="target">
    <!-- place to put our img tag -->
</div>

JS:

const svg = document.querySelector("svg");
const serializer = new XMLSerializer();
const svgStr = serializer.serializeToString(svg);

const img = document.createElement("img")
img.src = 'data:image/svg+xml;base64,'+ window.btoa(svgStr);

const target = document.querySelector("#target");
target.appendChild(img);
svg.parentNode.removeChild(svg);

Now, if we want to do this in React, I assume we can do it something like this:

import React from "react";
import { QRCode } from "react-qr-svg";

  class Demo extends React.Component {
      render() {
        const svg = function {
          return (<QRCode
                  level="Q"
                  style={{width: 256, marginBottom: 50 }}
                  value={'hello world'}
              />);
        };
        const serializer = new XMLSerializer();
        const svgStr = serializer.serializeToString(svg);
        const img_src = 'data:image/svg+xml;base64,'+ window.btoa(svgStr);

          return (
            <img src={ img_src }/>
          );
      }
  } 

OR we could probably do it with lifecycle hooks such as componentDidMount()

import React from "react";
import { QRCode } from "react-qr-svg";

  class Demo extends React.Component {
    componentDidMount() {
        const div = this.refs.target;
        const svg = div.querySelector("svg");
        const serializer = new XMLSerializer();
        const svgStr = serializer.serializeToString(svg);
        const img = this.refs.img;
        img.src = 'data:image/svg+xml;base64,'+ window.btoa(svgStr);
        svg.parentNode.removeChild(svg);
      }

    render() {                
      return (<div ref="target">
                <QRCode
                level="Q"
                style={{width: 256, marginBottom: 50 }}
                value={'hello world'}
                />
              <img ref="img"/>
              </div>
            ) 
      }
  } 

UPDATE - I have it working in React on CodePen: https://codepen.io/Alexander9111/pen/GRJKQQK

HTML: <div id="root"></div>

CSS: svg{border: 2px solid black;} img{border: 2px solid blue;}

React JS:

class Demo extends React.Component {
  componentDidMount() {
    const div = this.refs.target;
    const svg = div.querySelector("svg");
    console.log(svg);
    const serializer = new XMLSerializer();
    const svgStr = serializer.serializeToString(svg);
    const img = this.refs.img;
    console.log(img);
    img.src = 'data:image/svg+xml;base64,'+ window.btoa(svgStr);
    svg.parentNode.removeChild(svg);
  }
  render() {                
      return (
        <div ref="target">
          <svg width="400" height="400">
            <circle r="100" cx="200" cy="200" fill="red" />
          </svg>
          <img ref="img"/>
        </div>
      ); 
    }
} 

ReactDOM.render(
  <Demo/>,
  document.getElementById('root')
);
Chirpy answered 1/2, 2020 at 0:0 Comment(9)
That is not possible since it won't render the SVG on the PDF file. The console prints "Uncaught Error: Invalid element of type rect passed to PDF renderer". I know that svg and rect are not react-pdf primitives and won't print on the document, I'm asking if someone could point me to the right direction to achieve this.Baguio
ahh ok, now I understood what you mean. There should be a way to do it serializing the svg to string. Let me try to put a demo together. I am assuming it all has to be client-side?Chirpy
Any luck with my answer(s) Eddy?Chirpy
I have it working in React on CodePen: codepen.io/Alexander9111/pen/GRJKQQK - see my updated answerChirpy
Sorry, I couldn't test until now. It's still not working because serializeToString is taking a function as a parameter instead of an object. I'm trying to find a workaround for that.Baguio
serializer.serializeToString(svg); is taking the svg element as its argument? It works for me in my CodePen. Hope you can make it work. How does <QRCode/> render normally on the page? it must be like <svg> ... </svg> right?Chirpy
I'm using a bit of your code to try to return only the base 64 string and came up with this const QrPdf = () => { const svg = <QRCode level="Q" value={'hello world'} /> const serializer = new XMLSerializer(); const svgStr = serializer.serializeToString(svg); const img = this.refs.img; console.log(img); return 'data:image/svg+xml;base64,'+ window.btoa(svgStr); } export default QrPdf; This is where serializer.serializeToString(svg); is failingBaguio
You have to make <QRCode /> render first I assume, so that it converts to <svg>...</svg> etc. I don't think you can pass it directly as JSX - There is no CDN version of that package so I can't test it in a CodePen etc. :(Chirpy
Anyone get this option to work? I'm struggling here. "Argument type JSX.Element is not assignable to parameter type Node"Molton
G
11

I use qrcode.react with @react-pdf/renderer. First thing you need to do is to convert your QR canvas to base64

const qrCodeCanvas = document.querySelector('canvas');
const qrCodeDataUri = qrCodeCanvas.toDataURL('image/jpg', 0.3);

Then, pass base64 string (qrCodeDataUri) as a prop to your PDF component in source of @react-pdf/renderer image tag:

<Image source={ {uri: props.yourBase64Image} } />
Garrick answered 30/3, 2020 at 12:27 Comment(3)
Much nicer solution! (it would feel a bit cleaner if we could go straight from the qrccode component to the base64 data without having to write to the DOM - but this is still great!)Allhallowtide
Can you please add the complete code I am confused, since @react-pdf/renderer Document does not allow propsHollowell
@user158 try to import Image component from @react-pdf/renderer library and use that component.Garrick
C
3

Directly from the docs: https://www.npmjs.com/package/react-qr-svg

You don’t need to put it in an image tag.

My assumption is this library outputs <svg> ... </svg> (I.e. valid HTML tags to output directly)

You can just output the tag/element out directly?

  import React from "react";
  import { QRCode } from "react-qr-svg";

  class Demo extends React.Component {
      render() {
          return (
            <QRCode
            bgColor="#FFFFFF"
            fgColor="#000000"
            level="Q"
            style={{ width: 256 }}
            value="some text"
            />
          );
      }
  } 

UPDATE

If I understand correctly, we cannot simply output <svg>...</svg> on the page as the library @react-pdf/renderer will not work with svg tags. So I propose we serialize the svg to a base-64 string, assigning that to the src of the image tag and then it should work.

I put a simplified demo together without libraries:https://codepen.io/Alexander9111/pen/QWweYXO

HTML (simplified):

<svg>    
    <ellipse class="ground" fill="##787f6a" cx="283.5" cy="487.5" rx="259" ry="80"/>
    <path class="kiwi" fill="#94d31b" d="M210.333,65.331C104.367,...,203.01z"/>
</svg>
<div id="target">
    <!-- place to put our img tag -->
</div>

JS:

const svg = document.querySelector("svg");
const serializer = new XMLSerializer();
const svgStr = serializer.serializeToString(svg);

const img = document.createElement("img")
img.src = 'data:image/svg+xml;base64,'+ window.btoa(svgStr);

const target = document.querySelector("#target");
target.appendChild(img);
svg.parentNode.removeChild(svg);

Now, if we want to do this in React, I assume we can do it something like this:

import React from "react";
import { QRCode } from "react-qr-svg";

  class Demo extends React.Component {
      render() {
        const svg = function {
          return (<QRCode
                  level="Q"
                  style={{width: 256, marginBottom: 50 }}
                  value={'hello world'}
              />);
        };
        const serializer = new XMLSerializer();
        const svgStr = serializer.serializeToString(svg);
        const img_src = 'data:image/svg+xml;base64,'+ window.btoa(svgStr);

          return (
            <img src={ img_src }/>
          );
      }
  } 

OR we could probably do it with lifecycle hooks such as componentDidMount()

import React from "react";
import { QRCode } from "react-qr-svg";

  class Demo extends React.Component {
    componentDidMount() {
        const div = this.refs.target;
        const svg = div.querySelector("svg");
        const serializer = new XMLSerializer();
        const svgStr = serializer.serializeToString(svg);
        const img = this.refs.img;
        img.src = 'data:image/svg+xml;base64,'+ window.btoa(svgStr);
        svg.parentNode.removeChild(svg);
      }

    render() {                
      return (<div ref="target">
                <QRCode
                level="Q"
                style={{width: 256, marginBottom: 50 }}
                value={'hello world'}
                />
              <img ref="img"/>
              </div>
            ) 
      }
  } 

UPDATE - I have it working in React on CodePen: https://codepen.io/Alexander9111/pen/GRJKQQK

HTML: <div id="root"></div>

CSS: svg{border: 2px solid black;} img{border: 2px solid blue;}

React JS:

class Demo extends React.Component {
  componentDidMount() {
    const div = this.refs.target;
    const svg = div.querySelector("svg");
    console.log(svg);
    const serializer = new XMLSerializer();
    const svgStr = serializer.serializeToString(svg);
    const img = this.refs.img;
    console.log(img);
    img.src = 'data:image/svg+xml;base64,'+ window.btoa(svgStr);
    svg.parentNode.removeChild(svg);
  }
  render() {                
      return (
        <div ref="target">
          <svg width="400" height="400">
            <circle r="100" cx="200" cy="200" fill="red" />
          </svg>
          <img ref="img"/>
        </div>
      ); 
    }
} 

ReactDOM.render(
  <Demo/>,
  document.getElementById('root')
);
Chirpy answered 1/2, 2020 at 0:0 Comment(9)
That is not possible since it won't render the SVG on the PDF file. The console prints "Uncaught Error: Invalid element of type rect passed to PDF renderer". I know that svg and rect are not react-pdf primitives and won't print on the document, I'm asking if someone could point me to the right direction to achieve this.Baguio
ahh ok, now I understood what you mean. There should be a way to do it serializing the svg to string. Let me try to put a demo together. I am assuming it all has to be client-side?Chirpy
Any luck with my answer(s) Eddy?Chirpy
I have it working in React on CodePen: codepen.io/Alexander9111/pen/GRJKQQK - see my updated answerChirpy
Sorry, I couldn't test until now. It's still not working because serializeToString is taking a function as a parameter instead of an object. I'm trying to find a workaround for that.Baguio
serializer.serializeToString(svg); is taking the svg element as its argument? It works for me in my CodePen. Hope you can make it work. How does <QRCode/> render normally on the page? it must be like <svg> ... </svg> right?Chirpy
I'm using a bit of your code to try to return only the base 64 string and came up with this const QrPdf = () => { const svg = <QRCode level="Q" value={'hello world'} /> const serializer = new XMLSerializer(); const svgStr = serializer.serializeToString(svg); const img = this.refs.img; console.log(img); return 'data:image/svg+xml;base64,'+ window.btoa(svgStr); } export default QrPdf; This is where serializer.serializeToString(svg); is failingBaguio
You have to make <QRCode /> render first I assume, so that it converts to <svg>...</svg> etc. I don't think you can pass it directly as JSX - There is no CDN version of that package so I can't test it in a CodePen etc. :(Chirpy
Anyone get this option to work? I'm struggling here. "Argument type JSX.Element is not assignable to parameter type Node"Molton
R
1

My solution does not write the initial svg to the DOM, instead it converts the react component to static markup. Then I parse that markup to use its properties in the Svg component that @react-pdf/renderer supports.

import React from 'react';
import { Text, View, Svg, Path } from '@react-pdf/renderer';
import { renderToStaticMarkup } from 'react-dom/server';
import ReactHtmlParser from 'react-html-parser';
import QRCode from 'qrcode.react';

const PdfWithQrCode = () => {
    const qrCodeComponent = (
        <QRCode
            value={ssf_id}
            renderAs="svg"
            size={80}
        />
    );

    const qrCodeComponentStaticMarkup = renderToStaticMarkup(qrCodeComponent);

    const parsedQrCodeSvg = parseQrCodeMarkup(qrCodeComponentStaticMarkup);
    if (! parsedQrCodeSvg) {
        return null;
    }

    return (
        <View>
            <Svg
                style={{ width: 50, height: 50 }}
                viewBox="0 0 29 29"
            >
                {parsedQrCodeSvg.props.children.filter(c => c.type === 'path').map((child, index) => (
                    <Path
                        key={index}
                        d={child.props.d}
                        fill={child.props.fill}
                    />
                ))}
            </Svg>
        </View>
    );
}

const parseQrCodeMarkup = (markup) => {
    let parsedQrCodeSvg = null;

    ReactHtmlParser(markup).forEach(el => {
        const { type } = el;
        if (type === 'svg') {
            parsedQrCodeSvg = el;
        }
    });

    return parsedQrCodeSvg;
};

export default PdfWithQrCode;
Revolutionize answered 9/2, 2022 at 12:40 Comment(0)
M
0

The docs specify that Image supports promises of base64 strings. Source https://react-pdf.org/components#source-object

So, we can use the qrcode to generate it. I am using typescript here, so be sure to install @types/qrcode for this to work.

npm install qrcode @types/qrcode
import QRCode from "qrcode";
import { Image } from "@react-pdf/renderer";

export function QRCodeImage(props: { url: string }) {
  const urlPromise = QRCode.toDataURL(props.url);
  return <Image src={urlPromise} />;
}

Mombasa answered 31/10, 2023 at 0:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.