Polotno
Features

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:

FieldMeaningWhen omitted
textOverflow'resize', 'ellipsis' or 'fit'The global text overflow setting
maxLinesCap on visible lines, in every mode; text past it ends in an ellipsisNo cap
minFontSizeSmallest size 'fit' may draw at, in px1px
maxFontSizeLargest size 'fit' may draw at, in pxfontSize

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 minFontSize

Use 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.

On this page