Search code examples
node.jssvgserverdownloadwebfonts

Downloading google font via nodejs, live convert to base64 for use in css datauri


The end goal is to generate and serve an svg which inlines all external elements as datauris, so that the resulting svg can be used within html5 canvas to generate images on the fly.

I've managed to get downloading and embedding images on the fly on the server working in the output svg, but the svgs also include google fonts which the user can select at random, so also need to download the specified font file from google, convert it to base64, and put that into the svg's css as a datauri in a @font-face.

So far the fonts just refuse to work.

If I use an online service to convert the exact same font file and paste that in place of my converted font datauri, it works, so there's something wrong with either how I'm downloading the font, or how I'm converting it to base64.

All the downloading and converting is happening on the server so I can server up an 'all-in-one' svg. I'm using Firebase's Cloud Functions which uses nodejs.

I've got it so I can download the google font import file (eg: https://fonts.googleapis.com/css?family=Comfortaa), which returns a css @font-face import string, eg:

@font-face {
  font-family: 'Comfortaa';
  font-style: normal;
  font-weight: 400;
  src: local('Comfortaa Regular'), local('Comfortaa-Regular'), url(https://fonts.gstatic.com/s/comfortaa/v12/1Ptsg8LJRfWJmhDAuUs4QIFqL_KWxWMT.woff2) format('woff2');
}

I then extract the url of the actual font file (ie: https://fonts.gstatic.com/s/comfortaa/v12/1Ptsg8LJRfWJmhDAuUs4QIFqL_KWxWMT.woff2) and use nodejs' https to download the font and return it as a promise.

function getFontToImport( file ) {
    return new Promise( ( resolve , reject ) => {
        https.get( file , ( res ) => {
            const { statusCode } = res;
            const contentType = res.headers['content-type'];
            let error;
            if (statusCode !== 200) {
                error = new Error('Request Failed.\n' + `Status Code: ${statusCode}`);
            }
            if ( error ) {
                // consume response data to free up memory
                res.resume();
                resolve( error ) ;
            }

            //res.setEncoding('utf8');
            let rawData = '';
            res.on('data', (chunk) => { rawData += chunk; });
            res.on('end', () => {
                try {
                    resolve( rawData ) ;
                } catch ( e ) {
                  console.log( 'Could not log the file' ) ;
                  console.error(e.message);
                  resolve( e.message ) ;
                }
            });
        }).on( 'error' , (e) => {
          console.error( 'Got error:' + e.message ) ;
          resolve( e.message ) ;    
        });    
    }) ;    
}

I then convert that data to base64 later, using:

const base_64 = new Buffer( rawData ).toString( 'base64' );

I inject that into my svg code in the @font-face css I create, but the font fails to display. If I convert the same font using an online converter to base64 it, and paste that in place of my base64 datauri in the @font-face, the text displays properly, so the problem is something to do with how I download and/or convert the font.

When I compare a version of the font downloaded via my nodejs function to one the same file downloaded via my browser, they do look quite different.

Here's a link of page using the functions I'm working on with some different outputs: https://us-central1-project-7161459789373699890.cloudfunctions.net/font64?font=Comfortaa

I think the problem is specifically in how I'm downloading the font. I don't really know node so I just copied an example and it worked well for the images. It didn't work for the font so I took out the encoding as suggested but still no luck.

If anyone can give any suggestions or offer clarification on the difference between the font data loading in via node vs the font data that downloads by using a browser to access the font link directly, I'd be massively thankful.


Solution

  • Has to set the encoding to 'binary'.

    By default it's set to utf8, so removing the encoding setting was misleading me to think the data would come through in its default encoding. Setting the encoding to binary allowed the data to come through in the right format, which then allowed it to be converted to base64 correctly, which allowed it to display correctly when used as a datauri.