Skip to content

Measure and wrap text into lines for SVG in the browser

License

Notifications You must be signed in to change notification settings

JosephusPaye/svg-text

Repository files navigation

svg-text

Measure and wrap text into lines for SVG in the browser.

This project is part of #CreateWeekly, my attempt to create something new publicly every week in 2020.

Installation

npm install -g @josephuspaye/svg-text

Usage

Measure text

The following example shows how to measure strings of text (runs in the browser):

import { measureText } from '@josephuspaye/svg-text';

const measures = measureText(['nyan', 'oh hai mark'], {
  fontFamily: 'Helvetica, Arial, sans-serif',
  fontWeight: 'bold',
  fontSize: '2em',
});

console.log(`measure for 'nyan':`, measures[0]);
console.log(`measure for 'oh hai mark':`, measures[1]);
View output
measure for 'nyan': { width: 74.6875, height: 35.21875 }
measure for 'oh hai mark': { width: 180.21875, height: 35.21875 }

Wrap text

The following example shows how to layout text into wrapped lines (runs in the browser):

import { layoutText } from '@josephuspaye/svg-text';

const lines = layoutText(
  'The tired teacher told Tom to take thirty-three tables to the townhall.',
  { maxWidth: 300, maxLines: 2 },
  {
    fontFamily: 'Helvetica, Arial, sans-serif',
    fontWeight: 'bold',
    fontSize: '2em',
  },
  {
    lineSpacing: 8,
  }
);

console.log(lines);
View output
[
  {
    width: 259.015625,
    height: 78.4375,
    lines: [
      {
        text: 'The tired teacher',
        measure: {
          width: 259.015625,
          height: 35.21875,
        },
        position: {
          x: 0,
          y: 0,
        },
      },
      {
        text: 'told Tom to take',
        measure: {
          width: 244.734375,
          height: 35.21875,
        },
        position: {
          x: 0,
          y: 43.21875,
        },
      },
    ],
  },
  {
    width: 270.28125,
    height: 78.4375,
    lines: [
      {
        text: 'thirty-three tables',
        measure: {
          width: 270.28125,
          height: 35.21875,
        },
        position: {
          x: 0,
          y: 0,
        },
      },
      {
        text: 'to the townhall.',
        measure: {
          width: 234.640625,
          height: 35.21875,
        },
        position: {
          x: 0,
          y: 43.21875,
        },
      },
    ],
  },
];

API

type ElementAttrs = Record<string, string | number>;

interface TextMeasure {
  width: number;
  height: number;
}

interface MeasuredLine {
  text: string;
  measure: TextMeasure;
  position: {
    x: number;
    y: number;
  };
}

interface LineGroup {
  width: number;
  height: number;
  lines: MeasuredLine[];
}

/**
 * Measure the given array of texts by converting them to SVG
 * <text> elements, temporarily appending to the DOM, and
 * measuring their rendered bounding boxes.
 *
 * `fontAttrs` is an object of SVG `font-*` attributes for
 * the rendered <text> elements.
 */
function measureText(texts: string[], fontAttrs: ElementAttrs): TextMeasure[];

/**
 * Layout the given text to fit the given constraints, with line
 * wrapping and line spacing. Returns groups of lines each with
 * no more than `constraints.maxLines` number of lines.
 */
function layoutText(
  text: string,
  constraints: {
    maxWidth: number;
    maxLines: number;
  },
  fontAttrs: ElementAttrs,
  lineAttrs: {
    lineSpacing: number;
  }
): LineGroup[];

Licence

MIT