Desenhar texto para <canvas> com @ font-face não funciona na primeira vez

93

Quando desenho um texto em uma tela com uma fonte carregada por meio de @ font-face, o texto não é exibido corretamente. Ele não é mostrado (no Chrome 13 e Firefox 5) ou a fonte está errada (Opera 11). Esse tipo de comportamento inesperado ocorre apenas no primeiro desenho com a fonte. Depois disso, tudo funciona bem.

É o comportamento padrão ou algo assim?

Obrigado.

PS: A seguir está o código-fonte do caso de teste

<!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>
Lemonedo
fonte
1
Os navegadores carregam a fonte em segundo plano, de forma assíncrona. Este é um comportamento normal. Ver também paulirish.com/2009/fighting-the-font-face-fout
Thomas

Respostas:

71

O desenho na tela deve acontecer e retornar imediatamente quando você chamar o fillTextmétodo. No entanto, o navegador ainda não carregou a fonte da rede, o que é uma tarefa em segundo plano. Portanto, ele precisa voltar à fonte que está disponível.

Se você quiser ter certeza de que a fonte está disponível, faça com que algum outro elemento na página pré-carregue-a, por exemplo:

<div style="font-family: PressStart;">.</div>
bobince
fonte
3
Você pode ficar tentado a adicionar display: none, mas isso pode fazer com que os navegadores ignorem o carregamento da fonte. É melhor usar um espaço em vez de um ..
Thomas
6
Usar um espaço fará com que o IE jogue fora o nó de espaço em branco que deveria estar no div, não deixando nenhum texto para renderizar na fonte. É claro que o IE ainda não oferece suporte ao canvas, portanto, não se sabe se o futuro IE continuará a fazer isso e se isso teria um efeito no comportamento de carregamento de fontes, mas é um problema antigo de análise de HTML do IE.
bobince
1
Não há maneira mais fácil de pré-carregar a fonte? por exemplo, forçar através do javascript de alguma forma?
Joshua
@Joshua: apenas criando um elemento na página e definindo a fonte nele, ou seja, criando o mesmo conteúdo acima, mas dinamicamente.
bobince
17
Adicionar isso não garante que a fonte já estará carregada quando o JavaScript for executado. Tive que executar meu script usando a fonte em questão atrasada (setTimeout) que, claro, é ruim.
Nick
23

Use este truque e vincule um onerrorevento a um Imageelemento.

Demonstração aqui : funciona no Chrome mais recente.

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 /programming/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);
};
um nerd pago
fonte
3
truque inteligente. note que você está carregando o css que, por sua vez, contém uma referência ao arquivo de fonte real (por exemplo, .ttf, .woff, etc). Tive de usar o seu truque duas vezes, uma para o arquivo css e outra para o arquivo de fonte referenciado (.woff) para ter certeza de que tudo foi carregado.
magma
1
Tentei essa abordagem com a fonte .ttf - não funciona de forma estável no Chrome (41.0.2272.101 m). Mesmo o setTimeout em 5 segundos não ajuda - a primeira renderização vem com a fonte padrão.
Mikhail Fiadosenka
2
Você deve definir o manipulador onerror antes de definir src
asdjfiasd
1
também "nova imagem;" faltam parênteses.
asdjfiasd
@asdjfiasd Os parênteses não são necessários e, mesmo que o srcatributo seja definido, o carregamento começará no próximo bloco de execução.
um nerd pago de
16

O cerne do problema é que você está tentando usar a fonte, mas o navegador ainda não a carregou e possivelmente nem a solicitou. O que você precisa é de algo que carregue a fonte e forneça um retorno de chamada quando ela for carregada; depois de obter o retorno de chamada, você sabe que pode usar a fonte.

Veja o WebFont Loader do Google ; parece um provedor "personalizado" e um activeretorno de chamada após o carregamento faria com que funcionasse.

Eu nunca usei antes, mas a partir de uma rápida verificação dos documentos você precisa fazer um arquivo css fonts/pressstart2p.css, assim:

@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');
}

Em seguida, adicione o seguinte 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);
  })();
Ellisbben
fonte
13

Você pode carregar fontes com a API FontFace antes de usá-la na tela:

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

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

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

O recurso de fonte myfont.woff2é baixado primeiro. Após a conclusão do download, a fonte é adicionada ao FontFaceSet do documento .

A especificação da API FontFace é um rascunho de trabalho no momento da redação deste artigo. Veja a tabela de compatibilidade do navegador aqui.

Fred Bergman
fonte
1
Você está faltando document.fonts.add. Veja a resposta de bruno.
Pacerier
Obrigado @Pacerier! Atualizei minha resposta.
Fred Bergman
Esta tecnologia é experimental e não é suportada pela maioria dos navegadores.
Michael Zelensky
11

