Dynamically loading fonts on HTML5

Hi, I’m trying to load a font file at runtime on my project. I won’t be able to add it at the assets folder, so I need to load the font file and then register it. This is what I’m doing:

  1. loading the font file with an URLLoader object
var ld:URLLoader = new URLLoader();
ld.dataFormat = URLLoaderDataFormat.BINARY;
ld.addEventListener(Event.COMPLETE, onLoader);
ld.load(new URLRequest('path to the font file'));
  1. when loaded, creating the font
var ld:URLLoader = cast evt.target;
var fnt:Font = Font.fromBytes(ld.data);
Font.registerFont(fnt);
  1. then, adding a textfield with it
var txt:TextField = new TextField();
var fmt:TextFormat = new TextFormat(fnt.fontName, 30);
txt.defaultTextFormat = fmt;
txt.text = 'my text here';
addChild(txt);

This works on neko target, but not on HTML5 one. For neko, I used a ttf font without problems. For HTML5 I tryed ttf, otf, woff and woff2 without success. How can I register this font to my HTML5 project at runtime?

Extra: will this work on AIR too?

The reason is - as it is almost all of the time - Font.fromBytes() is not implemented
for the HTML5 target.
What is implemented is Font.loadFromName(), which unfortunately relies on a custom font
specified via CSS using the @font-face rule.
Nevermind though, Haxe’s js.html.FontFace API let’s you load a FontFace dynamically.
So what you need to do is converting the ByteArray inside the onLoader() event handler
to an ArrayBuffer, which is what the JavaScript side needs and ultimately feed that
to the FontFace constructor().
If ‘loading’ of those bytes succeeded, you can utilize OpenFL’s Font.loadFromName() function
to register the font.

Here’s a working example:

package;

import openfl.display.Sprite;
import openfl.net.URLLoader;
import openfl.net.URLLoaderDataFormat;
import openfl.net.URLRequest;
import openfl.Lib;
import openfl.events.Event;
import openfl.text.TextField;
import openfl.text.TextFormat;
import openfl.text.Font;
#if (js && html5)
	import js.Browser;
	import js.html.FontFace;
	import js.html.ArrayBuffer;
	import js.html.DataView;
#end
import openfl.utils.ByteArray;

class Main extends Sprite
{

	public function new()
	{
		super();
		var ld:URLLoader = new URLLoader();
		ld.dataFormat = URLLoaderDataFormat.BINARY;
		ld.addEventListener(Event.COMPLETE, onLoader);
		ld.load(new URLRequest('myFont.ttf'));
	}

	private function onLoader(evt:Event):Void
	{
		var ld:URLLoader = cast evt.target;
		#if (js && html5)
			var bytes:ByteArray=cast(ld.data, ByteArray);
			var buffer:ArrayBuffer = new ArrayBuffer(bytes.length);
			var view:DataView = new DataView(buffer, 0, buffer.byteLength);
			for (i in 0...bytes.length)
			{
				view.setUint8(i, bytes[i]);
			}
			var fontFace = new FontFace('MyFontName', buffer);
			Browser.document.fonts.add(fontFace);
			fontFace.load().then(function(_)
			{
				var future:lime.app.Future<Font> = Font.loadFromName("MyFontName");
				future.onComplete(function(font)
				{
					drawText(font.fontName);
				});
			});
		#else
			var fnt:Font = Font.fromBytes(ld.data);
			Font.registerFont(fnt);
			drawText(fnt.fontName);
		#end
	}

	private function drawText(fontName):Void
	{
		var txt:TextField = new TextField();
		var fmt:TextFormat = new TextFormat(fontName, 30);
		txt.defaultTextFormat = fmt;
		txt.text = 'my text here';
		addChild(txt);
	}
}
2 Likes

Oh, I see. Thank you a lot, I’ll try it. Do you know if Font.fromBytes() is implemented at swf/AIR target? If I try to call it while buiding for AIR I’m getting a “TypeErrror #1006” and I believe this may be the case as well.

On Flash/Air targets Haxe is utilizing the flash.text.Font API - which did not have a
fromBytes() method. That is simply because dynamic loading of a font file on these
targets does require the actual font file embedded in a .swf file. You can’t just
use the binary data of a .ttf file.
Well, I don’t quite like self-promotion but if all you want to do is display dynamic
text using a font obtained from a .ttf file, I’d recommend using my Stroked TextField
library and simply don’t give your text an outline color.

1 Like

It worked! Thank you a lot!
And, about your library, it was already on “my list” to check it out, I loved the idea!

Do you know how do I embed fonts in an AIR project built on OpenFL (this one can be from the assets)?

Looks like an opportunity for someone to contribute a Font.loadFromBytes method to the OpenFL core library with support for HTML5 :+1:

1 Like

@chokito This is not a big deal. Actually OpenFL’s sample AddingText is just doing that. Be sure though that the textField.embedFonts = true; line is important - otherwise you won’t see your custom font.

@singmajesty I already thought about that. The problem is that this is an asynchronous operation on the JavaScript side (while for all other targets it’s synchronous and returns a Font object ‘instantly’) and I’m not sure how to handle that on the OpenFL side. Or should it simply return a Future<Font>? Well, my feeling tells me that it should return the same type on any target. :wink:

1 Like

I misspoke.

It appears openfl.text.Font.loadFromBytes and openfl.text.Font.loadFromFile both exist, and use lime.text.Font.loadFromBytes and lime.text.Font.loadFromFile under the hood.

It would be worth double-checking that these are working properly, and if not, updating the functionality specifically for HTML5.

1 Like