tclpdf

man-page
Login

man-page

NAME

tclpdf - PDF generation for Tcl

SYNOPSIS

package require Tcl 8.6.11-

package require tclpdf 1.0

DESCRIPTION

tclpdf creates PDF documents from Tcl. It is a pure Tcl package: no compiler, no binary extension, no Tk.

What is documented below is what the package provides — nothing here is planned or partial. Roughly a third of ISO 32000-1 is covered, weighted by the page count of its chapters; the remainder is almost entirely what a reader of foreign PDFs needs rather than a writer, plus encryption, form fields and tagged PDF.

REQUIREMENTS

Tcl 8.6.11 or newer. The package also runs under Tcl 9 — note that this requires the open-ended form of the version requirement, since package require Tcl 8.6 is rejected by Tcl 9 with a version conflict.

The core needs nothing beyond Tcl itself. In particular zlib is a built-in command rather than a package, so it must not be requested with package require: the dummy package of that name exists only in Tcl 8.6.

Tk is not required and is never loaded. Two conveniences would pull it in and are therefore avoided: resolving colour names via winfo rgb, and decoding images via image create photo. tclpdf carries its own colour table and its own image parsers.

OPTIONAL PACKAGES

tdom

: Used by the SVG module for parsing, and preferred when present: measured, tdom parses 27 to 48 times faster than the parser tclpdf brings along, and it rejects an entity expansion bomb that the built-in one would try to expand. Without tdom the package still reads SVG, through its own element tree parser — the same four accessors sit in front of both, so nothing else in the package can tell the difference.

Nothing else is optional, because nothing else is used. Encryption and barcodes are planned work; this page will name the packages they need once they exist.

COMMANDS

Creating a document

tclpdf new ?option value ...?

: Creates a document object and returns its command name. Options: -unit (mm, the default, or pt, cm, in), -format (a page format name such as a4, or a pair of numbers in the document unit), -orientation (portrait or landscape), -version (1.0 through 1.7, or 2.0; default 1.7) and -compress (1 by default — content streams are deflated; 0 writes them plainly, which is for reading the output, not for shipping it).

A size given as two numbers is taken as it stands. It is turned only if an orientation is asked for as well — {88 55} stays 88 by 55.

tclpdf formats

: The known page format names.

doc configure ?option value ...?

: Changes the document options after creation. It returns nothing; read a single option with cget. A page already added keeps the size it was given.

doc cget option

: One option.

doc destroy

: Releases the object. A document that was written is not needed afterwards.

Coordinates and units

Positions are given in the document unit, and y counts from the top of the page downwards — the opposite of PDF's own convention, which the package converts on the way out. -at always names the top left corner of what is being placed. A form or a pattern script is no exception: inside them the origin is that object's own top left corner and y counts downwards, exactly as on the page — the conversion happens against the object's height instead of the page's.

doc coords x y

: The point in PDF user space, for a caller that needs it.

doc distance value ?unit?

: A length in points.

doc extent size ?unit?

: A {width height} pair in points.

Pages

doc page add ?-format f? ?-orientation o? ?-rotate deg?

: Adds a page and makes it current. Without options the document defaults apply. -rotate must be a multiple of 90.

doc page count

: The number of pages.

doc page current

: The index of the current page, counting from zero.

doc page size ?index?

: {width height} in the document unit.

doc page box name ?value? ?index?

: Reads or sets one of the five page boxes: media, crop, bleed, trim or art. The value is {x0 y0 x1 y1} in the document unit — two corners, not a corner and a size. A box may start away from zero; the size is then the difference of the pairs, and the caller's origin follows the box rather than the axis.

doc page content ?index?

: The content stream built for that page so far, as text. For diagnosis: it shows which operators a call actually produced, which is the only way to see a graphics state that leaks past the shape that set it.

Text

doc font ?-family f? ?-style s? ?-size n? ?-color c? ...

: Sets the font state, which stays in force until changed. Without arguments it returns the current state as a dictionary, including the resolved font name.

-family takes one of the fourteen standard faces (helvetica, times, courier, symbol, zapfdingbats), an exact PostScript name such as Times-Italic, or the alias of an embedded face. -style takes bold, italic or both. -size is always in points. Further options: -spacing (extra space between glyphs — see below), -wordSpacing, -stretch (horizontal scaling in percent), -leading (line spacing, default 1.2 times the size) and -rise (baseline shift, for super- and subscript).

-spacing adds its space between glyphs, not between characters, because that is where the PDF operator behind it puts it. The two differ only when ligatures are in play: office is six characters and, in a face that has the ffi ligature, four glyphs — so -spacing opens three gaps there, not five. For classic letterspacing, where every letter stands apart, set -ligatures 0 in the same call; a ligature says the letters belong close together, which is the opposite of what letterspacing says.

-ligatures applies the standard ligatures (liga) of an embedded face and is on by default. Where a face has one, the letters of fi, ff, ffi and their relatives are drawn as the single glyph the designer made for them. The characters are unaffected: the ToUnicode map carries the ligature back to the letters it was made from, so the text is copied and searched as it was written. Only liga is read - not the discretionary (dlig) or historical (hlig) sets, which the feature registry has off, and not the required ligatures (rlig) of the Arabic scripts, which need a shaper this package does not have. Note that a ligature need not change any width: measured on DejaVu Sans, fi and fl take exactly the room the two letters took, while ff is narrower.

-unshaped draws text from a writing system this package cannot set correctly, and is off by default — which means such text is refused rather than drawn wrong. See "Writing systems" below.

-kerning applies the pair kerning of an embedded face and is on by default. The amounts are read from the font while writing - from its GPOS table where it has kerning lookups, otherwise from its kern table, which is the order ISO/IEC 14496-22 prescribes - and are written into the content stream, so neither table is embedded. Kerning changes the width of every line it touches, and the width is measured with it: textWidth, the line breaker and the table column widths all see the kerned figures. Set -kerning to 0 where a document has to come out exactly as an earlier release produced it. The fourteen standard faces are unaffected: the metrics shipped for them carry widths per byte value, not kerning pairs.

A combining accent between two letters does not interrupt a pair. Most faces tell the reader to leave marks out of the sequence while kerning - measured here, 51 of the 73 faces on this machine that kern from GPOS do - so A + U+0301 + V is kerned as the pair A V, exactly as the single character U+00C1 followed by V has always been. The adjustment is applied in front of the second letter, which leaves the accent where the font puts it. What tclpdf does not do is position the mark itself: GPOS mark attachment is not read, so a combining glyph is drawn at the pen position with the side bearing its face gives it.