Que tal usar CSS simples para ocultar um div usando a fonte como esta:

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>
Gabriel Gonzalez
fonte
6

https://drafts.csswg.org/css-font-loading/

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

myFont.load().then(function(font){

  // with canvas, if this is ommited won't work
  document.fonts.add(font);

  console.log('Font loaded');

});
Bruno Andrade
fonte
1

Não tenho certeza se isso vai te ajudar, mas para resolver o problema com meu código, simplesmente criei um loop for no topo do meu Javascript que percorreu todas as fontes que eu queria carregar. Em seguida, executei uma função para limpar a tela e pré-carregar os itens que queria na tela. Até agora, funcionou perfeitamente. Essa foi a minha lógica. Postei meu código abaixo:

var fontLibrary = ["Acme","Aladin","Amarante","Belgrano","CantoraOne","Capriola","CevicheOne","Chango","ChelaOne","CherryCreamSoda",
"ConcertOne","Condiment","Damion","Devonshire","FugazOne","GermaniaOne","GorditasBold","GorditasRegular",
"KaushanScript","LeckerliOne","Lemon","LilitaOne","LuckiestGuy","Molle","MrDafoe","MrsSheppards",
"Norican","OriginalSurfer","OswaldBold","OswaldLight","OswaldRegular","Pacifico","Paprika","Playball",
"Quando","Ranchers","SansitaOne","SpicyRice","TitanOne","Yellowtail","Yesteryear"];

    for (var i=0; i < fontLibrary.length; i++) {
        context.fillText("Sample",250,50);
        context.font="34px " + fontLibrary[i];
    }

    changefontType();

    function changefontType() {
        selfonttype = $("#selfontype").val();
        inputtextgo1();
    }

    function inputtextgo1() {
        var y = 50;
        var lineHeight = 36;
        area1text = document.getElementById("bag1areatext").value;
        context.clearRect(0, 0, 500, 95)
        context.drawImage(section1backgroundimage, 0, 0);
        context.font="34px " + selfonttype;
        context.fillStyle = seltextcolor;
        context.fillText(area1text, 250, y);
    }
Grapien
fonte
Eu adicionei algum código acima para ilustrar minha resposta. Eu tive um problema semelhante ao desenvolver outra página da web e isso resolveu porque no final do servidor carrega todas as fontes, portanto, permite que sejam exibidas corretamente na página da web.
grapien
1

Escrevi um jsfiddle incorporando a maioria das correções sugeridas aqui, mas nenhuma resolveu o problema. No entanto, sou um programador novato, então talvez não tenha codificado as correções sugeridas corretamente:

http://jsfiddle.net/HatHead/GcxQ9/23/

HTML:

<!-- you need to empty your browser cache and do a hard reload EVERYTIME to test this otherwise it will appear to working when, in fact, it isn't -->

<h1>Title Font</h1>

<p>Paragraph font...</p>
<canvas id="myCanvas" width="740" height="400"></canvas>

CSS:

@import url(http://fonts.googleapis.com/css?family=Architects+Daughter);
 @import url(http://fonts.googleapis.com/css?family=Rock+Salt);
 canvas {
    font-family:'Rock Salt', 'Architects Daughter'
}
.wf-loading p {
    font-family: serif
}
.wf-inactive p {
    font-family: serif
}
.wf-active p {
    font-family:'Architects Daughter', serif;
    font-size: 24px;
    font-weight: bold;
}
.wf-loading h1 {
    font-family: serif;
    font-weight: 400;
    font-size: 42px
}
.wf-inactive h1 {
    font-family: serif;
    font-weight: 400;
    font-size: 42px
}
.wf-active h1 {
    font-family:'Rock Salt', serif;
    font-weight: 400;
    font-size: 42px;
}

JS:

// do the Google Font Loader stuff....
WebFontConfig = {
    google: {
        families: ['Architects Daughter', 'Rock Salt']
    }
};
(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);
})();

//play with the milliseconds delay to find the threshold - don't forget to empty your browser cache and do a hard reload!
setTimeout(WriteCanvasText, 0);

function WriteCanvasText() {
    // write some text to the canvas
    var canvas = document.getElementById("myCanvas");
    var context = canvas.getContext("2d");
    context.font = "normal" + " " + "normal" + " " + "bold" + " " + "42px" + " " + "Rock Salt";
    context.fillStyle = "#d50";
    context.fillText("Canvas Title", 5, 100);
    context.font = "normal" + " " + "normal" + " " + "bold" + " " + "24px" + " " + "Architects Daughter";
    context.fillText("Here is some text on the canvas...", 5, 180);
}

