Skip to content
Inkoxide

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

Images

Edit this page

import { readFileSync } from 'node:fs';

const logo = readFileSync('logo.png');

<Image src={logo} className="h-10 w-10" fit="contain" />         // bytes: Uint8Array, Buffer, ArrayBuffer
<Image src="data:image/png;base64,iVBORw0…" />                  // a base64 data: URL
<Image src="logo" />                                            // an id from options.images
<Image src="./images/logo.png" />                               // a file, when allowed
<Image src="https://cdn.example.com/photo.jpg" />               // remote, when allowed
<Image src={{ uri: 'https://cdn.example.com/chart', method: 'POST', headers, body }} />
<Image src={() => loadLogo()} />                                // a function or a promise
  • Fit and position. fit (or objectFit, or object-contain, object-cover, object-fill, object-none) sizes the image in its box: fill (the default) stretches it (an SVG with a viewBox is not stretched: as in CSS, the box becomes its viewport, and its preserveAspectRatio, by default centred and whole, fits the drawing in), contain shows all of it, cover fills the box, none keeps its own size (96 dpi). With contain, cover and none, the image is centred unless objectPosition places it, as CSS object-position does: one or two values, each a keyword (left, center, right, top, bottom, in either order when both are keywords), a length or a percentage ('25% 75%' lines up that point of the image with that point of the box). A single value leaves the other axis centred. The Tailwind classes object-left-top, object-right, object-bottom and so on, and object-[25%_75%], do the same. What falls outside the box is clipped. Edge offsets ('right 10pt bottom 5pt') are not supported.

    <Image src={photo} className="h-40 w-full" fit="cover" style={{ objectPosition: 'center top' }} />
    <Image src={logo} className="h-10 w-32 object-contain object-left" />
  • Size. An Image with no width or height is as big as the image is (96 dpi), but no wider than the room it has: when the image is wider, it is scaled down to fit, keeping its aspect ratio. CSS would let it overflow. With one of them, the other follows the ratio.

  • Images in text. An Image inside a Text sits in the line, like a character: lines break before and after it. With neither width nor height it is 1em high (the text's size) and as wide as its aspect ratio gives; with one of them (in points or em), the other follows the ratio. Percentages and box styles (margins, padding, borders) are errors there. fit, objectFit and objectPosition work as on a block image, alt is the text it copies and searches as, and a Link around it makes it a link. verticalAlign places it: baseline (the default, its bottom on the baseline), middle (its middle half an x-height above the baseline), text-top and text-bottom (its top at the font's ascender, its bottom at its descender), top and bottom (at the line's top or bottom), super and sub, or a number of points its bottom sits above the baseline. The line grows to fit it. SVG images work too.

    <Text>
      Rated <Image src={star} style={{ height: '0.9em', verticalAlign: 'middle' }} alt="4 stars" /> by
      <Link href="https://example.com"><Image src={logo} className="align-text-bottom" /></Link>
    </Text>
  • Background images. ImageBackground is a View with an image behind its children. It takes the props of a View and src in any form Image takes; fit, objectFit and objectPosition (and the object-* classes) place the image as on an Image, fill by default. The image fills the padding box, inside the borders, as a CSS background-image does: it is drawn over the background colour and under the borders and the children, and clipped to the box, rounded by borderRadius. A box split across pages is sliced with its image, like its background colour. source, imageStyle and srcSet throw: pass the image as src, and place it with fit and objectPosition.

    <ImageBackground src={photo} fit="cover" className="h-48 rounded-xl p-6 object-top">
      <Text className="text-2xl font-bold text-white">Annual report</Text>
    </ImageBackground>
  • Files. Image files are off by default. Allow the folders they come from with the localImages option, or the INKOXIDE_LOCAL_IMAGES environment variable (folders separated by :, or ; on Windows):

    await renderToBuffer(<Doc />, { localImages: { root: '/srv/app/images' } });

    A string is then a file path when it is written as one: relative with ./ or ../, absolute (/srv/app/images/logo.png, C:\images\logo.png), or a file: URL. Any other string (logo, logo.png, img/logo.png) is an image id, so write ./logo.png for a file. Without localImages, a path is an image id too, and no file is read.

  • Folders. A relative path is resolved against the first root (an array allows several), not the working directory. Any path must lead, symbolic links followed, to a regular file inside one of them. Network paths (\\host\share, //host/share, file://host/…) are refused, and so is a file over maxBytes (default 20 MiB). The error names the path as the template wrote it, and is the same for a missing file and a file outside the folders: Cannot read the image ../secret.png: no such file in the localImages folders. (IMAGE_READ_FAILED).

  • Ids first. A string that names an image in the images option (or a template's images) is that image, even when it looks like a path or a URL. No file is read for it.

  • Requests. { uri, method, headers, body } fetches uri with that method, headers and body. It follows the same rules as a URL string: the allowlist, the timeout, the size limit and no redirects.

  • Redirects. A remote image is not followed to another URL, since that URL could be outside the allowlist: a redirect fails the render with IMAGE_FETCH_FAILED (HTTP 302, and redirects are not followed). Use the URL the image ends up at.

  • Remote limits. remoteImages.timeoutMs (default 10 000 ms; Infinity waits without a limit) covers the whole download, body included, and maxBytes (default 20 MiB) its size. A download that times out, breaks off or grows past the limit fails with IMAGE_FETCH_FAILED.

  • Functions and promises. A function is called once per render, after the tree is built and before the engine runs, with the other images loading in parallel; a promise is awaited then. Either resolves to bytes, a string or a request, read as if written in src. If it throws, rejects or does not settle within imageTimeoutMs (default 10 000 ms), the render fails with TEMPLATE_ERROR. A render prop that creates a new function or promise each time has it loaded again when it renders again for the page count. Prefer a function to a promise made before the render starts: a promise that rejects while nothing awaits it yet is an unhandled rejection.

Image files and remote images are off by default, so a template cannot make your server read its files or fetch arbitrary URLs. Allow only the folders and hosts your images are in. An entry allows only the default port of its scheme unless it names one (cdn.example.com:8443). Remote images are allowed per render, or with the INKOXIDE_REMOTE_IMAGES environment variable (a comma-separated list):

await renderToBuffer(<Doc />, { remoteImages: { allow: ['https://cdn.example.com/', '*.example.org'] } });

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