Drawing text to <canvas> with @font-face does not work at the first time

85,742

Solution 1

Drawing on canvas has to happen and return immediately when you call the fillText method. However, the browser has not yet loaded the font from the network, which is a background task. So it has to fall back to the font it does have available.

If you want to make sure the font is available, have some other element on the page preload it, eg.:

<div style="font-family: PressStart;">.</div>

Solution 2

Use this trick and bind an onerror event to an Image element.

Demo here: works on the latest Chrome.

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'http://fonts.googleapis.com/css?family=Vast+Shadow';
document.getElementsByTagName('head')[0].appendChild(link);

// Trick from https://stackoverflow.com/questions/2635814/
var image = new Image();
image.src = link.href;
image.onerror = function() {
    ctx.font = '50px "Vast Shadow"';
    ctx.textBaseline = 'top';
    ctx.fillText('Hello!', 20, 10);
};

Solution 3

You can load fonts with the FontFace API before using it in the canvas:

const myFont = new FontFace('My Font', 'url(https://myfont.woff2)');

myFont.load().then((font) => {
  document.fonts.add(font);

  console.log('Font loaded');
});

The font resource myfont.woff2 is first downloaded. Once the download completes, the font is added to the document's FontFaceSet.

The specification of the FontFace API is a working draft at the time of this writing. See browser compatibility table here.

Solution 4

The nub of the problem is that you are trying to use the font but the browser has not loaded it yet and possibly has not even requested it. What you need is something that will load the font and give you a callback once it is loaded; once you get the callback, you know it is okay to use the font.

Look at Google's WebFont Loader; it seems like a "custom" provider and an active callback after the load would make it work.

I've never used it before, but from a quick scan of the docs you need to make a css file fonts/pressstart2p.css, like this:

@font-face {
  font-family: 'Press Start 2P';
  font-style: normal;
  font-weight: normal;
  src: local('Press Start 2P'), url('http://lemon-factory.net/reproduce/fonts/Press Start 2P.ttf') format('ttf');
}

Then add the following JS:

  WebFontConfig = {
    custom: { families: ['Press Start 2P'],
              urls: [ 'http://lemon-factory.net/reproduce/fonts/pressstart2p.css']},
    active: function() {
      /* code to execute once all font families are loaded */
      console.log(" I sure hope my font is loaded now. ");
    }
  };
  (function() {
    var wf = document.createElement('script');
    wf.src = ('https:' == document.location.protocol ? 'https' : 'http') +
        '://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
    wf.type = 'text/javascript';
    wf.async = 'true';
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(wf, s);
  })();

Solution 5

What about using simple CSS to hide a div using the font like this:

CSS:

#preloadfont {
  font-family: YourFont;
  opacity:0;
  height:0;
  width:0;
  display:inline-block;
}

HTML:

<body>
   <div id="preloadfont">.</div>
   <canvas id="yourcanvas"></canvas>
   ...
</body>
Share:
85,742

Related videos on Youtube

lemonedo
Author by

lemonedo

Updated on July 08, 2022

