2

This is a complicated one. I've created a Chrome Extension that will receive JSON-RPC requests over a WebSocket from a client, render text using HTML decoration, and respond back to the client with a PNG image. I'm using a Canvas and an SVG element to render the text using the foreignObject tag that contains the HTML I'm rendering. This all works fantastic except for when I need to use an external font. There are three levels of indirection happening here, so maybe I'm asking too much of the browser; but I hope not.

You might think that rendering text directly to an SVG would be an option, but that means I would lose some of the rich features provided by HTML like word-wrap. There are hacks for word-wrap for SVGs, but I don't want to go down that road. That likely would be just the first of many problems I would encounter.

What I need is for the HTML inside of the SVG tag to recognize the external font I want it to use. This only needs to work in Chromium, so a solution specific to Chromium is a viable option for me.

<link rel='preload' href='https://fonts.gstatic.com/s/roboto/v27/KFOkCnqEu92Fr1MmgVxIIzI.woff2' as='font' crossorigin='anonymous' />
<style>@font-face { font-family: Roboto; src: url('https://fonts.gstatic.com/s/roboto/v27/KFOkCnqEu92Fr1MmgVxIIzI.woff2'); } body { font-family: Roboto; font-size: 28px; }</style>
<canvas style='position:absolute; top:90px; left:394px;' id='canvas'></canvas>
<div><span style="font-family:courier; font-size:20px">External straight-HTML (works): </span>Hello World!</div><br/>
<span style='font-family:courier; font-size:20px'>HTML within SVG (doesn't work): </span><br/><br/>
<span style='font-family:courier; font-size:20px'>&nbsp;&nbsp;Text directly to SVG (works): </span><br/><br/>

<script>

   // Get the canvas element from the above HTML and an associated context.
    var canvas = document.querySelector('#canvas');
    var ctx = canvas.getContext('2d');

    // Create an empty SVG image.
    var svg = new Image();

    // Before we set the svg.src value, we need to define what to do 
    // immediately after the svg element has completed loading.
    svg.onload = function() {

        // DOES NOT RENDER USING CORRECT FONT HERE.
        // Draw the image into the canvas context.  This is the HTML
        // source containing the foreign object that I wish to render
        // using the external font.
        ctx.drawImage(svg, 0, 0);
    
        // Prove that the SVG element itself can use the external font.
        // THIS WORKS, but is not what I want.
        ctx.font = "28px Roboto";
        ctx.fillText("Hello World!", 0, 80);
   }

    // Build HTML to create a SVG image generated from HTML.
    var source = "<svg xmlns='http://www.w3.org/2000/svg'>"
               + "<foreignObject width='2000' height='800' overflow='visible'>"
               + "<div xmlns='http://www.w3.org/1999/xhtml'>" 
               + "<link rel='preload' href='https://fonts.gstatic.com/s/roboto/v27/KFOkCnqEu92Fr1MmgVxIIzI.woff2' as='font' type='font/woff2' crossorigin='anonymous' />"
               + "<style>@font-face { font-family: Roboto; src: url('https://fonts.gstatic.com/s/roboto/v27/KFOkCnqEu92Fr1MmgVxIIzI.woff2); } body { font-family: Roboto; font-size: 28px; }</style>"
               + "<div>Hello World!</div>"
               + "</div>"
               + "</foreignObject>"
               + "</svg>";

    // This doesn't always work because sometimes the font isn't yet available even though we've theoretically preloaded the font.
    svg.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source);

</script>
Jerry
  • 89
  • 7
  • 2
    SVG used as an image must be self contained so you'd need to convert the font to a data URL and embed it into the SVG source. – Robert Longson Apr 05 '21 at 19:53
  • Thanks for the advice! I do remember seeing something about that, but I think the dots didn't connect in my head because a canvas lets you live-build an SVG without using a data URL. I see that the key here is by my using svg.src I'm effectively specifying a static SVG image (not building an SVG using canvas tools), and therefore that src image needs to contain everything needed to draw the SVG. I'll get right on that and hopefully post the answer here upon success. Thanks again! – Jerry Apr 06 '21 at 10:25

1 Answers1

4

