Text Sizing
Fit variable text into a fixed box, cap lines, and measure the result
A template reserves space for text that changes later: a customer's name on a business card, a headline fed from a spreadsheet, a price, a list of features. "Li An" and "Aleksandra Konstantinopolskaya-Fernandez" have to land in the same box, and the box has to stay where the designer put it.
Text sizing gives each text element its own policy for that: fit the text into
the box by changing its drawn size within bounds you set, cap the number of
lines, or cut it with an ellipsis. The authored fontSize is never rewritten,
so a long value shrinks and the next short value grows back. The same engine
sizes the text in the editor, in polotno-node and in every exporter, so what
the canvas shows is what the file contains.
Live demo
Type into the fields: the headline and the price fit their boxes, the
description stops after two lines, the footer is cut with an ellipsis, and the
panel shows what measureText reports for the headline.
The four fields
Four optional fields on a text element control it:
| Field | Meaning | When omitted |
|---|---|---|
textOverflow | 'resize', 'ellipsis' or 'fit' | The global text overflow setting |
maxLines | Cap on visible lines, in every mode; text past it ends in an ellipsis | No cap |
minFontSize | Smallest size 'fit' may draw at, in px | 1px |
maxFontSize | Largest size 'fit' may draw at, in px | fontSize |
The fields work in the editor, in polotno-node and in the SVG, HTML, PDF and PPTX exporters, so an export shows the same text as the canvas.
Fit text into its box
'fit' draws the text at the largest size that fits the box, between
minFontSize and maxFontSize. It never writes the fitted size back: fontSize
stays the authored size, so a long value shrinks and a shorter one grows back.
const title = store.selectedElements[0];
// shrink down to 12px when the text is long, grow up to 72px when it is short
title.set({ textOverflow: 'fit', minFontSize: 12, maxFontSize: 72 });Without maxFontSize, 'fit' only shrinks. Set it above fontSize to let
short text grow.
Inline sizes in rich text, stroke and shadow scale with the fitted size, so a styled headline keeps its proportions.
Replace template values
Mark the variable elements in your template with a custom property, then set
their text and let 'fit' do the rest.
// in the template, once
nameElement.set({
custom: { field: 'name' },
textOverflow: 'fit',
minFontSize: 16,
maxFontSize: 64,
});
// for each generated design
const values = { name: 'Alexandra Konstantinopolsky', city: 'Berlin' };
store.pages.forEach((page) => {
page.children.forEach((element) => {
const field = element.custom?.field;
if (element.type === 'text' && field in values) {
element.set({ text: values[field] });
}
});
});Every renderer sizes the text the same way, so the design can be exported
right away with store.toDataURL() or on the server with polotno-node.
Cap the number of lines
maxLines limits visible lines in every mode. Text past the cap ends in an
ellipsis. Under 'fit' the cut falls between paragraphs: the text shrinks until
every visible paragraph is complete, and later paragraphs are dropped.
// a card description: two lines at the authored size, then "…"
description.set({ textOverflow: 'resize', maxLines: 2 });
// a fitted headline that may never wrap to a third line
headline.set({ textOverflow: 'fit', fontSize: 48, maxLines: 2 });With 'resize' the box grows to the capped height. With 'ellipsis' and
'fit' the box stays as authored.
Measure before you commit
measureText returns the layout the canvas
draws. Pass an element, or plain text properties with the sizing fields, to
check a value before writing it into the design.
import { measureText } from 'polotno/utils/measure-text';
const result = await measureText({
...title.toJSON(),
text: 'A much longer headline than the template planned for',
textOverflow: 'fit',
});
result.fontSize; // the size the text is drawn at
result.lineCount; // visible lines
result.truncated; // true when some text does not fit even at minFontSizeUse truncated to reject a value, pick a shorter one or fall back to a
different layout.
Flow a list across columns
Measure the list at the authored size with 'resize' and no maxLines, and
assign whole items to each column while their lines fit.
import { measureText } from 'polotno/utils/measure-text';
async function fillColumns(items, columns) {
let remaining = [...items];
for (const column of columns) {
const { lines } = await measureText({
...column.toJSON(),
text: remaining.join('\n'),
textOverflow: 'resize',
});
// keep every item whose lines all end inside the column
let count = remaining.length;
for (const line of lines) {
if (line.top + line.height > column.height) {
count = line.paragraph;
break;
}
}
if (count === 0) break; // one item is taller than the column
column.set({ text: remaining.slice(0, count).join('\n') });
remaining = remaining.slice(count);
if (remaining.length === 0) break;
}
return remaining; // items that did not fit any column
}Each line reports text, top, height and the paragraph it belongs to,
so the split works with mixed sizes and wrapped items.
Fit everything
To fit every text element that sets no policy of its own, change the global setting instead:
import { setTextOverflow } from 'polotno/config';
setTextOverflow('fit');An element's own textOverflow, maxLines and size bounds still apply. On the
server, pass textOverflow: 'fit' in polotno-node's render attrs or the
exporter options for the same result.
Curved text
Under 'fit', curved text takes the largest size whose arc fits the element
width. maxLines and box height do not apply to curves.