Text boxes

Shrink to fit

Try it in the demo →

A design often gives text a fixed space: a headline band 128 pixels high, a name line on a badge. When the words change, copyfitDocument makes the type smaller, only as far as it must, so every word fits the box's width and the height you give. Font sizes, outline widths, letter spacing and the shadow scale together, so the text keeps its look.

Fit new words into a box

Build the new document at the text's full design size, then hand it to the box with the height it must fit:

// The new words at the headline's own size, 40px
const next = createDocumentFromText(words, 40, "Playfair Display");

// Made smaller only as far as it must, to fit 128 pixels high
const scale = headline.copyfitDocument(next, { containerHeight: 128 });
// scale: the factor applied to the sizes - 0.75 means the 40px words are now 30px

copyfitDocument is a method of every text box: the Konva RichText and the plain-canvas CanvasTextBox. It replaces the box's document with the fitted one, keeps the box's width, and is one undo step. It returns the scale it applied to the document's sizes.

Copyfit only shrinks. To let shorter words grow back, start from the full-size document each time, as above. The scale it finds is within 1% of the largest one that fits (the tolerance option).

Note: Without containerHeight, only the width is checked: copyfit makes sure no word is wider than the box. A box's own height always grows to fit its text, so it cannot be the limit.

Options

Option Default Does
containerHeight none The height the text must fit in, in pixels
containerWidth the box's width The width to fit to. The box is set to this width.
minScale 0.1 The smallest scale allowed, as a multiple of the document's sizes (0.5 is half size). The text never goes below it, even if it then overflows.
maxIterations 20 The most trial layouts the search makes
tolerance 0.01 The search stops once the scale is known to within this fraction

To keep text legible, express a minimum size as a scale:

headline.copyfitDocument(next, { containerHeight: 128, minScale: 12 / 40 }); // never below 12px

Copyfit measures with the fonts already loaded. If the new document uses a family that is not on the page yet, load it first so the fit is measured in the real font:

await loadFonts(next);
headline.copyfitDocument(next, { containerHeight: 128 });

Fit when a box loads

The copyfit setting shrinks a box's text once, when its fonts have loaded:

// Konva
new RichText({ x: 28, y: 32, width: 352, document: doc, copyfit: { enabled: true, containerHeight: 128, minFontSize: 12 } });

// Plain canvas
rt.addBox({ x: 28, y: 32, width: 352, document: doc, copyfit: { enabled: true, containerHeight: 128, minFontSize: 12 } });
Field Does
enabled Turns the fit on
containerHeight The height to fit in. On Konva, when it is left out, the box's height setting is used. A canvas box has no height setting, so set it there.
minFontSize The smallest size to shrink to. If the text cannot fit at that size, it shrinks further, because fitting comes first.
maxFontSize Lets the text grow, up to this size for its largest letters, to fill the height. Without it, text only shrinks.
newText New words for a design made with other words: the box lays out its original text, takes the height that text needs as the space to fill, and fits newText into it. Each line becomes a paragraph in the look of the paragraph it replaces; extra lines take the first paragraph's look.

The fitted sizes are written into the document, and the copyfit setting is then removed from the box, so a saved design does not fit again each time it opens. A box being edited is not fitted.

Scale a document yourself

scaleDocumentTypography returns a copy of a document with its type scaled: font sizes, letter spacing, border widths, outline widths, the base size and the shadow. Line height, paragraph spacing and list indents are multiples of the font size, so they stay as they are.

import { scaleDocumentTypography } from "@mycreativeshop/rich-text/konva";

const half = scaleDocumentTypography(doc, 0.5);

It is exported from the Konva entry point, @mycreativeshop/rich-text/konva.

Check whether text fits

layoutAt(document, width) lays a document out the way a box would - its fonts, its fallback size - and returns the result without changing the box:

const trial = headline.layoutAt(next, headline.width());
if (trial.height > 128) showWarning("The headline is too long for its space");

The result is a TextLayout plus box, the size the box would give itself:

Field Is
lines Each line's text, paragraph, width, baseline and why it ended ("wrap", "paragraph", "softbreak", "end")
graphemes Every letter's position and size
contentWidth The widest line
height The height the text needs
longestWordWidth The widest single word
box The { width, height } the box would take

Two more box methods help with fitting rules: getMinimumFontSize() returns the smallest size the text is drawn at, and getLongestWordWidth() the narrowest the box can be without a word overflowing.

Without a box

layoutText lays out a document at a width with no box and no canvas on screen: its lines, every letter's position, and the height it needs. Load the document's fonts first, or it measures in stand-in fonts:

import { layoutText, loadFonts } from "@mycreativeshop/rich-text"; // on Konva: "@mycreativeshop/rich-text/konva"

await loadFonts(doc);
const { lines, height } = layoutText(doc, { width: 352 });

Limit: layoutText needs a browser. To run it on a server, run it in a headless browser.

In the API reference