Comments

  • lemonedo
    lemonedo almost 2 years

    When I draw a text in a canvas with a typeface that is loaded via @font-face, the text doesn't show correctly. It doesn't show at all (in Chrome 13 and Firefox 5), or the typeface is wrong (Opera 11). This type of unexpected behavior occurs only at the first drawing with the typeface. After then everything works fine.

    Is it the standard behavior or something?

    Thank you.

    PS: Following is the source code of the test case

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <title>@font-face and &lt;canvas&gt;</title>
            <style id="css">
    @font-face {
        font-family: 'Press Start 2P';
        src: url('fonts/PressStart2P.ttf');
    }
            </style>
            <style>
    canvas, pre {
        border: 1px solid black;
        padding: 0 1em;
    }
            </style>
        </head>
        <body>
            <h1>@font-face and &lt;canvas&gt;</h1>
            <p>
                Description: click the button several times, and you will see the problem.
                The first line won't show at all, or with a wrong typeface even if it does.
                <strong>If you have visited this page before, you may have to refresh (or reload) it.</strong>
            </p>
            <p>
                <button id="draw">#draw</button>
            </p>
            <p>
                <canvas width="250" height="250">
                    Your browser does not support the CANVAS element.
                    Try the latest Firefox, Google Chrome, Safari or Opera.
                </canvas>
            </p>
            <h2>@font-face</h2>
            <pre id="view-css"></pre>
            <h2>Script</h2>
            <pre id="view-script"></pre>
            <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
            <script id="script">
    var x = 30,
        y = 10;
    
    $('#draw').click(function () {
        var canvas = $('canvas')[0],
            ctx = canvas.getContext('2d');
        ctx.font = '12px "Press Start 2P"';
        ctx.fillStyle = '#000';
        ctx.fillText('Hello, world!', x, y += 20);
        ctx.fillRect(x - 20, y - 10, 10, 10);
    });
            </script>
            <script>
    $('#view-css').text($('#css').text());
    $('#view-script').text($('#script').text());
            </script>
        </body>
    </html>
    
  • Keyur Padalia
    Keyur Padalia almost 14 years
    You might be tempted to add display: none, but that might cause browsers to skip loading the font. It's better to use a space instead of a ..
  • Amit Patil
    Amit Patil almost 14 years
    Using a space will cause IE to throw away the whitespace node that should be in the div, leaving no text to render in the font. Of course IE doesn't support canvas yet, so it's unknown whether future-IE will continue to do this, and whether that would have an effect on font loading behaviour, but it's a long-standing IE HTML-parsing problem.
  • Ryan Badour
    Ryan Badour almost 13 years
    Doesn't seem to work: Tested in Chrome 12 Edit: Refresh a few times for it to miss the font
  • Joshua
    Joshua about 12 years
    Is there no easier way to preload the font? e.g. force it through javascript somehow?
  • Amit Patil
    Amit Patil about 12 years
    @Joshua: only by creating an element on the page and setting the font on it, ie. creating the same content as above but dynamically.
  • Nick
    Nick about 12 years
    Adding this does not guarantee that the font will already be loaded when the JavaScript is executed. I had to execute my script using the font in question delayed (setTimeout) which, of course, is bad.
  • magma
    magma over 11 years
    smart trick. note though that you're loading the css which, in turn, contains a reference to the real font file (e.g., .ttf, .woff, etc). I had to use your trick twice, once for the css file, and once for the referenced font file (.woff) to make sure that everything was loaded.
  • grapien
    grapien about 11 years
    I have added some code above to illustrate my answer. I had a similar problem when developing another webpage and this solved it as on a the server end it loads all the fonts thus allows them to display correctly on the webpage.
  • ccnokes
    ccnokes almost 10 years
    While this technique is probably the simplest way to ensure the font is loaded, it doesn't work for me in IE 9 and 10. The function provided here: stackoverflow.com/questions/5680013/… did the trick. What technique you use depends on your particular use cases.
  • Mikhail Fiadosenka
    Mikhail Fiadosenka about 9 years
    Tried this approach with .ttf font - doesn't work stably on Chrome (41.0.2272.101 m). Even the setTimeout in 5 seconds doesn't help - first render goes with default font.
  • Big McLargeHuge
    Big McLargeHuge almost 9 years
    This did not work for me in Chrome 42.0.2311.135 (latest). ellisbben's answer did.
  • asdjfiasd
    asdjfiasd over 8 years
    You should set onerror handler before you set src
  • asdjfiasd
    asdjfiasd over 8 years
    also "new Image;" has missing parenthesis.
  • a paid nerd
    a paid nerd over 8 years
    @asdjfiasd The parens aren't required and, even though the src attribute is set, loading will begin in the next execution block.
  • Robert Monfera
    Robert Monfera almost 8 years
    I'd add that it's not sufficient to force the load of the font-family. If, for example, a specific font weight is desired, then that font weight has to be loaded, e.g. by using that font weight on the mock div.
  • Pacerier
    Pacerier over 6 years
    You're missing document.fonts.add. See bruno's answer.
  • Fred Bergman
    Fred Bergman over 6 years
    Thanks @Pacerier ! Updated my answer.
  • Michael Zelensky
    Michael Zelensky about 5 years
    This technology is experimental and not supported by most of the browsers.
  • Miguel Garcia
    Miguel Garcia almost 4 years
    Wondering if this answer could be updated to use link preload. Something like <link rel="preload" href="fonts/PressStart2P.ttf" as="font" type="font/ttf"> at the top of the html file would allow the font file to be loaded prior to the css. This would work in Chrome and Edge at least
  • Kevin Shiflett
    Kevin Shiflett over 3 years
    Could you do something like this with a local font?
  • Kamil Kiełczewski
    Kamil Kiełczewski over 2 years
    great answer - thank you