truffle-text
v2.0.0
Published
Deterministic Saffron-class text rendering with HarfBuzz shaping and certified Flash replay.
Maintainers
Readme
Truffle Text
Truffle is a deterministic AIR/Saffron HTML canvas text renderer.
air-generative is the production path: HarfBuzz shaping, bidi,
grapheme-safe fallback, recovered AIR outline fitting and escapement, and
runtime rasterization at arbitrary positive sizes. legacy-air-exact is an
explicit frozen replay oracle; generic is a developer comparison control.
Install
yarn add truffle-text
npx truffle-setup --out public/assets/truffleRun truffle-setup again after upgrading the package. The command expands the
compact package payload into files that your own website serves; no external
CDN is required. Serve .tfc as application/octet-stream.
Browser or canvas usage
import { loadPackedTruffle } from 'truffle-text/packed';
const truffle = await loadPackedTruffle({
base: './assets/truffle',
styles: ['u_chat_speak', 'u_chat_name'],
});
truffle.drawText(context, 'Hello world!', {
x: 0,
y: 0,
style: 'u_chat_speak',
});
truffle.drawRichText(context, 'Hello <b>bold</b> and ' +
'<font size="18" color="#C62828">large red</font>', {
x: 0,
y: 24,
style: 'u_regular',
});For only the scalable engine, initially load the bundled fonts but none of the legacy tables:
const truffle = await loadPackedTruffle({
base: './assets/truffle',
loadCalibration: false,
});
const pixels = truffle.renderToBuffer('Hello — Truffle', {
fontFamily: 'Ubuntu',
size: 21.5,
engineMode: 'air-generative',
});Calling ensureStyles or ensureAllStyles later enables legacy tables lazily.
Applications that provide their own font bytes can use the standalone clean entry point. It has no dependency on the legacy renderer or payload:
import { SaffronText } from 'truffle-text/generative';
const truffle = SaffronText.fromFonts([{
family: 'My UI Font',
data: await fetch('/fonts/ui.ttf').then(response => response.arrayBuffer()),
}]);
const pixels = truffle.renderToBuffer('Any text', {
fontFamily: 'My UI Font', size: 19.25, engineMode: 'air-generative',
});Unicode fallback fonts
The npm payload bundles Ubuntu and Volter for the included Habbo-style presets.
It does not bundle broad Unicode fonts. Applications that need Arabic, CJK, or
other scripts should serve licensed TrueType faces, register them after loading
Truffle, and name them in fontFallbacks.
This example registers upright Regular and Bold faces for families that do not provide designed italics. If your family includes real italic faces, register those instead of the upright aliases:
import { FlashFont } from 'truffle-text';
const unicodeFallbacks = ['Noto Sans Arabic', 'Zen Kaku Gothic New'];
async function loadFace(family, weight, url) {
const response = await fetch(url);
if (!response.ok) throw new Error(`Unable to load ${url}: HTTP ${response.status}`);
return new FlashFont(
new Uint8Array(await response.arrayBuffer()),
`${family} ${weight}`,
);
}
async function registerUprightFamily(family, regularUrl, boldUrl) {
const [regular, bold] = await Promise.all([
loadFace(family, 'Regular', regularUrl),
loadFace(family, 'Bold', boldUrl),
]);
for (const italic of [false, true]) {
truffle.registerFont(family, regular, { bold: false, italic });
truffle.registerFont(family, bold, { bold: true, italic });
}
}
await Promise.all([
registerUprightFamily(
'Noto Sans Arabic',
'/fonts/NotoSansArabic-Regular.ttf',
'/fonts/NotoSansArabic-Bold.ttf',
),
registerUprightFamily(
'Zen Kaku Gothic New',
'/fonts/ZenKakuGothicNew-Regular.ttf',
'/fonts/ZenKakuGothicNew-Bold.ttf',
),
]);
const pixels = truffle.renderToBuffer('مرحبا · こんにちは世界', {
styleName: 'u_chat_speak',
fontFallbacks: unicodeFallbacks,
});Fallback selection is grapheme-safe and works with shaping, bidi, rich text, measurement, editing, and rendering. Keep each external font's required copyright and license files with your application.
React or Nitro usage
Preload once while the client starts:
import { preloadTruffle, TruffleCanvasText, TruffleRichText } from 'truffle-text/react';
await preloadTruffle({
base: './assets/truffle',
styles: [
'u_chat_speak',
'u_chat_name',
'u_chat_whisper',
'u_chat_name_whisper',
'u_chat_shout',
],
});Then render named styles anywhere:
<TruffleCanvasText text="C5: " styleName="u_chat_name" />
<TruffleCanvasText text="Hello!" styleName="u_chat_speak" />
<TruffleRichText
markup={'Hello <b>rich text</b>'}
baseStyle="u_regular"
width={320}
/>Chat mappings used by the Habbo-style test:
Speak: u_chat_name + u_chat_speak
Whisper: u_chat_name_whisper + u_chat_whisper
Shout: u_chat_name + u_chat_shoutFor bubble wrapping, measure and place text with Truffle before showing the bubble. Keep canvas coordinates integer-aligned. Do not show HTML text first and replace it after loading.
Styles
Use names from HABBO_CSS_STYLE_NAMES or inspect HABBO_STYLES. Named styles
are read-only presets, not size or face restrictions:
const style = truffle.resolveStyle('u_regular', {
engineMode: 'air-generative',
size: 17.5,
bold: true,
color: 0x17365D,
});Normal calls and fidelity: 'auto' use air-generative, even for certified
signatures. The deprecated 'geometric' spelling is an alias for the same
route. 'exact' or explicit legacy-air-exact requires certified replay and
rejects a miss. Select generic only as a comparison control. The resolved
engine is exposed in render metadata and by engine(style).metadata.mode.
Editable search and input fields
import { createTruffleEditable } from 'truffle-text/editable';
const search = createTruffleEditable(document.querySelector('#search'), truffle, {
style: { styleName: 'u_italic', size: 11 },
placeholder: 'Search profiles...',
});This keeps a real transparent textarea for keyboard input, IME, clipboard, and accessibility while Truffle draws the text, selection, and caret from one layout. Ctrl+A never exposes browser-rendered fallback text. Dynamically added underlines have a one-pixel gap by default.
Playground
Try every included style in the browser at isetht.github.io/truffle-text/test.
Licensing
Truffle's original code is available under the MIT License. Bundled fonts are
third-party components governed separately; see THIRD_PARTY_NOTICES.md and
the licenses/ directory. Volter (Goldfish) does not currently have a located
standalone licence or EULA, so review its notice before redistribution.