What that means in practice. Text that arrives composed — Ü as U+00DC, Á as U+00C1 — is unaffected, because the face has a finished glyph for it and there is no mark to place. That covers every European language, and it is what databases, XML and the web deliver. Text that arrives decomposedÜ as U+0055 followed by U+0308 — comes out with its accents visibly displaced, and so do combinations Unicode has no composed form for at all, such as a letter carrying both a macron and an acute. Where such text has to be set, normalise it to NFC before it reaches the package.

Writing systems

tclpdf maps one character to one glyph and draws them left to right. For most scripts that is the entire job, and they are set correctly: Latin, Greek, Cyrillic, the CJK scripts, Tibetan, Cuneiform, Egyptian Hieroglyphs, symbol and emoji faces.

Some scripts need more, and there tclpdf refuses to draw rather than draw something wrong:

needs scripts
contextual shaping — the glyph depends on its neighbours — and right-to-left ordering Arabic, Syriac, N'Ko, Mandaic
right-to-left ordering Hebrew, Thaana, Samaritan
reordering and conjunct forms Devanagari, Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada, Malayalam, Sinhala, Myanmar, Khmer
mark placement and reordering Thai, Lao

The refusal is the same rule the package applies to a character the face has no glyph for: a reader shows the wrong text, a validator says nothing, and only this end can notice. Arabic drawn without shaping comes out as isolated letter forms in reverse order — it looks like text and is not.

-unshaped 1 turns the refusal off and draws the characters as isolated glyphs in logical order. That is right for exactly one case: a script that needs only ordering, in a line with no digits and no Latin words — Hebrew without nikud, say — where reversing the string before passing it in gives a correct line.

doc font embed alias path ?-subset 0? ?-metrics path? ?-axes {tag value …}? ?-instance name?

: Embeds a font file under an alias, which is then usable as -family. The file says what it is; the extension is not consulted.

: TrueType (.ttf) is subset to the glyphs actually used, addressed by glyph number, and gets kerning and ligatures from the font's own tables. OpenType with CFF outlines (.otf) goes in whole, as /FontFile3 with /Subtype /OpenType and a /CIDFontType0 descendant — everything else about it, the character mapping and the widths included, is read exactly as for TrueType. Type 1 (.pfb, .pfa, .t1) goes in whole as well and is addressed by single bytes through WinAnsiEncoding — see below.

: Why a CFF face is not subsetted. Subsetting rewrites the loca and glyf tables, and a CFF font has neither: its outlines are charstrings in a table this package does not read. So the whole file is embedded, around 40 to 100 KB depending on the face. Where the same face exists as .ttf, that form is the better choice for a document that uses it for a heading and nothing else — a subset of a few words is a fraction of either. -subset is accepted and has no effect; the face carries no subset prefix, because nothing was subsetted.

: Type 1 needs its metrics beside it. The widths of a Type 1 face are inside its encrypted charstrings, so they come from the AFM instead: font embed looks for the same base name with .afm, and -metrics names it where it sits elsewhere. Without metrics the face is refused rather than embedded with no widths.

Embedding a Type 1 program is a copy — the file already consists of the three pieces PDF asks for as Length1, Length2 and Length3. Nothing is decrypted, and consequently nothing is subsetted: the face goes in whole, which for a text face is 25 to 105 KB. -subset does not apply. Kerning and ligatures do not either; a Type 1 program carries neither GPOS nor GSUB, and the kern pairs an AFM may list are not read.

The reach of such a face is the 224 positions of WinAnsiEncoding, as with the standard fourteen — a character outside it is an error, not a blank. Where a document needs more, TrueType is the format to embed.

: Variable fonts carry one set of outlines plus a rule for bending them, and -axes or -instance says where on that rule to embed. -axes {wght 620 wdth 87} names axis values directly; -instance "Condensed Bold" names a point the designer named, taken from the font's own name table. The two combine — -instance sets the starting point and -axes overrides single axes of it. Without either, the face is embedded at its default position, which is what sits in its outline table.

Each point on the axes is its own embedded font, because PDF has nowhere to put an axis value: not in the font dictionary, not in the descriptor. The outlines are therefore computed while embedding and go into the file as a fixed instance. Nine weights on a page mean nine subsets.

The advance widths vary with the axes and are read from the same source as the outlines, so textWidth, the line breaker and the table columns all measure the instance that is actually drawn. An axis the font does not have, or a named instance it does not offer, is an error that lists the ones it does. -axes on a face without an fvar table is refused rather than ignored.

What varies and what does not: outlines, component offsets and advance widths do. Hinting programs are carried through unchanged, and the deltas an HVAR table would add on top of the phantom points are not read — measured, neither Roboto nor any face in the examples ships one.

: Which file to embed for a standard face. A document that has to be archivable may not leave a font unembedded, and that includes the fourteen standard faces — PDF/A makes no exception for them. Their outlines were never released, so an equivalent has to take their place. The table below maps each of the fourteen to the file that stands in for it.

PDF standard 14 URW Core 35 Adobe Type 1 Adobe OpenType macOS Windows
Helvetica NimbusSans-Regular.ttf Helvetica.pfb HelveticaLTStd-Roman.otf Helvetica Arial.ttf
Helvetica-Bold NimbusSans-Bold.ttf Helvetica-Bold.pfb HelveticaLTStd-Bold.otf Helvetica Bold Arialbd.ttf
Helvetica-Oblique NimbusSans-Oblique.ttf Helvetica-Oblique.pfb HelveticaLTStd-Obl.otf Helvetica Oblique Ariali.ttf
Helvetica-BoldOblique NimbusSans-BoldOblique.ttf Helvetica-BoldOblique.pfb HelveticaLTStd-BoldObl.otf Helvetica Bold Oblique Arialbi.ttf
Times-Roman NimbusRoman-Regular.ttf Times-Roman.pfb TimesLTStd-Roman.otf Times Times.ttf
Times-Bold NimbusRoman-Bold.ttf Times-Bold.pfb TimesLTStd-Bold.otf Times Bold Timesbd.ttf
Times-Italic NimbusRoman-Italic.ttf Times-Italic.pfb TimesLTStd-Italic.otf Times Italic Timesi.ttf
Times-BoldItalic NimbusRoman-BoldItalic.ttf Times-BoldItalic.pfb TimesLTStd-BoldItalic.otf Times Bold Italic Timesbi.ttf
Courier NimbusMonoPS-Regular.ttf Courier.pfb CourierStd.otf Courier Cour.ttf
Courier-Bold NimbusMonoPS-Bold.ttf Courier-Bold.pfb CourierStd-Bold.otf Courier Bold Courbd.ttf
Courier-Oblique NimbusMonoPS-Italic.ttf Courier-Oblique.pfb CourierStd-Oblique.otf Courier Oblique Couri.ttf
Courier-BoldOblique NimbusMonoPS-BoldItalic.ttf Courier-BoldOblique.pfb CourierStd-BoldOblique.otf Courier Bold Oblique Courbi.ttf
Symbol StandardSymbolsPS.ttf Symbol.pfb SymbolStd.otf Symbol Symbol.ttf
ZapfDingbats D050000L.ttf ZapfDingbats.pfb ZapfDingbatsStd.otf Zapf Dingbats

