@borgar/textbox

A utility to render formatted multi-line rich text.


Keywords
text, format, formatted, rich-text, canvas, svg
License
BSD-3-Clause
Install
npm install @borgar/textbox@1.0.0

Documentation

Textbox

Textbox is a simple library to layout text for display on SVG or Canvas. It can fairly decently line-break and render rich text given some boundaries. It understands simple text, and a subset of HTML and LaTeX syntax. The original purpose of this software is to aid labeling charts.

Features:

  • Line-break text to fit dimensions.
  • Can overflow text into ... if it doesn't fit designated area.
  • Understands common text features: bold, italic, links, etc...
  • Knows that there is different whitespace like thin, or non-breaking.
  • Tries to be smart about line-breaking before or after certain characters (it can occur after - but not before).
  • Supports hyphenation if text is prepared with soft-hyphens.

Installing

If you don't want to download and build Textbox yourself, the library is also provided as an NPM package:

$ npm install @borgar/textbox

API Reference

# the Textbox class

For any use, you will need to start by defining a new Textbox instance. You may pass a configuration object as a parameter:

const box = new Textbox({
  font: '12px/14px sans-serif',
  width Infinity,
  height: Infinity,
  align: 'left'
  valign: 'top'
  x: 0,
  overflow: 'ellipsis',
  parser: Textbox.defaultparser,
  createElement: Textbox.createElement
});

Shown here are the defaults, but any or all of the above parameters can be provided. The Textbox instance will provide methods by the same name, along with line-breaking and rendering methods:

const box = new Textbox()
  .font('12px/14px sans-serif')
  .align('left')
  .createElement(React.createElement);

# .font( css_font_shorthand )

Define what font to use. This will allow setting both font-size and line-height as well as font-family. Defaults to 12px/14px sans-serif.

# .width( width_in_px )

Controls the horizontal dimension of the text. This can be a callback function if you want runaround text layout, or to flow the text into irregular space. Defaults to Infinity (a single line).

A callback provided to this will be called every line with the line number as a parameter.

# .height( height_in_px )

Controls the vertical dimension of the text. Defaults to Infinity. If the text ends up with more lines than fit into the height, the text is cut and postfixed with an overflow indicator (see overflow).

# .x( indent_in_px )

Sets text horizontal indent. This is most useful for flowing text into irregular shapes. A callback provided to this will be called every line with the line number as a parameter.

# .align( alignment )

Sets text horizontal alignment. Accepts all values you would expect CSS text-align to understand, as well as SVG text-anchor equivalents: left, start, center, middle, right, end, and justify.

# .valign( alignment )

Sets text vertical alignment. Accepts all values you would expect CSS vertical-align to understand: top, start, center, middle, bottom, and end.

# .overflow( indicator )

Sets text overflow mark, similar to CSS text-overflow. The keyword ellipsis will set the overflow mark to , otherwise the provided string is used as-is.

# .parser( parser )

Selects which text parser to use. The available parsers are:

  • Textbox.textparser (the default, may be selected with "text")
  • Textbox.htmlparser (may be selected with "html")
  • Textbox.latexparser (may be selected with "latext")

Textbox will look for a parser on the instance first, then default to Textbox.defaultparser. So you if you know that you will exclusively be using the HTML parser, you can change the default once:

Textbox.defaultparser = Textbox.htmlparser;

# .createElement( element_factory )

Set the element factory method for creating elements for SVG rendering. This has the same interface as React.createElement so you may assign that if you are rendering a React application.

If nothing is provided Textbox will default to Textbox.createElement so you can change the default once globally:

Textbox.createElement = React.createElement;

# .linebreak( text )

Parses text, flows it into the set dimensions and returns a list of the lines. The returned object can then be passed on to .render().

As well as a list of lines of tokens, the lines object has a height property which is useful if you want to set the render destination to the fit the text.

The lines object additionally has a render method so you can pass it on without having the originating Textbox instance.

# No height is set to the box
const box = new Textbox({ width: 150 });
# Text is turned into lines
const lines = box.linebreak( longTextPassage );
# Destination canvas is set to the height of the output
myCanvas.height = lines.height;
# Lines are rendered to the canvas
lines.render( myCanvas );

The lines render method is flexible when it comes to its arguments. It can accept a Canvas, a CanvasRenderingContext2D, or nothing in which case it will emit SVG. See .render().

# .render( text )
# .render( text, Canvas )
# .render( text, CanvasRenderingContext2D )
# .render( text, d3-selection )

Render text or a "lines object" (see .linebreak()) to either SVG or Canvas.

The lines render method is flexible when it comes to its arguments and their order. If provided either a Canvas, a CanvasRenderingContext2D it will render to the canvas, otherwise it will emit SVG built with the .createElement() interface.

Limitations:

  1. In SVG links do not automatically get color or pointer cursor. You will need to add your own styles to these.

    svg text a {
      fill: blue;
      text-decoration: underline;
      cursor: pointer;
    }
    
  2. Multi-word links in SVG text are not necessarily a single entity/element like they are in HTML. If the text in a link is line broken, then hovering one segment will not cause segment in the next line over to trigger hover styles.

  3. Text justification is fairly broken in SVG. Avoid it for formatted text.

    1. Justification does now work consistently in all browsers as they have buggy support for the word-spacing property. Google Chrome seems to work fine, Safari works for plain text but incorrectly for formatted text. Firefox is lost in the woods.

    2. Because of the way text handling is done in SVG, justifying underlined text will create gaps in the underlined text.

  4. Subscript and superscript are bugged in Firefox because they have never implemented the baseline-shift property. The bug report is 15 years old when this is written so not likely to be solved soon.