Skip to content
Inkoxide

Search covers every page and heading of the docs. Use ↑ ↓ and ↵.

Fonts

Edit this page

Four families ship with the package, so nothing needs registering:

FamilyFacesUse it with
Interregular, italic, boldthe default (font-sans)
Inter Displayboldfont-display, for large headings
Roboto Monoregularfont-mono
Noto Sansregularautomatic, for characters Inter lacks (fallbackFonts)

font-semibold and other weights without a face of their own use the nearest one (bold), as in CSS.

No serif face ships with the package, so font-serif sets Noto Sans. For serif text, register a serif family and use it with fontFamily or font-[…], or set --font-serif in the theme you pass to initTailwind({ css }) from @inkoxide/tailwind.

Add your own fonts with Font.register:

import { Font } from '@inkoxide/render';

Font.register({ family: 'Lora', src: './fonts/Lora-Regular.ttf' });

Font.register({
  family: 'Source Serif',
  fonts: [
    { src: './fonts/SourceSerif-Regular.ttf' },
    { src: './fonts/SourceSerif-Bold.ttf', fontWeight: 'bold' },
    { src: 'https://example.com/fonts/SourceSerif-Italic.ttf', fontStyle: 'italic' },
  ],
});

Then use it with style={{ fontFamily: 'Lora' }}, or with className="font-[Lora]".

  • Sources. src is a file path, a file:, https: or http: URL, or the bytes (Uint8Array or ArrayBuffer). Prefer https:: a font fetched over http: can be changed on the way. Formats are TrueType (.ttf), OpenType (.otf), WOFF (.woff) and WOFF2 (.woff2). Variable fonts are not supported: register the static instances. Font collections (.ttc) are not supported either: register each font as its own file.

  • Loading. register is synchronous. The file is read or fetched just before the next render, and a font that cannot be loaded fails that render with INVALID_FONT. The fonts registered with it still load, and later renders can use them. The face that failed is then dropped, and is not tried again: later renders succeed, and text in it falls back as for a face never registered (to the nearest face of its family, else to Inter), with only a warning in the warnings of generate. Register it again, from the same source or another, to try once more. To refuse to render without it, load your fonts at startup with await init() and stop when it throws. A URL is fetched with a 30-second limit for the whole download and a 50 MiB size limit, and redirects are followed; renders wait for it, so for a font over that size or from a slow host, read the bytes yourself and register them.

  • Weights. Weights are numbers (1–1000, as in CSS) or names ('light', 'medium', 'bold'). Missing weights and styles fall back to the nearest one, as in CSS.

  • Colour emoji. Register a colour emoji font, such as Noto Color Emoji, and list it in fallbackFonts:

    Font.register({ family: 'Noto Color Emoji', src: './fonts/Noto-COLRv1.ttf' });
    
    <Document fallbackFonts={['NotoSans-Regular', 'Noto-Color-Emoji-400']}>…</Document>

    An emoji (😀, ❤️, a flag, a keycap, a skin tone, a sequence such as 👩‍💻) goes to the first colour font in fallbackFonts that has it, then to the run's font, then to the other fallbacks. Followed by U+FE0E (text presentation), or as a character that is text by default (❤, ©), it is text like any other. Colour glyphs come from the font's COLR table (versions 0 and 1) or its PNG bitmaps (CBDT, as in Noto Color Emoji's bitmap build, or sbix); a font with bitmaps and no outlines works too, but cannot be a form field's font. They are drawn as images that copy and search as their text. COLR layers in the foreground colour take the run's color, as in browsers (in the text of an SVG image, black); the colour's alpha fades the whole glyph. Gradients that sweep around a point draw with their first colour, and blend modes draw as normal. A colour glyph that is malformed or takes too much work to draw (over 10 000 drawing steps) draws without colour, and so do colour glyphs past the first 4096 of a render (one that takes the run's colour counts once per colour) or its first 32 MB of colour glyph images. In a font with bitmaps and no outlines, which has nothing else to draw them with, such glyphs are not drawn, but they still copy and search as their text. Fonts with only SVG glyphs are not drawn in colour. PDF/A-1b forbids transparency: there, a colour glyph that needs it (a bitmap with an alpha channel, as in every emoji bitmap font, or a layer with an opacity) fails the render with PDFA_CONFLICT at the path of its run (at the src of an SVG whose text has it), and inline images and emoji images that need it fail it at their src or emojiImages key. Opaque COLR glyphs draw in colour there.

  • Emoji from images. Font.registerEmojiSource draws emoji from an image set instead: { url, format } adds each emoji's code points and the format to the URL (https://cdn.example.com/emoji/1f469-200d-1f4bb.png; withVariationSelectors: true keeps fe0f in them), and { builder } turns the code points into any image src. The images load like <Image src>: a URL must be allowed by remoteImages (else the render fails, naming the emoji), a path needs localImages, and images fetched from URLs are kept between renders (16 MB of them, each render still holding them to its own remoteImages.maxBytes). An emoji image is 1em high with its aspect ratio, its bottom 0.1em below the baseline (raised or lowered with the text by verticalAlign: 'super' or 'sub'), and copies as the emoji. An emoji whose image cannot be loaded, or is not an image the engine can draw (other bytes, an image over the size limits), is drawn by the fonts, with a warning; a ZWJ sequence (👩‍💻) without an image of its own uses the images of its parts. null removes the source.

    Font.registerEmojiSource({ url: 'https://cdn.example.com/emoji/72x72/', format: 'png' });
  • Faces. A face is a family, a weight and a style. Registering one again from the same source (the same path, URL or bytes) does nothing, and from another source throws. A face the bundled fonts have (Inter 400) cannot be replaced: register your build under another family name. You can add the faces they lack, such as Inter bold italic. Two family names that differ only in spaces and hyphens (Brand Two, Brand-Two) give the same font id to faces of the same weight and style, so such a face cannot be registered under both: Brand Two 400 and Brand-Two 400 throw, Brand Two 400 and Brand-Two 700 do not.

Synced from packages/render/GUIDE.md in the inkoxide repository.