Three of those columns can be embedded as they stand. The URW files are TrueType and go in unchanged; Adobe's Type 1 files go in whole, provided their AFM is beside them; Adobe's OpenType files go in whole as CFF. The macOS entries for Helvetica, Times and Courier are TrueType collections — several faces in one file — and a single face still has to be extracted before it can be used.

Two of the fourteen have no working substitute today. The URW files for Symbol and ZapfDingbats carry a (3,0) symbol cmap and no Unicode one, so font embed refuses them. The twelve text faces are unaffected and cover Helvetica, Times and Courier completely.

The URW faces are metric substitutes, and measured against the metrics this package ships: every one of the 2 592 advances of the twelve text faces matches the standard face it stands in for, across all mapped WinAnsi byte values. They are published under the SIL Open Font License 1.1, so they can be redistributed with a document workflow; Adobe's own outlines cannot.

tclpdf does not install any font. Embedding one means having the file, and which file that is remains the caller's choice — the package reads what it is given. The URW set is version 2.0 of the URW++ Core 35, kept at https://github.com/twardoch/urw-core35-fonts; distributions carry the same fonts as a package of their own, on Debian and its derivatives as fonts-urw-base35. The copies used to write the examples sit in the source archive under examples/assets/fonts/urw-core35-fonts, with the licence texts beside them.

doc font names

: The aliases embedded so far.

doc font info alias

: What the file says about itself: family, postScript, glyphs, unitsPerEm, characters, and the embedding permission as fsType and permission.

doc text string ?-at {x y}? ?-width w? ?-align a? ...

: Draws text. Without -width this is one line, and -align refers to the given point: left starts there, right ends there, center is centred on it; the call returns nothing. With -width the string is broken into a paragraph of that width, -align justify becomes available, and the call returns the y coordinate below the last line, so the next block can continue there.

-anchor chooses what the y coordinate means: baseline (the default) or top. -rotate turns the text about -at. All font options are accepted per call without changing the state.

-height h limits the block. What fits is drawn and the return value becomes a dictionary with y and rest — the text that did not fit, ready to be set in the next column or on the next page. Without -height the return value is the y coordinate as before.

-indent, -indentRight and -firstIndent narrow the column; a negative first indent hangs the opening line out to the left, which is how a numbered clause is set. -paragraphSpacing adds room between paragraphs, on top of the leading.

-avoid takes a list of shapes the text runs around: {rect {x y} {w h}} and {circle {x y} r}. Each line is narrowed by whatever reaches into it and set in the widest free segment, so a circle is followed by its outline rather than by a box around it. The shapes are not drawn — that is a separate call, and the text can just as well keep clear of something invisible.

-avoidMargin d holds the text off every avoided shape by that distance; a shape may state one of its own as a fourth element ({circle {x y} r 7}), which then wins. Without it the words touch the picture, which reads as a mistake however exact the geometry is.

-tag type names what the text is in a tagged document — P unless said otherwise, H1 for a heading, Artifact for what is outside the tree; -expansion text marks the string as an abbreviation and gives its expanded form. Both are described under "Structure and accessibility" below.

Soft hyphens are honoured. U+00AD is not a character but a permission — this word may be broken here. Where the breaker takes the offer, a real hyphen is set at the end of the line; everywhere else the mark stays invisible, in the drawing and in the measurement alike, so the same string can be set in any width. tclpdf does not hyphenate by itself: that needs language data and is a feature of its own. What it does is honour the marks that text arriving from a database, an XML file or an editor already carries. Note that extracting such a line yields the hyphen as well, and that a standard face is no longer refused for carrying the mark.

doc leader left right -at {x y} -width w ?-fill "."? ?-gap d? ?-tag type? ?font options?

: A row with two ends and a filled middle — a table of contents, a price list, a total:

~~~tcl $doc leader "3. Embedding fonts" "24" -at {20 100} -width 120 # 3. Embedding fonts ........................... 24 ~~~

Both ends are measured and the space between them is filled with as many whole copies of -fill as fit, holding -gap clear of each end (1 unit by default). The remainder stays in front of the right hand end, so the figures of several rows line up. -fill may be any string, and an empty one draws nothing at all — which is what a sum under a rule wants. Either end may be empty. Returns the y coordinate one line down, so rows stack without measuring again.

It does not wrap: each end is one line. A left side too long for the width keeps its full length and the fill disappears, rather than moving the figure a reader is looking for. In a tagged document the row is one element and the fill is an artifact — a reader that spelled the dots out would say "dot dot dot dot" between every entry and its number. -tag names the element (P by default), and -tag Artifact takes the whole row out of the tree, which is what a running head is.

doc textPath string -segments {...} ?-align a? ?-offset d? ?-tag type? ?font options?

: Sets one line of text along a path, glyph by glyph, each one turned by the direction the path takes at its own position. The segments are the ones path takes (move, line, curve, close); -align places the string at the start, the middle or the end of the path, and -offset lifts the baseline off it — positive above, negative below. Returns the length of the path, which is what a caller measures a string against beforehand: glyphs that run past the end are dropped rather than piled up there. The path itself is not drawn.

doc pageNumbers -at {x y} ?-format "Page %n of %m"? ?-from n? ?-total n? ?font options?

: Puts a page number on every page. %n is the number, %m the total. The numbers are drawn when the document is written, not when the call is made — which is the only moment the total is known — so the call may come before the pages it numbers. -from leaves the leading pages unnumbered, -total states a total of its own for a document that is part of a larger set. Several calls are independent of each other: a number at the foot and a running title at the head are two of them.

doc textWidth string ?font options?

: The width of a string in the document unit.

doc textHeight string -width w ?options?

: The height a paragraph of that width would take.

doc textLines string -width w ?options?

: The lines a paragraph would be broken into.

Graphics

doc line -from {x y} -to {x y} ?-stroke c? ?-width w?

doc rect -at {x y} -size {w h} ?-radius r? ?-fill c? ?-stroke c?

doc circle -at {x y} -radius r ?-fill c?

doc ellipse -at {x y} -size {w h} ?-fill c?

doc polygon -points {x y x y ...} ?-close 1? ?-fill c?

doc curve -from {x y} -c1 {x y} -c2 {x y} -to {x y}

