Loading a custom font file (TTF, OTF, WOFF, WOFF2) in the browser without a stylesheet sounds like it shouldn't work. But the FontFace API makes it possible — and it's surprisingly clean.
The FontFace API
Most developers know @font-face in CSS. Fewer know you can do the same thing entirely in JavaScript:
async function loadFontFromFile(file) {
const fontName = `custom-${Date.now()}`;
const arrayBuffer = await file.arrayBuffer();
const font = new FontFace(fontName, arrayBuffer);
await font.load();
document.fonts.add(font);
return fontName;
}
Once added to document.fonts, you can use the font name anywhere in CSS or Canvas.
Supporting Multiple Formats
WOFF and WOFF2 files are compressed web fonts. TTF and OTF are the raw formats. The browser handles all of them through FontFace — you don't need to specify the format, unlike in CSS @font-face.
const SUPPORTED = ['ttf', 'otf', 'woff', 'woff2'];
function getExtension(file) {
return file.name.split('.').pop().toLowerCase();
}
function isValidFont(file) {
return SUPPORTED.includes(getExtension(file));
}
Rendering Weight Variations
Not all fonts include all weights. When you set font-weight: 900 and the font only has regular, the browser synthesizes bold — which usually looks bad.
To show only what's actually available, I check which weights render differently:
async function detectAvailableWeights(fontName) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const testChar = 'A';
const available = [];
for (const weight of [100, 200, 300, 400, 500, 600, 700, 800, 900]) {
ctx.font = `${weight} 48px "${fontName}"`;
const metrics = ctx.measureText(testChar);
// Compare width — different weights render at different widths
available.push({ weight, width: metrics.width });
}
return available;
}
Letter Spacing and Line Height on Canvas
CSS letter-spacing and line-height don't apply to Canvas — you have to implement them manually.
For letter spacing, measure and reposition each character:
function drawTextWithSpacing(ctx, text, x, y, spacing) {
let curX = x;
for (const char of text) {
ctx.fillText(char, curX, y);
curX += ctx.measureText(char).width + spacing;
}
}
The Comparison View
The most useful feature turned out to be rendering all nine weights side by side. Users can instantly see which weights their font actually supports.
Try it at toolzip.app/tools/font-preview — drag in any TTF, OTF, WOFF, or WOFF2 file.
Top comments (0)