Skip to content

Glyph Outlines & Path Operations ​

You can convert the glyphs contained in a font into SVG paths.

Rendering Glyphs as SVG ​

TypeScript
import * as fs from 'node:fs';
import { TrueTypeFont } from '@pdfa-lab/fontkit';

const filename = 'OpenSans/OpenSans-Regular.ttf';
const bytes = fs.readFileSync(filename);
const font = new TrueTypeFont(bytes);

const glyph = font.glyphForCodePoint('€'.charCodeAt(0));
if (!glyph) {
	throw new Error('No glyph found for the Euro sign!');
}

const svgPath = glyph.path.toSVG();
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="${glyph.bbox.minX} ${glyph.bbox.minY} ${glyph.bbox.width} ${glyph.bbox.height}">
  <path d="${svgPath}" fill="black" />
</svg>
`.trim();

console.log(svg);

Rendering Glyphs into a PDF ​

You can also render it into a PDF document created with pdfkit:

== TypeScript

TypeScript
import PDFDocument from 'pdfkit';
import * as fs from 'node:fs';
import { TrueTypeFont } from '@pdfa-lab/fontkit';

const filename = 'OpenSans/OpenSans-Regular.ttf';
const bytes = fs.readFileSync(filename);
const font = new TrueTypeFont(bytes);

const doc = new PDFDocument();

doc.pipe(fs.createWriteStream('output.pdf'));

const filename = 'OpenSans-Regular.ttf';
const bytes = fs.readFileSync(filename);
const font = new TrueTypeFont(bytes);

const glyph = font.glyphForCodePoint('€'.charCodeAt(0));
if (!glyph) {
	throw new Error('No glyph found for the Euro sign!');
}

const f = glyph.path.toFunction();
f(doc);
doc.end();

== ES6

JavaScript
import PDFDocument from 'pdfkit';
import * as fs from 'node:fs';
import { TrueTypeFont } from '@pdfa-lab/fontkit';

const filename = 'OpenSans/OpenSans-Regular.ttf';
const bytes = fs.readFileSync(filename);
const font = new TrueTypeFont(bytes);

const doc = new PDFDocument();

doc.pipe(fs.createWriteStream('output.pdf'));

const filename = 'OpenSans-Regular.ttf';
const bytes = fs.readFileSync(filename);
const font = new TrueTypeFont(bytes);

const glyph = font.glyphForCodePoint('€'.charCodeAt(0));
if (!glyph) {
	throw new Error('No glyph found for the Euro sign!');
}

const f = glyph.path.toFunction();
f(doc);
doc.end();

== CommonJS

JavaScript
const PDFDocument = require('pdfkit');
const fs = require('node:fs');

const doc = new PDFDocument();

doc.pipe(fs.createWriteStream('output.pdf'));

const filename = 'OpenSans-Regular.ttf';
const bytes = fs.readFileSync(filename);
const font = new TrueTypeFont(bytes);

const glyph = font.glyphForCodePoint('€'.charCodeAt(0));
if (!glyph) {
	throw new Error('No glyph found for the Euro sign!');
}

const f = glyph.path.toFunction();
f(doc);
doc.end();

:::

Rendering Glyphs into a Canvas ​

Warning This feature may currently crash your application! See https://github.com/gflohr/pdfa-lab/issues/105!

To a certain extent, it is also possible to render a glyph to an HTML5 Canvas, or a Canvas implementation for Node.js, for example https://www.npmjs.com/package/canvas.

However, rendering colour glyphs will invoke methods fillColor() and image() which are not present in the Canvas interface. Your application will crash on it.

For regular glyphs, this works:

== TypeScript

TypeScript
import * as fs from 'node:fs';
import { type FontkitCanvas, TrueTypeFont } from '@pdfa-lab/fontkit';
import { createCanvas } from 'canvas';

const filename = 'OpenSans-Regular.ttf';
const bytes = fs.readFileSync(filename);
const font = new TrueTypeFont(bytes);

const glyph = font.glyphForCodePoint('€'.charCodeAt(0));
if (!glyph) {
	throw new Error('No glyph found for the Euro sign!');
}

const f = glyph.path.toFunction();

const canvas = createCanvas(400, 400)
const ctx = canvas.getContext('2d')

const fontSize = 150;
const scale = fontSize / font.unitsPerEm;

ctx.save();
ctx.translate(20, 200);
ctx.scale(scale, -scale);
ctx.beginPath();
f(ctx as unknown as FontkitCanvas);
ctx.fillStyle = '#1d4ed8';
ctx.fill();
ctx.restore();

const png = canvas.toBuffer();
fs.writeFileSync('euro.png', png);

== ES6

JavaScript
import * as fs from 'node:fs';
import { TrueTypeFont } from '@pdfa-lab/fontkit';
import { createCanvas } from 'canvas';

const filename = 'OpenSans-Regular.ttf';
const bytes = fs.readFileSync(filename);
const font = new TrueTypeFont(bytes);

const glyph = font.glyphForCodePoint('€'.charCodeAt(0));
if (!glyph) {
	throw new Error('No glyph found for the Euro sign!');
}

const f = glyph.path.toFunction();

const canvas = createCanvas(400, 400)
const ctx = canvas.getContext('2d')

const fontSize = 150;
const scale = fontSize / font.unitsPerEm;

ctx.save();
ctx.translate(20, 200);
ctx.scale(scale, -scale);
ctx.beginPath();
f(ctx);
ctx.fillStyle = '#1d4ed8';
ctx.fill();
ctx.restore();

const png = canvas.toBuffer();
fs.writeFileSync('euro.png', png);

== CommonJS

JavaScript
const fs = require('node:fs');
const { TrueTypeFont } = require('@pdfa-lab/fontkit');
const { createCanvas } = require('canvas');

const filename = 'OpenSans-Regular.ttf';
const bytes = fs.readFileSync(filename);
const font = new TrueTypeFont(bytes);

const glyph = font.glyphForCodePoint('€'.charCodeAt(0));
if (!glyph) {
	throw new Error('No glyph found for the Euro sign!');
}

const f = glyph.path.toFunction();

const canvas = createCanvas(400, 400)
const ctx = canvas.getContext('2d')

const fontSize = 150;
const scale = fontSize / font.unitsPerEm;

ctx.save();
ctx.translate(20, 200);
ctx.scale(scale, -scale);
ctx.beginPath();
f(ctx);
ctx.fillStyle = '#1d4ed8';
ctx.fill();
ctx.restore();

const png = canvas.toBuffer();
fs.writeFileSync('euro.png', png);

:::