Stevie Star
Stevie Star

Reputation: 2371

React Nodejs - Custom @font-face in jsx for PDF generation

I have a React project in which part of the functionality involves sending a request to a Nodejs server that runs PhantomJS and the html-pdf plugin in order to create a pdf that gets build via a react component from my node server. The problem I'm having is actually embedding custom fonts into my react component. I have a base64 encoded font set that would be easier to include versus a bunch of files, but due to how React loads in CSS, I keep getting errors.

Here is the function that gets called to generate the PDF on my NodeJS server:

exports.order = (req, res, next) => {

  phantom.create().then(function(ph) {
    ph.createPage().then(function(page) {
      // page.viewportSize = { width: 600, height: 600 };
      page.open(`http://localhost:3005/print/work/${req.params.id}`).then(function(status) {
        page.property('content').then(function(content) {
          pdf.create(content, options).toBuffer(function(err, buffer){

              res.writeHead(200, {
               'Content-Type': 'application/pdf',
               'Content-Disposition': `attachment; filename=order-${req.params.id}.pdf`,
               'Content-Length': buffer.length
             });
             res.end(buffer);
          });
          page.close();
          ph.exit();
        });
      });
    });
  });

}

Here's the actual React component that contains the HTML for the PDF:

var React = require('react');
var moment = require('moment');

class PrintWorkOrder extends React.Component {
  render() {
    var order = this.props;

    var s = {
      body: {
        width: '100%',
        float: 'none',
        fontFamily: 'Helvetica',
        color: '#000',
        display: 'block',
        fontSize: 10,
      }
    }
    return (
      <div style={s.body}>
         I shortened the content in her for brevity
      </div>
    )
  }
}

module.exports = PrintWorkOrder;

The functionality currently works, it's just now I need to add some custom fonts that aren't Helvetica and I'm having trouble solving that with this current implementation. Does anyone have any insight into this?

Upvotes: 1

Views: 1292

Answers (1)

vun
vun

Reputation: 1269

I also have the same issue, what I did is to encoded front into base64 and put it as

font.css

@font-face {
  font-family: 'fontName';
  src: url(data:application/x-font-woff;charset=utf-8;base64,<base64>) format('woff'),
  font-weight: normal;
  font-style: normal;
}

The trick is to read CSS file content and inject it into the DOM markup as style tag

React.createElement('style', { dangerouslySetInnerHTML: { __html: this.props.children } });

So to do it: For the second line of code, what it does it to create style element. You could do is to write a component that read content from the .css file(s) and then put the content inside style element like this:

Style.js

var React = require('react');
export default class Style extends React.Component {
    static propTypes = {
        children: React.PropTypes.string.isRequired
    };

    render() {
        return React.createElement('style', { dangerouslySetInnerHTML: { __html: this.props.children } });
    }
}

Modify PrintWorkOrder.js

var React = require('react');
var moment = require('moment');
var fs = require('fs');
var path = require('path');
var Style = require('./Style');

const readFile = (filePath) => fs.readFileSync(path.resolve(__dirname, filePath)).toString();

class PrintWorkOrder extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            styles: []
        }
    }

    componentWillMount() {
        this.setState({
            styles: [
                readFile('font.css') // Assuming putting font.css same location with PrintWorkOrder component
            ]
        })
    }

    render() {
        var order = this.props;

        var s = {
            body: {
                width: '100%',
                float: 'none',
                fontFamily: 'Helvetica',
                color: '#000',
                display: 'block',
                fontSize: 10,
            }
        }

        return (
            <html>
                <head>
                    {/* Here to render all the styles */}
                    {this.state.styles.map(s => (<Style>{s}</Style>))}
                </head>
                <body>
                    <div style={s.body}>
                        I shortened the content in her for brevity
                    </div>
                </body>
            </html>
        );
    }
}

module.exports = PrintWorkOrder;

Upvotes: 2

Related Questions