doc path -segments list ?-fill c? ?-stroke c? ?-rule evenodd?

: The shapes. Common options are -fill and -stroke (a colour), -width (line width), -dash (a pattern), -cap, -join, -miter (the miter limit: how far a pointed join may reach before it is cut to a bevel), -opacity and -blend. -rule takes nonzero (the default) or evenodd and decides which parts of a self-intersecting path count as inside. A segment of -segments is {move x y}, {line x y}, {curve x1 y1 x2 y2 x y} or {close}, in document coordinates.

doc clip -at {x y} -size {w h} ?-rule evenodd? / doc clip -segments list ?-rule evenodd?

: Clips everything drawn afterwards to a rectangle or to an arbitrary path, until the graphics state is restored — so it wants a save around it. -segments takes the same list as path. The path itself is never drawn: it ends in W n instead of a painting operator and only decides what of the following output stays visible. -rule evenodd is what leaves the hole in a ring open. Give either -at with -size or -segments, not both.

doc save / doc restore

: Push and pop the graphics state (q and Q).

doc transform ?-translate {dx dy}? ?-rotate deg? ?-scale s? ?-skew {a b}? ?-at {x y}? ?-matrix {a b c d e f}?

: Multiplies the current transformation matrix. -at names a fixed point to turn, scale or skew about; -translate is a displacement. The two are different things and must not be confused. The parts are applied in the order translate, rotate, skew, scale.

-skew shears by two angles in degrees: the first tilts vertically — y follows x — and the second horizontally, which is the slant an italic-looking stamp needs. -matrix takes the six numbers of a PDF matrix and multiplies them in as they stand, ignoring every other option: the values are the raw cm operands — points, origin at the bottom left, y upwards — for the caller who already has a matrix rather than wants one built.

doc opacity value

: Fill and stroke opacity between 0 and 1.

doc blend mode

: The blend mode: how a colour is combined with what is already on the page. One of Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color or Luminosity. Like the alpha it is graphics state and holds until changed; the shapes take it per call as -blend, which keeps it inside their own save/restore. Compatible is refused — it has been deprecated since PDF 1.4 and means Normal. PDF/A parts 2 and 3 permit every mode listed.

doc style ?options?

: Sets the drawing state — the same options the shapes take, in force until changed.

Colour

A colour is a name (red, steelblue — 148 of them, without Tk), a grey value, {r g b} between 0 and 1, {c m y k}, or a registered separation. Fills may also name a pattern: {pattern sky}.

Images

doc image embed alias ?path? ?-data bytes? ?-type *auto jpeg png*?

: Reads a JPEG or PNG and prepares it for placing. JPEG data passes through untouched as /DCTDecode; a PNG without alpha passes through as /FlateDecode; only a PNG with an alpha channel is decoded, to split the channel into an /SMask.

: -data takes the bytes instead of a file name — for an image that never was a file: a canvas posted from a browser, a plot from a subprocess, a value out of a database. The format is decided by the leading bytes in both cases, so nothing else changes; image info then reports an empty path. A PNG with an alpha channel is by far the most expensive way in — the channel has to be split out in pure Tcl, measured at about a hundred times the cost of the pass-through. Where transparency is not needed, JPEG or a PNG without alpha is the cheaper choice, and for a browser canvas that means toDataURL("image/jpeg").

doc image place alias -at {x y} ?-width w? ?-height h? ?-size {w h}? ?-scale s? ?-dpi n? ?-rotate deg? ?-opacity o? ?-alt text? ?-artifact bool?

: Places an embedded image. Giving only one of width and height keeps the aspect ratio; -size sets both extents at once and keeps nothing. Without any of them the natural size applies, and -dpi decides it: a pixel is 1/dpi of an inch, and the default 72 makes one pixel one point — -dpi 300 places a scan at the size it was scanned from. -scale multiplies that natural size and yields to any explicit width, height or size. The same image placed five times is stored once.

doc image draw ?path? ?-data bytes? -at {x y} ?options? ?-alt text? ?-artifact bool?

: Embeds and places in one call, for an image used once. It takes -data as well; with no file name to key the cache on, the bytes themselves are the key, so the same picture drawn twice still travels once. The options are those of image place, -alt and -artifact among them — what they do is described under Structure and accessibility.

doc image info alias / doc image size alias / doc image names

: What the file is, its size, and the aliases embedded so far. image info answers type, path, bytes, width, height and bitDepth for both formats; a PNG adds colorType, alpha and transparency, a JPEG adds components (1 grey, 3 RGB, 4 CMYK) and alpha 0. image size is the size a placement would come out at, in the document unit, and takes the same sizing options as place — what a caller needs to lay out around a picture. image names lists the aliases.

Tables

doc table -at {x y} -width w ?-head rows? -body rows ?-foot rows? ?options?

: Draws a table and returns the y coordinate below it. A row is a list of cells; a cell is a string, or a dictionary with text and any of colSpan, rowSpan, align, valign and style keys. align and valign may equally be written inside style; on the cell itself they win over column, section and theme.

A row is as tall as its tallest cell. Where one cell carries running text over several lines and its neighbours hold a single line each, valign says where in the row those single lines sit — top by default, or middle or bottom. In a table whose rows are all one line it makes no difference, which is why the default is the one that leaves such a table alone.

Unknown keys are an error, in a cell, in a style, and in a column description — as they already were among the -options. A dictionary written over defaults otherwise refuses nothing: a mistyped key is carried along, read by nobody, and the caller sees the default and no message.

A cell is read as a dictionary when its first word is one of the cell keys and it has an even word count; anything else is the cell's text. Tcl draws no line between a string and a dictionary, so this is a decision rather than a detection, and it leaves one ambiguous case: a plain string that begins with text, align, colSpan, rowSpan, valign or style and happens to have an even word count is read as a dictionary. It then names the offending key rather than silently keeping a fragment of the sentence. Write such a string as a cell dictionary — {text "text is set here"} — and it is unambiguous.

Column widths come in three kinds, resolved in that order: fixed ({width 34}), weighted ({weight 1}) and automatic — the rest is shared according to how wide the content actually is.

-columns describes the columns, -theme picks striped, grid or plain, and -style, -headStyle, -bodyStyle and -footStyle set fonts, colours and padding. -alternateFill colours every second body row — the stripe the striped theme brings, replaceable with any colour; the other themes have none. -minRowHeight is the least height a row may take, for rows whose content alone would leave them shallower. The border style key takes none, all, horizontal, vertical or outer; outer frames the block once per page instead of ruling every cell. -repeatHead and -repeatFoot carry those sections onto each page. -horizontalBreak deals a table too wide for the page over further pages, with -repeatColumns keeping the leading columns on each.

