leerraum.js is a PDF typesetting / layouting library based on typeset (which provides an implementation of the Knuth and Plass line breaking algorithm) and pdfkit. Its goals are simplicity and perfect control over the positioning of graphic elements.
Table of contents
- Basic usage
leerraum.js is especially well suited for situations in which perfect control over positioning, layout and high quality hyphenated line breaking is essential.
leerraum.js is not well suited for math typesetting, books or when features from LaTeX or other typesetting systems are required.
npm install leerraum
Everything in leerraum.js is measured in points.
A bounding box is a rectangle, along with an optional
A stream is a pure function from an index to a
Tags may be used to mark specific bounding boxes - i.e. the first bounding box on every page can be marked with a
'new_page' tag, allowing for a renderer to skip ahead to the next page break.
withMargins generates an infinite stream of bounding boxes of the provided format with the specified margins in points. Each bounding box is tagged with a
columnsWithMargins generates an infinite stream of bounding boxes of the provided format with the specified margins in points, laid out in two columns. Every bounding box with an even index is tagged with a
Styles combine some basic drawing parameters:
Colors are in '#' + hexadecimal, opacities are in the 0 - 1 range.
The basic building block of leerraum.js is a
Simply said, a renderer is a pure function from a stream of bounding boxes (describing the geometry in which the to-be-rendered graphic element will be laid out) to an array of actually occupied bounding boxes and an array of abstract
Why are renderers operating on streams instead of simple arrays? Because oftentimes it is not possible to estimate the area (and thus the size or number of bounding boxes) that certain graphic elements, like text, are going to occupy before actually laying them out. A lazy, infinite stream of bounding boxes solves that problem.
renderParagraph renders a paragraph of text:
rightIndentation control the left and right indentation (in points) per line, respectively.
leading (in points) indicates the distance between successive lines of text, while
paragraphLeading specifies the distance between the current and next paragraphs.
hypher is an optional Hypher instance used for hyphenation. If none is provided,
hyphenation.en-us is used.
tolerance specifies the maximum amount of allowed space stretching / shrinking (if none is provided, a default value of 10 is used). Note that if the paragraph can not be laid out according to the provided tolerance, no text will be rendered.
Paragraph also contains an array of
fontFamily must contain the full path of a
pdfkit-compatible font file.
fontSize is the font size in points,
hyphenate is a flag indicating whether the span should be hyphenated.
text contains the span text to be rendered with an optional drawing style provided by
options field is passed to
renderText is just a convenience function for rendering an array of paragraphs:
renderPolygon renders a polygon with the provided style specified by an array of points:
renderTable renders a table with the provided column widths, in points, and the cell renderers specified by
cells (which may be anything - text, polygons, even tables).
For now, only absolute column widths are supported (due to laziness, PRs welcome.)
pageBreak introduces a page break (by skipping ahead to the next bounding box with a
The power of leerraum.js stems from the fact that renderers are pure functions and thus can easily be combined to create new renderers.
Two combinators are provided out of the box:
verticallyrenderers: Renderer: Renderer
vertically produces a new renderer by laying out the provided array of renderers vertically:
combinerenderers: : Renderer
combine takes an array of tuples of bounding box-mapping functions (i.e.
(BBox) => BBox) and renderers. In essence,
combine produces a new renderer by transforming its original stream of bounding boxes according to the mapping functions, feeding each transformed stream into its corresponding renderer and finally returning the union of occupied bounding boxes originating from each streamed bounding box:
Format specifies page dimensions. The map
leerraum.formats contains a number of useful formats (
landscape turns a portrait format into landscape.
renderToPages is a lower-level function for outputting
RenderNodes obtained by a renderer. In the most trivial case, one might do the following:
;;;;;doc.pipefs.createWriteStream'test.pdf';;leer.renderToPagesdoc, leer.formats.A4, ;doc.end;
renderToPages takes an array of
RenderNode arrays, which allows for rendering multi-layered documents. Each layer may be obtained by different combinations of bounding box streams and renderers.
There's also an optional
background parameter, which is a function from a page index to a number of
RenderNode layers (like above). The background layers, if specified, are rendered first, on every page.
Why two set of layers? In principle, one might just provide the background layers for every page in
layers. The problem however, is that different layers can't share information with each other, which makes it impossible to e.g. render a blue background on every page but stop when there's no more text to be rendered in another layer.
A more convenient function to use is
The above example could be expressed much simpler in the following way:
Measures is a collection of functions for measuring font metrics.
RenderNodes are abstract descriptions of laid out, ready-to-be-rendered elements (text fragments, polygons, etc).
Bug reports, bug fixes, ideas, PRs welcome!