Reference

Limitations

What stationery deliberately does not do (yet).

Known limitations#

From the README.

Fonts: no variable fonts (including CFF2) and no WOFF2 (it needs Brotli; convert to .ttf or .woff); shaping stops at pair kerning and single or ligature substitutions (liga by default, smcp, onum, tnum, ss01… on request), so contextual alternates (calt, clig, frac) do nothing and scripts that need contextual shaping (Arabic, Indic, Thai) draw glyph by glyph unless the application brings a shaper (shaper, see the shaper hook: the gem shapes none of them itself), and colour or emoji glyphs no font in the chain has are drawn as .notdef and reported (under a conformance level they raise, or are drawn as a stand-in with missing_glyphs: :replace). Text runs left to right: a shaper orders the glyphs within a stretch of one font and style, nothing reorders stretches or lines (CJK text wraps between ideographs, but there is no vertical layout), and poppler and MuPDF extract shaped right-to-left text reversed (see the matrix); hyphenation patterns are bundled for English, German and Swedish only (a soft hyphen works in any language), and justification only widens spaces.

SVG covers the shapes, gradients, text, stylesheets, use/symbol sprites and clip paths that icon sets and exports use, nothing else: image, mask, pattern, filter and textPath are skipped and reported, text inside a clipPath does not clip, and the shapes of a clip path join into one path, so overlapping shapes wound in opposite directions cancel where they overlap. Images are JPEG, PNG (non-interlaced) and lossless WebP (not lossy or animated WebP, and no more than 33 megapixels) and never fetched from a URL; a JPEG is embedded at its source resolution (only PNG and WebP can be downscaled), so an oversized one is reported, not resized; a lossless, arithmetic-coded, hierarchical or 12-bit JPEG, or one of more than 33 megapixels, is not decoded for to_png, to_zpl or monochrome. html reads a fixed subset of CSS (colours, sizes, weights, alignment, margins, padding, table borders and widths, page breaks; see What CSS is read), not a layout engine's worth: no display, positioning, font-family, auto or negative margins or selectors with combinators, and floats for images only, with their margin around them. markdown reads no CSS, and raw HTML inside Markdown stays literal text.

Layout: a box with a fixed height: never splits (use min_height: for a floor that can); a row splits only when every column can; a rotated box and a stack move to the next page whole. columns balances to the shortest height that holds the content and fills the columns evenly from the first one on (ten lines in three columns are 4, 3 and 3): a column takes what fits the height balanced for it and the columns after it, so one that ends above a block that cannot split may stay shorter than the column after it. It has columns of one width, nothing spanning them (end the block, write the full-width content, start another) and no column break of its own; a spacer that lands at the top of a column keeps its height. Text wraps around floated images and boxes, along their rectangles, never along a shape; the text after floats cut by a page break goes to the next page with the float that moved, never beside those that stayed; beside a float a table, a row, columns and a box with a size of its own are blocks of the width that is left, all the way down, where a box with a background wraps: a table resolves its column widths from its width, a row shares its width between its columns and columns divides it, and none of them can be laid out again at another width from some row on, which widening below the float would take (see Floats). A table reads its rows as pages reach them only from an Enumerator with a width for every column, and then refuses what needs its end (a row counted from the end, a cell spanning rows); in a row, in a box with break_inside: :avoid or beside a float it is read whole first (see Large documents). Link and form-widget rectangles stay in page space inside rotate and transform, and shadow: is stacked rectangles, not a blur.

PDF: PDF/A-2b, PDF/A-3b and PDF/UA-1 only (no PDF/A-1, no level A or U, no PDF/UA-2, no PDF/X), no JavaScript and no action but links and the print dialog (print dialog: :on_open). Under PDF/UA-1 a link in the header row a table repeats on its next pages, and canvas.link without a tag:, raise: they are outside the structure tree. A render carries one signature (/ETSI.CAdES.detached, RSA or EC with SHA-256): no long-term validation data (PAdES B-LT), no second signature and no signing of a file that already exists, all of which need incremental updates. Form fields are set in the document's fonts, but text typed into one is drawn by the viewer: characters outside the glyphs the field kept (ASCII and Latin-1) use the viewer's own font.