Special thanks to Robert Longson for setting me on the right track. The primary issue was that when you're setting the SVG's src data value, your SVG data must contain EVERYTHING it needs to draw the image within that source data. This is also true when using a foreign object. This is very different than drawing an SVG image on an HTML canvas. So (along with some other minor changes) the external font itself must be part of that src data, and therefore you must provide a data URL containing that font data. The other thing I had to do was explicitly specify which font to use within the div's style.

<style id="myStyle">@font-face { font-family: Roboto; src: url('data:application/octet-stream;base64,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'); } body { font-family: Roboto; font-size: 28px; }</style>
<canvas style='position:absolute; top:75px; left:394px;' id='canvas'></canvas>
<div><span style="font-family:courier; font-size:20px">External straight-HTML (works): </span>Hello World!</div><br/>
<span style='font-family:courier; font-size:20px'>&nbsp;&nbsp;&nbsp;HTML within SVG (now works): </span><br/><br/>
<span style='font-family:courier; font-size:20px'>&nbsp;&nbsp;Text directly to SVG (works): </span><br/><br/>

<script>

    // Get the canvas element from the above HTML and an associated context.
    var canvas = document.querySelector('#canvas');
    var ctx = canvas.getContext('2d');

    // Create an empty SVG image.
    var svg = new Image();

    // Before we set the svg.src value, we need to define what to do 
    // immediately after the svg element has completed loading.
    svg.onload = function() {

        // Draw the image into the canvas context.  This is the HTML
        // source containing the foreign object that I wish to render
        // using the external font.  THIS NOW WORKS.
        ctx.drawImage(svg, 0, 0);
    
        // Prove that the SVG element itself can use the external font.
        // THIS WORKS, but is not what I want.
        ctx.font = "28px Roboto";
        ctx.fillText("Hello World!", 0, 95);
    }

    // Hacky way of getting the data URI from the above HTML.
    // I would have just duplicated the data URI below, but Stack 
    // Overflow limits the size of this source, and that would make
    // it too big, so I have to programmatically extract it.
    var dataUri = document.getElementById("myStyle").innerText.split("url('")[1].split("');")[0];
    
    // Build HTML to create a SVG image generated from HTML.
    var source = "<svg xmlns='http://www.w3.org/2000/svg'>"
    
               // Beginning of foreign object.
               + "<foreignObject width='2000' height='800' overflow='visible'><div xmlns='http://www.w3.org/1999/xhtml'>" 

               // This NOW WORKS inside of the foreign object.
               + "<style>@font-face { font-family: Roboto; src: url('" + dataUri + "'); }</style>"
               + "<div style='font-family:Roboto; font-size:28px'>Hello World!</div>"

               // End of foreign object.
               + "</div></foreignObject>"
               + "</svg>";

    // Set the SVG source data now.
    svg.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(source);

</script>
Jerry
  • 89
  • 7
  • But the texts "Hello World!" in the 2nd and 3rd lines are not the same as the text in the first line. The first line shows thinner, cleaner font. How can be this addressed to have precise, the same rendering? – Ωmega May 21 '22 at 16:40
  • Ωmega, what browser are you using? I'm getting consistent results using a Chromium-based browser. All three "Hello World!" texts look the same to me when running the code snippet directly above. – Jerry May 22 '22 at 20:04
  • My Chrome 101.0.4951.67 (Official Build) (64-bit) renders it like this: https://pasteboard.co/grouCaopeVCv.png – Ωmega May 22 '22 at 20:26
  • Ωmega, I think the answer is that the top line is scalable HTML, but the bottom two are actually images that won't scale when you zoom in. I can get the same results as the image you posted by zooming my browser to 200%. I'm guessing your browser is zoomed in when you're viewing this post. Try resetting your zoom to 100%. If you need a higher resolution SVG image, then when you generate the image I think you'd need to up the SVG resolution and also increase the size of the font being rendered. Does that make sense? – Jerry May 23 '22 at 16:45
  • SVG = Scalable Vector Graphics --- it should be scalable and clean during zooming. – Ωmega May 23 '22 at 18:23
  • The top image is an SVG that is actually rendered (and re-rendered) by your browser each time you zoom in. The bottom two images are rendered once at the specified resolution (2000 x 800), and the zoom performed by the browser is simply zooming in to the images generated at the specified resolution. That's the difference. – Jerry May 24 '22 at 12:32