Gambiarra Acabei cedendo e, no primeiro carregamento, usei uma imagem do texto enquanto também posicionava o texto com as fontes fora da área de exibição da tela. Todas as exibições subsequentes das faces da fonte na área de exibição da tela funcionaram sem problemas. Esta não é uma solução alternativa elegante de forma alguma.

A solução está embutida no meu site, mas se alguém precisar, tentarei criar um jsfiddle para demonstrar.

HatHead
fonte
1

Alguns navegadores suportam a especificação CSS Font Loading . Ele permite que você registre um retorno de chamada para quando todas as fontes forem carregadas. Você pode atrasar o desenho da sua tela (ou pelo menos desenhar o texto na sua tela) até então e acionar um redesenho assim que a fonte estiver disponível.

MvG
fonte
0

Em primeiro lugar, use o carregador de fontes da Web do Google, conforme aconselhado na outra resposta, e adicione seu código de desenho ao retorno de chamada fornecido para indicar que as fontes foram carregadas. No entanto, este não é o fim da história. A partir deste ponto, é muito dependente do navegador. Na maioria das vezes funcionará bem, mas às vezes pode ser necessário esperar algumas centenas de milissegundos ou usar as fontes em outro lugar da página. Tentei várias opções e o método que o afaik sempre funciona é desenhar rapidamente algumas mensagens de teste na tela com a família de fontes e as combinações de tamanho de fonte que você vai usar. Você pode fazer isso com a mesma cor do fundo, então eles nem ficarão visíveis e vai acontecer muito rápido. Depois disso, as fontes sempre funcionaram para mim e em todos os navegadores.

Megas
fonte
0

Minha resposta aborda fontes do Google Web em vez de @ font-face. Procurei em todos os lugares por uma solução para o problema da fonte não aparecer na tela. Tentei timers, setInterval, bibliotecas de atraso de fonte e todos os tipos de truques. Nada funcionou. (Incluindo colocar a família da fonte no CSS para canvas ou o ID do elemento canvas.)

No entanto, descobri que a animação de texto renderizado em uma fonte do Google funcionou facilmente. Qual é a diferença? Na animação da tela, nós redesenhamos os itens animados novamente e novamente. Então, tive a ideia de renderizar o texto duas vezes.

Isso também não funcionou - até que eu também adicionei um pequeno atraso de temporizador (100 ms). Eu só testei em um Mac até agora. O Chrome funcionou bem a 100 ms. O Safari exigiu um recarregamento da página, então aumentei o cronômetro para 1000 e ficou bom. O Firefox 18.0.2 e 20.0 não carregaria nada na tela se eu estivesse usando fontes do Google (incluindo a versão de animação).

Código completo: http://www.macloo.com/examples/canvas/canvas10.html

http://www.macloo.com/examples/canvas/scripts/canvas10.js

macloo
fonte
0

Enfrenta o mesmo problema. Depois de ler "bobince" e outros comentários, uso o seguinte javascript para contornar isso:

$('body').append("<div id='loadfont' style='font-family: myfont;'>.</div>");
$('#loadfont').remove();
Sglai
fonte
0

Se você quiser redesenhar toda vez que uma nova fonte for carregada (e provavelmente alterar a renderização), a API de carregamento de fontes também tem um bom evento para isso. Tive problemas com o Promise em um ambiente totalmente dinâmico.

var fontFaceSet = document.fonts;
if (fontFaceSet && fontFaceSet.addEventListener) {
    fontFaceSet.addEventListener('loadingdone', function () {
        // Redraw something

    });
} else {
    // no fallback is possible without this API as a font files download can be triggered
    // at any time when a new glyph is rendered on screen
}
HolgerJeromin
fonte
0

A tela é desenhada independentemente do carregamento do DOM. A técnica de pré-carregamento só funcionará se a tela for desenhada após o pré-carregamento.

Minha solução, mesmo que não seja a melhor:

CSS:

.preloadFont {
    font-family: 'Audiowide', Impact, Charcoal, sans-serif, cursive;
    font-size: 0;
    position: absolute;
    visibility: hidden;
}

HTML:

<body onload="init()">
  <div class="preloadFont">.</div>
  <canvas id="yourCanvas"></canvas>
</body>

JavaScript:

function init() {
    myCanvas.draw();
}
Eduardo mihalache
fonte
-4

Adicione um atraso conforme abaixo

<script>
var c = document.getElementById('myCanvas');    
var ctx = c.getContext('2d');
setTimeout(function() {
    ctx.font = "24px 'Proxy6'"; // uninstalled @fontface font style 
    ctx.textBaseline = 'top';
    ctx.fillText('What!', 20, 10);      
}, 100); 
</script>
tanto faz
fonte