Pictures and labels: to_png and to_zpl draw no form fields (their widget is the viewer's), and to_zpl sends each page as one graphic field, with only barcodes as the printer's own commands (no ^GB rules or boxes, no printer fonts). The labels were compared with to_png and rendered by a ZPL viewer, not printed by the gem's specs.

In detail#

AreaNot supportedWhat you see
FontsVariable fonts (including CFF2) and WOFF2UnsupportedFont naming the reason (variable CFF2 fonts are not supported, WOFF2 needs Brotli; convert to .ttf or .woff)
ShapingGSUB beyond single and ligature substitutions: no contextual alternates (calt, clig, frac), no complex-script shaping (Arabic joining, Indic reordering, Thai marks), no vertical text, no right-to-left runs, unless the application brings a shaperGlyphs drawn one by one, left to right; a contextual feature in features: does nothing. With a shaper, nothing reorders stretches or lines, and poppler and MuPDF extract right-to-left text reversed
GlyphsColour glyph layers (COLR draws its monochrome outline) and any character no font in the chain has, emoji included.notdef box plus a MissingGlyph warning; Unicode whitespace draws as a blank instead. Under a conformance level it raises ConformanceError, or with missing_glyphs: :replace draws U+FFFD, U+25A1 or ? as a stand-in. A bitmap-only emoji font (no glyf/CFF outlines) is rejected with UnsupportedFont
TextHyphenation patterns for languages other than English, German and Swedish (a soft hyphen works everywhere), text wrapping along a shape (it wraps around the rectangle of a float), vertical textLines break at spaces, after hyphens, at hyphenation points, at zero-width spaces and between CJK characters (kinsoku respected); justify widens spaces only
SVGimage, mask, pattern, filter, textPath; text inside a clipPath; stylesheet selectors with combinators (g path, a > b)Skipped and listed in an UnsupportedSvg warning, as are a use or a clip-path whose target is missing
SVG (approximated)Gradient reflect/repeat spreads (drawn as pad), per-stop opacity (the first stop's), gradient strokes (the middle colour), rotated or skewed text (upright, uniformly scaled), dominant-baselineReported in UnsupportedSvg where it changes the drawing
SVG (clip paths)The union of a clip path's shapes as separate regionsThe shapes join into one clipping path, so overlapping shapes wound in opposite directions cancel where they overlap
ImagesGIF, lossy and animated WebP, TIFF, BMP, interlaced PNG; remote URLs; JPEG resampling (a JPEG is embedded at its source resolution, only a PNG or a lossless WebP can be downscaled)UnsupportedImage naming the format; an image drawn at more than twice max_ppi is reported as OversizedImage; html/markdown skip a remote or unreadable image with a SkippedImage warning
Images (decoded)Lossless, arithmetic-coded, hierarchical and 12-bit JPEGs, and JPEGs of more than 33 megapixels, for to_png, to_zpl and monochrome; the EXIF orientationA PDF embeds them as it does every JPEG; to_png draws a crossed box with a SkippedImage, a monochrome PDF embeds it as it is with a NotMonochrome
Pictures and labelsForm fields in to_png and to_zpl; ZPL rules, boxes and printer fontsFields draw nothing (their widget is the viewer's); a ZPL label is one graphic field per page, with only barcodes written as the printer's own ^BC, ^BE, ^BQ and ^BX. Labels are checked against to_png and a ZPL viewer, not printed by the specs
HTML / MarkdownCSS outside the subset html reads: display, float on anything but img, positioning, font-family, line-height, auto and negative margins, inline backgrounds, selectors with combinators or pseudo-classes; script/iframe/video, task lists, footnotes, math; raw HTML inside MarkdownUnread styles are named in an UnsupportedCss warning; unknown elements render their text; raw HTML in Markdown stays literal
NestingHTML elements or Markdown block quotes and lists nested deeper than max_depth: (64), more than 12 levels of indented block quotes and lists, SVG elements nested deeper than 128, a chain of more than 32 usesFlattened into the deepest level kept, text and shapes included, and reported as a NestingLimit warning (the use chain in UnsupportedSvg)
LayoutA fixed height: box, a rotated box and a stack never split; a row splits only when every column can; columns has columns of one width, nothing spanning them and no column break of its own, and fills balanced columns evenly from the first one on, so a column that ends above a block that cannot split may stay shorter than the one after itThe node moves to the next page whole; taller than a page it is placed anyway and reported as an Overflow warning
TablesReading rows as pages reach them from an Array, or from an Enumerator with a column without a width; a row counted from the end (t.row(-1)) or a cell spanning rows in a streamed tableSuch a table reads every row when it is built. In a streamed table a selection from the end, a cell spanning rows and a row with more columns than widths raise ArgumentError; in a row, a box with break_inside: :avoid or beside a float it is read whole
FloatsText beside the floats a page break left behindFloats taller than a page together are cut before the first that does not fit, and the text after them goes to the next page with it
FloatsContent that widens below a float in a table, a row, columns or a box with a size of its ownThey are blocks of the width the float leaves, all the way down: a table resolves its column widths from its width, a row shares its width between its columns and columns divides it, and none can be laid out again at another width from some row on. Text, list items and boxes, with a background or without, wrap
TransformsAnnotations inside rotate/transformLink and form-widget rectangles stay in untransformed page space
ShadowsBlurshadow: is stacked rounded rectangles at fading opacity, which reads as a soft shadow in print
PDF featuresPDF/X, JavaScript and actions other than links and the print dialogNot written
SignaturesLong-term validation data (LTV, PAdES B-LT and B-LTA), a second signature, signing a file that already exists; keys other than RSA and ECA render carries one signature over the whole file; the rest needs incremental updates, which are not written. Another key type raises ArgumentError
ConformancePDF/A-1, PDF/A levels A and U, PDF/UA-2; CMYK colour and CMYK JPEGs in a conforming documentOnly PDF/A-2b, PDF/A-3b and PDF/UA-1 are claimed; CMYK is a ConformanceIssue warning
PDF/UA-1 linksA link in the header row a table repeats on its next pages, and canvas.link without a tag:Outside the structure tree: ConformanceError under PDF/UA-1, an UntaggedLink warning in a tagged render. Links of the body, headers, footers and page templates are tagged
FormsGlyphs for text typed into a field beyond printable ASCII and Latin-1 (and a select's options)The value a field is rendered with is drawn in the document's fonts, whatever its script; what a reader types outside the kept glyphs is drawn by the viewer in a font of its own

Anything the engine skips at render time is reported in document.warnings rather than dropped silently — see Warnings and strict mode.

Sizing images#

JPEGs are embedded as they are.

A JPEG is embedded byte for byte (a monochrome render dithers it instead), so size it before you embed it: for print, width_in_points / 72 * 300 pixels is plenty (a 160 pt photo needs about 670 px), and screen PDFs need half that. An image drawn at more than twice max_ppi is reported as an OversizedImage, and a PNG or a lossless WebP can be resampled with downscale: true — see Resolution.