-top and -bottom are the type area a breaking table works within, which is not the same thing as where it sits. -at says where this table starts on its first page; -top says where it resumes on every page after that, and -bottom how far down it may run. Both default to a margin of five percent of the page height — 14.85 mm and 282.15 mm on A4 — and neither is taken from -at, because where a table happens to begin says nothing about where the page ends. Set -top to clear a running head, and -bottom to clear a footer.

-align decimal lines the decimal separators of a column up under each other, measured across head, body and foot together; -decimal picks the separator, . by default. Cells that are not numbers are set flush right.

Rows tied together by a rowSpan are never split across a page break: the whole group moves.

Four hooks are called: -didParseCell once per cell before it is measured, -willDrawCell and -didDrawCell around drawing it, and -didDrawPage after each page. Each receives a dictionary and the document, in that order; returning 0 from willDrawCell skips that cell, and a dictionary returned from didParseCell replaces the cell.

doc table layout ?same options?

: Measures without drawing — what a caller needs to decide whether a table still fits.

doc table themes

: The names of the built-in themes: plain, striped, grid.

Gradients and patterns

doc shading axial -at {x y} -size {w h} -colors list ?-angle deg? ?-from {x y}? ?-to {x y}?

doc shading radial -at {x y} -size {w h} -colors list ?-center {x y}? ?-radius r? ?-innerRadius r? ?-focus {x y}?

: Draws a gradient directly — shading types 2 and 3, clipped to the rectangle of -at and -size, which both forms require. More than two colours are stitched together.

For an axial gradient, -angle turns the run: 0 is left to right, counting clockwise, so 90 runs top to bottom; the end points are derived from the rectangle. -from and -to name the two end points directly instead and win over -angle — for a run that starts or ends inside the rectangle, or one that does not pass through its centre.

A radial gradient runs from an inner to an outer circle. -center and -radius describe the outer one — by default the middle of the rectangle and half its longer side. -innerRadius (default 0) is the radius of the inner circle and -focus its centre, the same as -center unless given; moved off it, the highlight of a sphere sits away from the middle.

-stops positions the colours: one value per colour, between 0 and 1 and ascending, and the count has to match -colors or the call is refused. Only the inner values have an effect — the first and the last colour sit at the ends regardless — so it matters from three colours on. Without it the colours are spaced evenly.

-extend is a pair of booleans, {1 1} by default: whether the first and the last colour continue beyond the ends of the gradient or stop there.

doc shading pattern name type ?options? ?-matrix {a b c d e f}?

: Registers a gradient as a pattern, usable afterwards as {pattern name} in any fill. type is axial or radial, and the options are the ones above.

-matrix maps the gradient into the page. A pattern is bound to the default space of the page and ignores whatever transformation is active when the shape is painted (8.7.3.1) — filled under a transform, the shape lands in the right place and the gradient inside it somewhere else. A caller drawing under a transform passes that transform here; the coordinates are then read in the space the matrix maps from.

doc shading names

: The gradients registered so far, by name.

doc pattern create name -size {w h} ?-step {sx sy}? ?-unit u? -script body

: A tiling pattern. Inside the script the tile is drawn like a small page, and every shape command works unchanged.

-step is how far apart the tiles sit, the tile size unless given: equal, they touch; larger, and the background shows through between them — a sparse watermark rather than a hatch. -unit reads -size and -step in another unit than the document's.

doc pattern names / doc pattern size name

: The registered tiling patterns, and the size of one of them as {w h} in the document unit.

Reusable content

doc form create name -size {w h} ?-unit u? -script body

: Defines a form XObject — a drawing stored once and placed as often as wanted. Inside the script the origin is the form's own top left corner and y counts downwards, the same way it does on a page. -unit reads -size in another unit than the document's.

doc form place name -at {x y} ?-scale s? ?-rotate deg? ?-opacity o? ?-alt text? ?-artifact bool?

: Places it. Placing is a transformation, not a redraw: the object stays one object in the file.

doc form names / doc form size name

SVG

doc svg path -at {x y} ?-width w? ?-height h? ?-size {w h}? ?-scale s? ?-opacity o? ?-alt text? ?-artifact bool?

doc svg -data markup -at {x y} ?same options?

: Draws an SVG as real vectors — paths, shapes, groups, transforms, use, text and gradients become PDF operators, not a picture. Returns {x y width height} of what was drawn. Without a size the drawing's own dimensions apply; with one it is fitted, keeping the aspect ratio. -size gives both extents at once, -scale multiplies the drawing's own size, and -opacity applies to the drawing as a whole.

-data takes the markup from a Tcl variable instead of a file, which is what a generator wants: whatever produces the SVG hands it over directly, with no temporary file in between. Everything else is the same, including -alt and -artifact — with a description the drawing becomes a Figure carrying it, without one an artifact, and -artifact 1 says that this is what was meant.

~~~tcl set markup "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"40\"> <circle cx=\"20\" cy=\"20\" r=\"18\" fill=\"$colour\"/></svg>" $doc svg -data $markup -at {20 20} -width 12 -alt "Status: $state" ~~~

Note that svg size below takes a file name only; the size of markup in a variable is what drawing it returns.

Text in a drawing uses the same faces as text does. font-family is a comma-separated wish list and the first name that resolves wins; an embedded face resolves under the alias it was embedded as, so font-family="house" finds it after font embed house …. That settles three things at once: the drawing may use any character the face has rather than the 224 positions of WinAnsi — Łódź and Москва are ordinary labels, not errors —, it can be set in the house face, and it can be part of an archivable document, which embeds every font it finds. Kerning and ligatures apply as they do elsewhere.

A list nobody can satisfy ends at helvetica rather than failing, which is what a drawing wants; in a PDF/A document that face is not embedded and write then refuses the document, so it is worth naming a face the document has. font-weight and font-style are not read — a face is chosen by name.

doc svg info

: What the LAST drawing skipped — the elements the module does not draw. It reports on the document, not on a file: anything written after info is accepted and ignored, so svg info some.svg says nothing about some.svg.

doc svg size path

: The natural size of an SVG file.

Barcodes

tclpdf has no barcode encoder and does not need one. tzint, the Tcl binding for libzint, encodes into SVG, and svg -data draws that as real vectors — a MaxiCode keeps its hexagons and rings, and the digits under an EAN stay characters that can be copied. No temporary file is involved: the markup goes from one variable into the drawing.

tzint is not a dependency. It is a C extension and therefore platform bound; nothing in tclpdf requires it, and a document that never draws a barcode never notices it.

package require tzint
::tzint::Encode svg markup "1234567890128" -barcode ean13
$doc svg -data $markup -at {20 20} -height 16 -alt "EAN-13 1234567890128"

