Search code examples
javascriptreactjssvgqr-codereact-pdf

Printing generated QR codes on a PDF file


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?


Solution

  • 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')
    );