2

I am generating an svg, with text, using D3.js for a user to download.

Unfortunately, when an svg is downloaded, the font family applied to the text on the svg does not appear to be included when the svg is converted to canvas, then downloaded as a png.

The font styling appears to be missing.

Is there a way to construct the png with this font family applied?

To download the svg as a png, I adapted the answer described here.

Here is my process:

First, I create the svg.

     await d3.xml(`img_templates/${template}_${dimensions}.svg`)
        .then(data => {
        
            divsvg.append(data.documentElement) //Append svg to DOM 
           
        }); 

Then, I add my text elements to the svg

 const quoteText = quoteGroup.append("text")
            .attr("class", "quote")
            .style("font-size", `15px`)
            .style("font-family", 'Merriweather')

Merriweather is a Google font family that I load in the head of a html page like so:

 <head>
    <link href="https://fonts.googleapis.com/css2?family=Merriweather&display=swap" rel="stylesheet"> 
    <link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,700;1,400&display=swap" rel="stylesheet"> <!--Another font I use-->
    <script src="d3.min.js"></script>
    <script src=genImage.js></script> <!--The js code to interact with the svg --> 
  </head>

On download, I execute the following functions on the svg,

function downloadSVG(svgObj){
  
    var data = (new XMLSerializer()).serializeToString(svgObj);
     
    var DOMURL = window.URL || window.webkitURL || window;
    var img = new Image();
    var svgBlob = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
    var url = DOMURL.createObjectURL(svgBlob);
    
    img.onload = function () {
        var canvas = document.createElement("canvas");              
        canvas.width = this.width; 
        canvas.height = this.height; 
        var ctx = canvas.getContext("2d");

        ctx.drawImage(img, 0, 0);
        DOMURL.revokeObjectURL(url);
    
        var imgURI = canvas
            .toDataURL('image/png')
            .replace('image/png', 'image/octet-stream');
        // console.log(imgURI)
        triggerDownload(imgURI);
    };
    
    img.src = url;
}



function triggerDownload (imgURI) {
    var evt = new MouseEvent('click', {
      view: window,
      bubbles: false,
      cancelable: true
    });
  
    var a = document.createElement('a');
    a.setAttribute('download', 'MY_COOL_IMAGE.png');
    a.setAttribute('href', imgURI);
    a.setAttribute('target', '_blank');
  
    a.dispatchEvent(evt);
  }

When serialized to string the svg appears as such:

<svg xmlns="http://www.w3.org/2000/svg" width="484" height="484" viewBox="0 0 484 484" fill="none">

<g class="quoteGroup" transform="translate(42,100)">
<text class="quote" fill="#FFFFFF" style="font-size: 25px; font-family: Merriweather;"><tspan x="0">"I’m staying alone in Montauk at a</tspan><tspan x="0" dy="40px">friend’s house while he’s in Portugal</tspan><tspan x="0" dy="40px">(something about taxes and quality of</tspan><tspan x="0" dy="40px">life). After my morning coffee and</tspan><tspan x="0" dy="40px">power shake, it’s time to water the</tspan><tspan x="0" dy="40px">plants."</tspan></text>
</g>

<g class="headlineGroup" transform="translate(42,365)">
<text class="headline" font-size="15px" fill="#FFFFFF" style="font-family: Roboto; font-style: italic;"><tspan x="0">Stop Thinking of the Breakup of Big Tech as Punishment</tspan></text>
</g>

<g class="publisherGroup" transform="translate(42,430)">
<text class="publisher" font-size="15px" fill="#FFFFFF" style="font-family: Roboto; font-weight: bold;">Marker</text>
</g>

<g class="dateGroup" transform="translate(353,430)"><text class="date" font-size="15px" fill="#FFFFFF" style="font-family: Roboto; font-weight: bold;">Aug 04, 2020</text></g>

</svg>

From what I can tell, the font family is being correctly specified in the string, yet when the svg is converted to canvas and then png, it is being lost. Why might this happen?

Please let me know if I can provide any additional information. Any assistance is greatly appreciated!

abrezey
  • 135
  • 9
  • 3
    because the font definitions are not in the SVG they are in the HTML document and the BLOB can't read them from there. – Robert Longson Aug 06 '20 at 16:59
  • That makes sense, is there a place I can put the font definitions to make them accessible to the BLOB? I also have the fonts downloaded as .ttf files too – abrezey Aug 06 '20 at 17:03
  • You could try URL encoding them (or base64 encoding them) and embedding them into the blob. – Robert Longson Aug 06 '20 at 17:09

1 Answers1

1

To anybody who finds this with the same problem, I was able to figure it out.

Thanks to Mr. Longson's suggestion, I found the following answer by Kaiido on this question.

Kaiido's GFontToDataURI method worked perfectly. I incorporated it into my code like so:

            GFontToDataURI("https://fonts.googleapis.com/css2?family=Merriweather&display=swap")
                .then(cssRules => {
                    let fontRules = cssRules.join('\n')
                    d3.select("svg").append('defs').append('style').attr('type', 'text/css').text(fontRules)
                    console.log("Added Merriweather")
                })
                .catch(reason => console.log(reason)) 
            
            GFontToDataURI("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,700;1,400&display=swap")
                .then(cssRules => {
                    let fontRules = cssRules.join('\n')
                    d3.select("svg").append('defs').append('style').attr('type', 'text/css').text(fontRules)
                    console.log("Added roboto")
                })
                .catch(reason => console.log(reason)) 
abrezey
  • 135
  • 9