Two things about the encoder are worth knowing, because neither is obvious and both cost an afternoon:

The status is three-valued. 0 means silence, 1 to 4 are warnings with a perfectly good symbol, and only 5 and up mean nothing was produced. Code that tests for "not zero" throws usable barcodes away — and on an error the target variable is not cleared but left as it was, so code that looks at the variable instead of the status quietly draws the previous barcode again. Both were measured: a Euro sign is a warning for qrcode and an error for code128.

set rc [::tzint::Encode svg markup $data -barcode qrcode -stat info]
if {$rc >= 5} {
    error "no barcode: [dict get $info error]"
}

An EPC-QR (GiroCode) needs -eci 26. The dataset states its own character set in line 3, and without the option the encoder picks one itself — the symbol scans, but its encoding is not the one the data claims. -security 2 is the error correction level the specification asks for.

For an archivable document the clear text line needs an embedded face, and the encoder names the one it wants: tzint writes font-family="OCRB, monospace" into its markup, so embedding a face under the alias OCRB is enough — the digits are then set in real OCR-B, stay text rather than becoming picture, and the markup is not touched. Where that face is not to hand, -notext 1 leaves the line out and text sets it.

doc attach path ?-name n? ?-mime m? ?-description d? ?-relationship r? ?-date d? ?-compress 0?

doc attach -data bytes -name n ?same options?

: Attaches a file. -relationship is the /AFRelationship value — Alternative, Data, Source, Supplement or Unspecified.

-data takes the bytes instead of a file — for an attachment that never was one. -name is then required, because there is no file name to fall back on; without it the call is refused.

doc attachments

: What has been attached.

doc link -at {x y} -size {w h} ?-url u? ?-page n? ?-structure name? ?-to {x y}? ?-zoom z? ?-tooltip t?

: A link rectangle over an area of the page — to a URL, to a page of this document, or to a named structure element. It is drawn as nothing: the visible text is a separate call.

For a page destination, -to names the point to land on and -zoom the magnification the reader applies there (1 is 100 %); without -zoom the reader keeps the one it has, and without -to the whole page is fitted and -zoom does not apply.

-structure takes the -name of a structure element and writes a structure destination (12.3.2.3), which names the element rather than a place on a page and therefore still lands on the right thing after the content above it has moved. PDF/UA-2 asks for internal targets to be written that way. It is written as a GoTo action carrying both the structure destination (/SD) and a page destination (/D) to the element's first page — /XYZ at the top of its first content, or /Fit when no position is known — so a reader that does not understand structure destinations still lands on the right page. It needs a tagged document. bookmark takes the same option.

-tooltip becomes the annotation's Contents, which PDF/UA requires on every link (7.18.5): it is what a reader announces instead of just saying "link".

doc bookmark title ?-page n? ?-at {x y}? ?-parent id? ?-open 1? ?-structure name?

: Adds an outline entry and returns its id, which can be the -parent of further entries. Bookmarks are turned into objects when the document is written. -open decides whether the entry shows its children unfolded, and is on by default; -open 0 collapses a branch until the reader asks for it.

doc bookmarks

: The outline built so far.

Metadata

doc info key ?value?

: Reads or sets an entry of the information dictionary: Title, Author, Subject, Keywords, Creator, Producer.

doc language ?tag?

: The natural language of the document as an RFC 3066 tag — de, de-DE, en-GB. It goes into the catalog and is what lets a screen reader pronounce the text correctly. PDF/A-3a and PDF/UA require it.

doc metadata ?xml?

: Reads or sets the XMP packet directly. Normally the package writes it: set by the caller it is kept as given; otherwise the packet is rebuilt on every write from the document's current title, language and declarations, so it never lags behind the Info dictionary.

doc catalogEntry key ?value?

: An entry of the document catalog, for anything the package does not offer by name.

Structure and accessibility

doc tagged ?0 1?

: Whether the document writes a structure tree. Off by default, and it has to be set before anything is drawn: the brackets go into the content stream as it is written.

A tagged document carries a second, invisible layer saying what the marks on a page are — a heading, a paragraph, a table cell — rather than how they look. The drawing does not change. Reading software needs it: without a tree it follows the order the content stream happens to have, which on a two column page runs across both columns. PDF/UA and PDF/A level A require it.

doc structure type ?-name name? ?-alt text? ?-lang tag? ?-title text? ?-actualText text? ?-expansion text? ?-scope side? ?-numbering style? ?-bbox {x y w h}? ?-colSpan n? ?-rowSpan n? -script body

: Opens a structure element, runs body with it open and closes it again — including when the body fails, so a half open tree cannot reach the file. Returns whatever the body returned. type is one of the standard types of ISO 32000-1 14.8.4; an unknown one is refused at the call rather than in a validator later.

Elements nest by nesting the calls. Grouping types — Sect, Div, L, LI, Table, TR and their kin — do not hold content themselves: text drawn inside an open Sect becomes a P within it, which is what the nesting rules ask for.

Most documents need few of these. A table knows it is a table and a paragraph knows it is a paragraph, so those tag themselves; structure is for the grouping a writer cannot infer.

: What is derived, and what has to be said. text becomes a P, and one call is one element however many lines it breaks into. table becomes a Table with TR, TH and TD, and the fill and rules of its cells become artifacts. image becomes an artifact unless -alt describes it, and then a Figure carrying that description.

-alt appears on image place, image draw, form place and svg for the same reason and with the same effect: with it the drawing becomes a Figure carrying that description, without it an artifact. -artifact 1 on the same four calls says that the artifact is intended — this one is decoration, and no description is missing. The two contradict each other and are refused together. The difference matters because an artifact is the one way real content passes a reader entirely, and PDF/UA allows it only for decoration: a graphic that became an artifact with neither option was never judged either way, and the document keeps a note of it — an artifact by default is what most pictures are, an artifact by intent is what a validator cannot ask for and a caller can say. In an untagged document neither option changes anything. A form or a drawing is one piece of marked content however many operators it contains — bracketing per element would scatter one illustration over dozens of leaves, and the parts of a drawing mean nothing on their own. Nothing inside a form, a pattern or a page-number XObject is marked at all: those are content streams of their own, mark numbers are unique per stream, and it is the invocation that carries the marking.

A hyphen the line breaker inserted at a soft-hyphen offer is bracketed as a break rather than left to look like part of the word: it sits in a Span with an empty ActualText, so extracted text gives the word back whole (14.8.2.6). A hyphen the text brought with it is untouched — E-Mail stays E-Mail. Only in a tagged document.

What no writer can infer is whether a line of text is a heading — neither its size nor its weight says so. That is what -tag on text is for: -tag H1, -tag Caption, and so on. -tag Artifact takes the text out of the tree altogether, which is what a running head or a page number needs; under PDF/UA anything left unmarked counts as a defect.

-expansion text is the expanded form of an abbreviation (ISO 32000 14.9.5) — PDF/UA asks that abbreviations be expanded (7.20), and a reader that is asked reads the expansion out. On structure it goes onto the element as /E; on texttext "EU" -expansion "European Union" — the word becomes a Span carrying it, inside the paragraph that is open, or inside the one the call would have made. It needs a tagged document and is refused without one, and it cannot go on an artifact.

: Attributes. Five options write standard attributes onto the element, each one only where the standard allows it — used elsewhere they would be written and then ignored, so they are refused at the call instead.

-scope takes Row, Column or Both and belongs on a TH; it says which way a header cell heads. A table sets it by itself: its head row heads columns. -numbering belongs on an L and takes Decimal, UpperRoman, Disc and the other values of ISO 32000 Table 347; PDF/UA makes it mandatory for an ordered list, and no writer can derive it — the label is drawn text, and 1. and - look the same from here. -bbox belongs on a Figure, Formula or Table and takes the same four numbers as a rectangle; it is not required by the letter of the standard, but the reading tools rely on it. A picture placed with -alt gets one by itself. -colSpan and -rowSpan belong on a cell and are written by the table itself where it spans.

: Types beyond 1.7. ISO 32000-2 adds Title, Aside, DocumentFragment, Sub, FENote, Em, Strong and the headings H7 to H10. They are accepted only in a 2.0 file — in a 1.7 one they would validate as non-standard types with no role map — and ua -part 2 is the ordinary way to get one. Twelve older types (Art, BlockQuote, TOC, TOCI, Index, Private, Quote, Note, Reference, BibEntry, Code and the generic H — the 2.0 namespace knows only the numbered headings) exist only in the 1.7 namespace and keep it even inside a 2.0 tree.

A leaf type such as P or H1 holds text and inline markup — Span, Em, Strong, Link, Figure and their kin — but no block element: a P inside a P is the standing example of what Annex L forbids.

: Naming an element. -name gives the element a name that a link or a bookmark points at with -structure. A structure destination names the element rather than a place on a page (12.3.2.3), so it still lands on the right thing after the content above it has grown — PDF/UA-2 asks for internal targets to be written that way. It is written as a GoTo action carrying both the structure destination (/SD) and a page destination (/D) to the element's first page — /XYZ at the top of its first content, or /Fit when no position is known — so a reader that does not understand structure destinations still lands on the right page. The name has to be unique and may be used before it is declared, which a link pointing forward at a later section needs. An unknown one is reported when the document is written, naming it.

: Artifacts name their kind. What is not in the tree is bracketed as an artifact, and the bracket says which sort it is: Pagination for a page number, Layout for everything else this package produces. PDF/UA-2 requires the naming; earlier versions permit it, so it is written either way and a document does not have to be redrawn when it is upgraded.

-tag takes the kind as well, as a list: -tag {Artifact Pagination Header} marks a running head, {Artifact Pagination Footer} a running foot, which is what PDF/UA asks for (7.8). pageNumbers works it out by itself — above the middle of the page it is a head, below it a foot — because that is the one place that knows.

PDF/UA

doc ua ?0 1? doc ua ?-part 1 2? ?-revision year? ?-wtpdf levels?

: Declares PDF/UA conformance — the promise that the document can be used by someone who cannot see the page. The boolean form means part 1 (ISO 14289-1) and is what a letter, an invoice or a briefing needs; part 2 (ISO 14289-2) is a PDF 2.0 format and is asked for by name.

Off by default and explicit, as pdfa is. The claim is legally meaningful in public procurement, so nothing should acquire it as a side effect — and a document using the standard 14 faces cannot make it at all.

: What it insists on, checked when the file is written. A title (info Title), a language (language), every font embedded — the standard 14 included, which is stricter than PDF/A and rules out Symbol and ZapfDingbats entirely, as neither has an embeddable representative. Headings starting at H1 with no level skipped. Tables with the same number of cells in every row, which colSpan and rowSpan cannot deliver. A description on every link, which is what link -tooltip writes. Every picture, drawing and form placement either described with -alt or declared decoration with -artifact 1 — one that became an artifact with neither was never judged, and an artifact may carry nothing a reader needs (7.1). Lists whose numbering and labels agree (7.6): an L with -numbering other than None needs a Lbl in every LI, and items carrying a Lbl need the L to say what they are — Decimal, Disc, … or None. DisplayDocTitle still on: ua sets it, and a later viewerPreferences -displayDocTitle 0 is refused rather than written. Part 2 adds a Desc on every attachment and forbids the generic H.

All of them are reported at once rather than one per run, and each message names the call to change. Writing fails; no file is left behind.

: What it contributes by itself: the pdfuaid schema in the XMP, ViewerPreferences with DisplayDocTitle, and for part 2 the 2.0 structure namespace and the file version. None of them moves a mark on a page. In a document that also declares PDF/A, the PDF/A extension schema describing pdfuaid is written as well — without it PDF/A refuses a schema it does not know.

-revision is the year of the edition claimed, four digits, and goes into the metadata as pdfuaid:rev for part 2 (ISO 14289-2 Table 1); the default is 2024, the year part 2 was published. Anything that is not a four-digit year is refused at the call.

-wtpdf adds a Well-Tagged PDF declaration and takes reuse, accessibility or both. It goes with part 2 only. The identifier is written in two spellings: WTPDF 1.0 gives the URI with a slash before the fragment, veraPDF 1.30 tests for the form without one, and a document that has to satisfy both carries both — which the declaration mechanism expressly allows.

: PDF/UA-2 and PDF/A-3 cannot be combined. Part 2 needs PDF 2.0, PDF/A-3 is a 1.7 format, and so an accessible ZUGFeRD invoice is ua 1 together with pdfa -part 3. Declaring both is refused at the call that creates the contradiction.

doc ua state

: What has been declared, as a dictionary: part, revision, wtpdf, registered. Empty before ua was called.

Viewer preferences

doc viewerPreferences ?-key value ...?

: How a reader should present the document (ISO 32000 12.2). Without arguments it answers with what has been set so far.

Calls accumulate: each one sets the keys it names and leaves the rest alone, so a document can state its window wishes in one place and its printing wishes in another.

Booleans: -hideToolbar, -hideMenubar, -hideWindowUI, -fitWindow, -centerWindow, -displayDocTitle, -pickTrayByPDFSize. Names: -nonFullScreenPageMode (UseNone, UseOutlines, UseThumbs, UseOC), -direction (L2R, R2L), -printScaling (None, AppDefault), -duplex (Simplex, DuplexFlipShortEdge, DuplexFlipLongEdge). And -numCopies, a positive integer.

A misspelled value is refused at the call. It has to be: a reader that meets one falls back to its default silently and nothing anywhere reports a problem — the document simply prints on one side for the rest of its life.

ViewArea, ViewClip, PrintArea and PrintClip are deliberately absent. They are deprecated in PDF 2.0 and no reader tested here acts on them.

Page labels

doc pageLabels ?-from index? ?-style s? ?-prefix text? ?-start n?

: What a reader calls each page (ISO 32000 12.4.2). A page has two numbers — the index the file counts it by and the number printed on it — and a reader shows the first unless the file says otherwise; then "go to page 3" lands on the third sheet while the sheet reading "3" is the sixth. One call is one range: it begins at the page -from, an index counted from 0 like everywhere else, and runs until the next range begins. Several calls make several ranges, in whatever order they come; a second call for the same index replaces the first. Without arguments it answers the ranges set so far, as a dictionary from index to style, prefix and start, in page order.

-style takes D (decimal, the default), R and r (roman), A and a (letters), or none for a range that carries a prefix and no number — a cover called "Cover". -prefix is put before the number, -start is the number the range begins with, a positive integer, 1 unless said otherwise. A misspelled style is refused at the call, because a reader that meets one ignores it silently.

The tree has to begin at index 0. A document that labels only its body from page 4 on has said nothing about the pages before, so a range without a style is put in front — the standard's own way of saying "no number here" — rather than writing a tree a reader may refuse.

Labels and printed numbers have to agree: Matterhorn 15-001 counts a visible page number that differs from the page label as an accessibility failure, and it is one no validator can see, because the printed number is drawn text. Nothing here is automatic — a document that prints its numbers with pageNumbers -from 3 should say pageLabels -from 0 -start 3 as well, so that the reader's page field shows what the sheet shows.

PDF/A and ZUGFeRD

doc pdfa ?-part n? ?-conformance level? ?-profile path? ?-identifier text?

: Declares PDF/A conformance, writes the output intent with the given ICC profile and raises the file version to match. Parts 2 and 3 are accepted; part 1 is refused because it forbids the transparency this package writes, and part 4 because it needs PDF 2.0.

Without -profile the sRGB profile shipped with the package is used, so every PDF/A file carries an output intent — this package paints in DeviceRGB, and ISO 19005 requires the intent for that. The output condition identifier is read from the profile's own desc tag (sRGB for the shipped one), falls back to the file name, and -identifier overrides it. A profile that does not exist is refused at the call.

-conformance takes B (the default), U or A. Level B promises the document looks the same in fifteen years; level U adds that its text can be extracted and searched reliably, which rests on the ToUnicode map written for every embedded face anyway — so U is the stronger claim at no cost and is worth asking for. Level A adds the structure tree, so it needs tagged 1 before anything is drawn; asked for without it, pdfa names the missing call rather than writing a file that claims 3a and fails validation.

Declaring conformance also turns on a check: every font in the document must be embedded, and writing fails with a message naming the offending face rather than producing a file that a validator rejects later.

doc pdfa state

: What has been set, as a dictionary: part, conformance, profile, identifier, extensions and registered. Empty before pdfa was called.

doc pdfa extension xml

: Adds an extension schema to the XMP packet — the way a profile such as ZUGFeRD announces its own properties. zugferd uses it.

doc zugferd path ?-name n? ?-profile p? ?-type t? ?-icc path? ?-version v? ?-relationship r? ?-description d? ?-compress 0?

: The one call an electronic invoice needs. It reads the profile from the invoice XML (BT-24), declares PDF/A-3B, writes the output intent with the sRGB profile shipped with the package, adds the Factur-X XMP extension schema, and attaches the file as factur-x.xml at document level with the /AFRelationship the profile prescribes — Data for MINIMUM and BASIC WL, Alternative for every fuller profile; -relationship overrides — plus an entry in the names tree and a modification date. Returns the detected profile.

-name is the name a reader looks the attachment up by, and the standards allow exactly four: factur-x.xml, zugferd-invoice.xml, xrechnung.xml and order-x.xml. Without the option the file's own name is kept where it is one of the four and factur-x.xml is taken otherwise; any other -name is refused. -type goes verbatim into fx:DocumentType: INVOICE, the default, or ORDER for an Order-X document. -description replaces the attachment description, which is "profile invoice data" unless given. -compress Flate-compresses the embedded XML and is off by default, so the invoice sits in the file byte for byte as it arrived.

doc zugferd profile xml

: The conformance level named in BT-24 of an invoice XML, read without writing anything. Refuses XML that carries no such identifier.

doc zugferd state

: What was attached and under which profile.

Writing

: PDF 2.0 differences that are written for you. A 2.0 file spells the zone offset of a date without the trailing apostrophe (7.9.4), and the version decides it — declaring ua -part 2 is enough. ProcSet, CharSet and CIDSet, all deprecated in 2.0, are written by no version of this package.

doc write path

: Writes the document to a file. Writing does not finish the document: a second write of an unchanged document produces a byte-identical file, and drawing between two writes works - the second file carries the additions.

doc writeChannel channel

: Writes to an open channel instead of a file — a CGI response, a socket, a pipe. The caller opens and closes it; the channel is put into binary translation here, because that is what decides whether the bytes arrive unchanged. write and writeChannel may be combined freely — the same document can go to a file and into a response.

Events

doc on event script / doc off token / doc subscribers event

: The document publishes events while it is written: beforeWrite, resources, catalog, info and afterWrite, plus pageAdded after each page add. This is how attachments, ZUGFeRD and the output intent attach themselves without the core knowing about them, and it is available to callers for the same purpose. on returns a token; off takes that token, not the event and script again, and accepts an unknown one silently. Subscribers run in registration order and are called with the emitting object followed by whatever the emitter passes on. The write-time events fire on every write, so a subscriber that creates objects must be idempotent: take its object numbers from reservation once and write over them on later runs, instead of reserving fresh ones each time. How to build an extension on top of this — the supported methods, the contracts and two worked examples — is the subject of doc/PLUGINS.md in the source distribution.

SEE ALSO

qpdf(1), veraPDF, pdffonts(1), pdftotext(1), tzint

tzint is a Tcl binding to the Zint barcode library. It produces SVG, which svg -data draws — so barcodes need no code in this package and are not a dependency of it. See the Barcodes section above.

KEYWORDS

pdf, pdf/a, pdf/ua, zugferd, factur-x, truetype, font embedding, invoice, accessibility, barcode

COPYRIGHT

Copyright (C) 2026 Alexander Schoepe, Bochum, DE

Distributed under the MIT License; see the file license.terms.