tclpdf

Timeline
Login

Timeline

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

50 most recent check-ins

2026-08-31
13:24
Version 1.4

- configure.ac and tclpdf.tcl carry 1.4, the manual synopsis follows
- configure regenerated by autoconf leaf check-in: f551c12f1c user: alex tags: trunk, release, version-1.4

13:14
Document timestamps: new module timestamp.tcl, examples, tests, shared signing helpers

- timestamp.tcl - ::tclpdf::sign timestamp appends an RFC 3161 document timestamp (/Type /DocTimeStamp) to a finished file; no signature required, the authority is configurable through -url or a -tsa transport prefix, and the reply's digest, nonce and status are verified before the file is touched
- sign.tcl - FieldName and Ranged hoisted out of sign::add as shared private procs, used by add and timestamp alike
- tests/timestamp.test - network-free suite against a local openssl ts authority with mutation counter-proofs; sign-9.3 follows the grown ensemble message
- examples 08.06 to 08.08, simple to involved: the minimal stamp, the stamped PDF/A archive file, and a signed file stamped on top; without network they fall back to a throwaway authority and say so on the console
- examples/common.tcl - shared timestamp scaffolding; the reachability probe now honours the port in the URL and detects a refused connect
- tools/check.sh - the pdfsig section knows document timestamps and checks their coverage instead of skipping stamped files as unfilled placeholders
- manual and skill reference document the call; configure.ac, pkgIndex.tcl.in and tests/all.tcl carry the new module check-in: d09d9bafb8 user: alex tags: trunk

2026-08-27
23:39
Download page: the label column no longer wraps

- uv/template-download.html - white-space nowrap and top alignment on the three label cells (tclpdf VERSION, source code, tdom), so the generated download.html keeps "tclpdf 1.3" and "source code" on one line check-in: 456b2e4724 user: alex tags: trunk

22:46
Skill reference: a structureResume snippet and -min on hyphenate load

- doc/claude/skills/tclpdf-tcl/reference/09-tagged-ua.md - a Sect resumed on a second pass takes a further child, and resuming a leaf is shown refused as TCLPDF STRUCTURE PLACE leaf; every call the manual describes has a snippet again
- doc/claude/skills/tclpdf-tcl/reference/03-text.md - the hyphenate load call names -min, the shortest word broken at all check-in: 2521448eab user: alex tags: trunk, release, version-1.3

22:39
Version 1.3; the four decisions of round 8 named in the manual, an unknown path letter reported, the shipped profile found through a link, the suite run in its own directory

- configure.ac, tclpdf.tcl, configure - the package is 1.3
- doc/tclpdf.md - a fillable field overrides an explicit -subset 1 on its face, and says so; a mask whose data fail only while its streams are built leaves the ICC profile of its carrier as an unreferenced object, and says so; package require tclpdf 1.3
- svgPath.tcl - a command letter the path grammar does not know ends the path where it stands and is reported through svg info like a missing coordinate (SVG 1.1, 8.3.9); the tokenizer used to match the ten known letters only, so an unknown one vanished and its numbers went to the command before it
- pdfa.tcl - the shipped ICC profile is recognised through a symbolic link or a relative path, so its identifier stays sRGB IEC61966-2.1
- tools/check.sh - make test runs with -tmpdir in a directory of its own, so no fixture stays in the working directory
- tests - svg-2.10 with its mutation proof; pdfa and svgPath raised one minor step check-in: c9286403a3 user: alex tags: trunk

22:03
Norm review round 8 built: object generations kept, one extension schema bag, transactional structure scripts, marks on the right ligature component, SVG transforms the right way round, rotation about the point named

- importRead.tcl, import.tcl, importInfo.tcl, update.tcl, filter.tcl - an object is identified by number and generation through parsing, resolution, copying, update and serialisation; a reference to a generation the file does not have is the null object and a null value is a missing entry; the header of an object stream is checked against the cross-reference table; literal strings read end-of-line as LF and cap an octal escape at a byte under Tcl 8.6 and 9 alike; ASCII85 and ASCIIHex refuse characters outside the encoding; the last of two equal dictionary keys wins and only one is written; every leading zero goes through one decimal reader; an integer beyond 2^31 is serialised as a real; an imported page raises the version to what its source claims and is refused where the document cannot follow; a layer only an XObject refers to reaches OCProperties; the Artifact brackets of an imported page are stripped with its marked content; a Flate stream is bounded at 64 MiB
- xmp.tcl, zugferd.tcl, attach.tcl, sign.tcl - every pdfaExtension contribution is merged into one schemas bag at packet build; a caller's whole metadata packet beside a claim that needs registered schemas is refused by name; the XRECHNUNG profile embeds as xrechnung.xml and the reserved hybrid names cannot be taken twice; sign add reads the catalogue /Version beside the header, bounds -size on the value written and tokenises /ByteRange
- structure.tcl, structureReport.tcl, ua.tcl, link.tcl - structure -script rolls back element, children, names and ids when the script fails before it marked anything, with the content stream as the commit boundary; a -tag inside an open LI is kept or refused instead of becoming LBody; PDF/UA-2 refuses two targets in one Link element, a FENote without its Ref pair, a page destination and a second or misplaced Caption in every element Table 372 names; a link inside a form script is refused
- text.tcl, gsubApply.tcl, markPos.tcl, forms.tcl, font.tcl, subset.tcl, varFont.tcl, colorFont.tcl, morx.tcl, sbix.tcl, kernGpos.tcl - a glyph without a code point stays in its predecessor's cluster; MarkToLigature and MarkToMark attach through the component index the run now carries; ToUnicode gives a character its own CID where two share a skeleton glyph; Apple Color Emoji folds a GPOS-attached glyph into the Type 3 glyph of its base and deduplicates by text and glyph; vertical metrics of a variable instance come from gvar; an ill-formed variation region ignores its axis
- annot.tcl, annotShape.tcl, field.tcl, fieldText.tcl, fieldChoice.tcl, fieldButton.tcl - an annotation inside a form script is refused before anything is written; seven keys check their PDF version through one table; a fillable text or choice field embeds its face whole; a field's structure element is opened only after its widget is checked and rolled back on a later failure; sizes and border widths of every field type are checked at the call
- textBlock.tcl, textAvoid.tcl, leader.tcl, pageLabel.tcl, pageNumber.tcl, document.tcl, table.tcl, tableLayout.tcl - no space glyph after a break hyphen in tagged text; -avoid ends at the foot of the page and leader -width is bounded, so neither loops; pageLabels -start stays an integer; -typeArea and table -at are checked at the call; a colSpan across a -horizontalBreak group is refused; every public method is in the lazy-loader table and a test calls each as the first call of a fresh document
- svg.tcl, svgPaint.tcl, svgElement.tcl, svgPath.tcl, svgClip.tcl, xml.tcl - rotate and skew as SVG 1.1 says; switch, visibility, display in style, currentColor, stroke-width 0, nested viewports, one-stop and unsorted gradients, href inheritance, elliptical radial gradients in objectBoundingBox, stop-opacity reported, tspan in document order, units, miter limit, dash offset, the mask's grey coefficients, Bezier extrema for the bounding box, whitespace, !important and odd polyline coordinates; use cycles, use depth and entity expansion are refused before drawing; every value is checked before the first operator so a refused drawing leaves the page untouched
- page.tcl, xObject.tcl, geometry.tcl, graphics.tcl, pattern.tcl, shading.tcl, pdfObj.tcl, layer.tcl - the CTM is carried per stream and the Figure BBox goes through it; pdfObj written and aboveZero refuse on the value the file carries; a singular placement matrix is refused; -rotate turns about the point -at names through one turnedAbout; an axis without length, page add inside a form and overprint under PDF/A with an ICC CMYK are refused
- image.tcl, imageJpeg.tcl, imagePng.tcl, imagePngAlpha.tcl, imageTiff.tcl, imageTiffStreams.tcl - two pictures with one digest are compared byte for byte; ColorTransform 0 is written for an RGB-coded JPEG by its markers; PNG bit depths are checked against the format's table; every refusal of a picture and its mask is asked before the first object; a truncated JPEG frame, a path beside -data, and an 8-bit TIFF ColorMap are handled
- pdfa.tcl, hyphenate.tcl - the part 4 refusal names what is true and zugferd-2.4 reads the new wording; the module head describes the two-level read
- tests - one or more tests with a mutation proof for every finding, fixtures built in the tests; fourteen tests that held the old behaviour corrected (structure-12.7, 12.9, 15.10, xObject-10.2, field-7.25, zugferd-11.1, 13.5, image-stencil-18.6, svg-2.9, 8.1, text-32.1, import-22.1, importLayer-3.2, update-6.7)
- examples - 05.02 embeds under the name its profile allows, 05.08 pairs its footnote and citation with -ref, 03.04 speaks English, the round 7 leftovers of 01.01, 02.09, 06.02, 09.03 and 11.01, heads of 01.15, 02.06, 02.15, 03.08, 04.03, 08.04 and common.tcl
- Manual, README, skill reference - every changed refusal, option and limit; the error code table regenerated; the skill's check.tcl fails a refusal snippet whose call stops refusing; sixty module versions raised
- Knowingly open: hyphenation keeps its documented two-level rule and the manual now says where libhyphen differs; a mask that fails while its streams are built still leaves the carrier's ICC profile; an unknown command letter in SVG path data is passed over without a report check-in: 438392197a user: alex tags: trunk

13:38
Signature in the structure tree, sign add refuses PDF/UA, ModDate on every attachment, Inf refused as a colour, SVG colour values held finite

- sign.tcl - a visible signature (-rect) is a form field of the document: its widget carries /StructParent, /TU from -tooltip and /Contents from -contents, stands in a Form structure element with an object reference and in the /Fields record like any field, so the three PDF/UA questions read it; an invisible signature stays an artifact, since ISO 14289-2 makes a widget of zero extent one (8.9.2.4.13) and a structure element around it fails the file; sign add refuses a file that claims PDF/UA as TCLPDF SIGN STATE ua before a session is opened, naming the write-time signature as the way
- field.tcl, ua.tcl - the field writer passes the record another module writes; a taken name and the PDF/UA messages name the signature field and the command that describes it
- attach.tcl - /Params carries /ModDate on every attachment, as ISO 32000-2 requires of an associated file (14.13.2, Table 45): the path form takes the file's modification time, -data requires -date (TCLPDF ATTACH ARGUMENT date) rather than a clock that would make two runs write two files; examples 01.04, 06.03 and 10.01 name their dates
- color.tcl - Inf and -Inf are refused wherever a component, a Lab white point or a range is read, through the same predicate that refuses NaN; a white point of Inf used to pass the sign check and fail the write under another module's code
- svgPaint.tcl - a colour component of fill, stroke or an rgba alpha that is not a finite number is refused as TCLPDF SVG COLOUR before the first operator, where it used to die inside min/max or pass as 1.0; a non-finite opacity falls back to the initial value like any other value that is not a number; a stop offset that is no number goes to the shading and is refused there by name
- examples/08.05-signature-accessible.tcl - new: a tagged PDF/UA-1 document with a visible, described signature in the structure tree, and the page says why the invisible one is an artifact
- tests - sign-10.x for both signatures in both orders under both parts, attach-3.6 turned from keeping the missing date to refusing it, eleven attach -data calls in five files name a date, color-parse-1.11 turned from clamping to refusing, svg-finite-41.x, the string guard counts the two widget strings of sign.tcl
- Manual and skill reference - the signature options and the artifact rule, sign state keys, the sign add refusal, -date and its obligation with -data, Inf beside NaN, SVG colour values; error code table regenerated (COLOUR under SVG); six module versions raised
- Knowingly open: stop-color in functional notation (rgb, rgba, hsl) is still refused although SVG 1.1 allows it; an incremental PDF/UA repair in sign add for the package's own files is a roadmap item, not built check-in: 090f6cd151 user: alex tags: trunk

09:25
Reader hardening, COLR radial rings, UA-2 list numbering, NaN colours, update error contract

- importRead.tcl - a number token is held to the grammar of 7.3.3 and anything else is refused as TCLPDF IMPORT SYNTAX with token and offset; an object stream is bounded by its own header before the loop (First inside the stream, N at most what the header can hold, every word a digit string with an offset inside the object part) instead of by whatever the file claims; an unknown cross-reference entry type is the null object of 7.5.8.3 and shadows an older live entry; object and generation numbers written with leading zeros are the numbers they mean, in the reference, at the object head and in the object stream header, through one Decimal helper; a missing path, a directory and an unreadable file are refused at the reader's one entry under TCLPDF IMPORT FILE, so pdf import and the five reading commands answer alike
- importInfo.tcl, import.tcl - the file checks moved into the reader; the Reader wrapper and the copy in pdf import are gone
- update.tcl - a session refuses a missing, directory or unreadable path as TCLPDF UPDATE FILE, and every refusal of the reader underneath reaches the caller under TCLPDF UPDATE with the reader's own class word - at open and at the deferred reads of body and replace - through one Reader helper
- colorFontBand.tcl, colorFontPaint.tcl - a radial band takes the side of an interval from the larger radius of its ends, not from the direction the parameter runs, so a shrinking gradient draws its ring instead of the inverse; a band that lies beyond every corner of the box is told apart from "no clip needed" and skipped, and a gradient whose bands all lie outside answers empty before a shading object is written, so a glyph that draws nothing is refused like any other
- ua.tcl - under part 2 a list whose items carry a Lbl may not say ListNumbering None (ISO 14289-2, 8.2.5.25); part 1 keeps it; the message for a missing -numbering no longer offers None where part 2 refuses it
- pdfObj.tcl, color.tcl - num asks whether a value fits before format sees it, so NaN is refused in the package's words instead of dying inside format; a NaN colour component, Lab white point or range is refused where it is read, in every colour space and at font -color, before it reaches the state; Inf keeps being clamped, in one predicate that turns it into a refusal with one line
- tests - a fixture for object streams and one for padded object numbers, the unreadable-file constraint hoisted into helper.tcl, two tests that carried their target in an exponent rewritten, update-6.2 checks the code and not the message
- Manual and skill reference - number grammar, object stream bounds, null-object semantics, the directory case, the NaN exception to clamping, the part-2 list rule, the UPDATE contract for a whole session; error code table regenerated (FILE under UPDATE); nine module versions raised
- Knowingly open: Inf as a colour component is still clamped and -whitePoint accepts it; the signature widget outside the structure tree (C-01), sign add on a PDF/UA file (C-02) and /ModDate on an associated file (C-07) wait for a decision; content streams are copied byte for byte, so an exponent inside one is not seen check-in: 06f6d1c786 user: alex tags: trunk

06:06
Bitmap colour fonts: sbix faces, AAT morx shaping, and a TrueType collection read by face

- sfnt.tcl - a collection is opened by face (-face, default 0) and rebuilt as a face of its own; the picture tables sbix, CBDT and EBDT stay on disk and are read by offset through openFace, slice and extent, so a 192 MB face costs 0.06 seconds and 28 MB where reading it whole cost 13 seconds and 4.3 GB; a file that is neither a collection nor a bitmap face goes through the old path byte for byte; a table left on disk is refused by sfnt table rather than answered empty
- morx.tcl - new: the AAT extended glyph metamorphosis chain, all five subtable kinds (rearrangement with its sixteen verbs, contextual, ligature, noncontextual, insertion), the six lookup formats and the extended state table, default subtables only, with the work budget of the GSUB machine; a deleted glyph hands its characters to its neighbour after every subtable, so the ToUnicode entry of a ligature keeps their order
- sbix.tcl - new: strikes, glyph records, dupe and flip with a cycle guard, PNG dimensions out of the IHDR, all through sfnt slice
- colorFontBitmap.tcl - new: a Type 3 glyph that places the face's own PNG as an image XObject, the alpha channel as its soft mask, the largest strike unless -strike names one, a flip sharing the source's XObject under a mirrored matrix, a glyph without a picture drawn from its own outline and only an empty outline refused
- colorFont.tcl - -face and -strike; the COLR path and the sbix path share one head; morx shapes the sequences of a face that has no GSUB; a character list the face deletes entirely is refused rather than registered as a family of no glyphs
- font.tcl, type3.tcl - font embed -face; an sbix or CBDT face is refused at embed whether or not some of its glyphs carry outlines, since the keycap bases of Apple Color Emoji are outlines and the other 1397 glyphs are boxes; font info reports bitmaps
- examples/02.18-noto-color-emoji.tcl - one procedure builds the document for either face, and on a machine with Apple Color Emoji the example writes 02.18-apple-color-emoji.pdf beside the Noto one: the same three pages, the paint statistics turned into strike statistics; each half skips itself where its face is absent. pdfimages -list does not see a picture inside a glyph description, so the page counts them itself
- tests - morx.test builds its word list out of the face's own cmap and holds every sequence against hb-shape (3169 of 3196 equal, the 27 others lone tag characters that HarfBuzz blanks after shaping), sbix.test and colorFontBitmap.test on synthetic faces, sfnt.test on collections and the offset reader, notoemoji.test on the count the example has written since the last check-in
- Manual, skill reference, Makefile comment, pkgIndex, configure - the new options, codes and modules; the version of the four touched modules raised
- Knowingly kept: CBDT and OpenType SVG faces are named, not read; morx applies the default features only, since no caller can name an AAT feature; the strike is chosen once per face, not per glyph check-in: 6d35855666 user: alex tags: trunk

2026-08-26
20:58
One equation under each skin-tone ramp of the emoji example

- examples/02.18-noto-color-emoji.tcl - under each of the three ramps a line "base + tone = toned base" out of the face's own glyphs, the modifier on its own drawn as the swatch the face has for it, the builder and the farmer with their third part, so that what the modifier does stands on the page beside the ramp that shows it; the five parts join the colour font's character list check-in: 4edc54d76b user: alex tags: trunk

20:41
Name the origin and the terms of the Tcl 9 logo

- examples/assets/README.md - Tcl9logo.svg is the Tcl/Tk 9.0 logo by Valerie Carroll with Steve Landers, taken unchanged from the published SVG, under the Tcl licence as the wiki page that introduced it says; the placeholder asking the author to confirm the terms is gone check-in: 051f014c92 user: alex tags: trunk

20:28
Put the library names back into the skill and one measurement comment

- doc/claude/TCLPDF.md, SKILL.md - the warning against writing a tclpdf call from memory of jsPDF, ReportLab, FPDF or pdf4tcl names them again, as intended: with the names the warning is sharper than without. The previous check-in had taken them out under the naming rule, and had cut the second half of the sentence in SKILL.md with them; both are restored to their earlier wording
- importInfo.tcl - the comment names jsPDF again as the writer of the measured form check-in: a02c17a111 user: alex tags: trunk

20:21
Norm-review round seven: twenty-nine findings no validator sees, all built

- Graphics - overprint never writes /OP without a word on the fill side, a pattern belongs to the stream that carries it (canvas id), an integer beyond 2^31 is written as a real, a shading angle under -matrix is mapped once, an open arc under a fill in force is refused, save without restore and a q depth past 28 are refused at the stream end, a name past 127 bytes at the call, /Rotate is normalised, an ICC v4 profile binds PDF 1.7, Exif orientation is read into image info and not applied, and the one PDF number bound lives in pdfObj fits behind option number, option point and page distance
- Fonts - an sfnt without glyf and loca is refused at embed, a face whose glyphs are boxes without contours (sbix, CBDT, SVG without COLR) is refused as drawing nothing, shadda and the hamza marks go before the vowels as UTR 53 says, the subset tag comes from the program bytes, Ascent and CapHeight of a Type 1 or bare CFF face come from the glyphs themselves, MVAR is applied, the cmap is chosen in HarfBuzz order, fsType bit 8 turns subsetting off, a positive descender is negated, colour operators are stripped from a d1 glyph, and font info reports masks and fsType
- COLR v1 - sweep angles carry the bias of 1.0 (every sweep stood turned by 180 degrees), srcAtop and xor are built exactly where the masking side is sharp or one level and refused otherwise, a fully transparent gradient is refused as empty, a colour line with all stops on one offset paints below and above, unknown extend and clip formats are refused, and the count of bands, clips, constants and masks per face is kept
- Shaping - a cyclic lookup graph runs against a work budget and returns the run as HarfBuzz does, a nested lookup does not filter the record position by its own flag, GPOS types 1, 2, 3, 7 and 8 are read and applied in the kern path (cursive attachment, pair kerning under every script, chained context shared with GSUB), GSUB type 8 is applied backwards, calt and rclt run in the closing stage, rlig for every script in one stage under latn as measured on HarfBuzz, and kern run answers one place per glyph so a right-to-left line is as wide as textWidth says
- Layout - a standard face in a 2.0 document carries Widths and a FontDescriptor, a refusal position counts code points under 8.6, a horizontally broken table is one Table with one TR per logical row (structureResume), the justify surplus is divided by Th, the word gap is decided per segment face, a tagged right-to-left paragraph measures without the appended space, a number beyond the PDF bound is refused before q and BT, textPath closes its mark on every failure, two-level hyphenation dictionaries are read as libhyphen reads them, column widths must be positive, -fit stops at one point, a cell dictionary needs text, a unit never ends before a zero-advance character, repeated table heads and feet are pagination artifacts, and pagination is linear in the text length
- Structure - structure -ref writes /Ref and UA-2 refuses a TOCI without one, the nesting rules of ISO/TS 32005 table 5 are enforced for inline elements, Art, Document, Caption and NonStruct, structure inside a suspended stream is refused, a Lbl after content is refused, a zoom below zero is refused everywhere (option zoom), bookmark -open and the link options are checked, and structureResume reopens a closed element for children on later pages
- PDF/A and metadata - xmpSchema checks prefix and URI, a name tree uses one encoding for all its keys, sign -field refuses a name a field already holds, encrypt -metadata 0 writes the Identity crypt filter, a date is respelled for the version the file is written in, zugferd refuses a foreign encoding declaration, attach -name refuses control characters, an XMP packet handed in must carry the claimed pdfaid or pdfuaid description, sign add refuses a DocMDP P 1 document, -unit -format -orientation are checked at the call, and a missing directory is refused with its own code
- Annotations, forms, import - the import checks every object of a closure before the first reservation, a field value outside the field font is refused at the call, values round-trip with a line feed, up to 1024 bytes after EOF are tolerated, a second import of the same file shares its streams, only MCID marks are stripped from a foreign stream in a tagged document, pdf info leaves content keys empty for an encrypted file and reports a certification, and each geometry annotation takes only its own options
- Tests - one test per fix with a mutation that turns it red, a shared freshInterp helper for the three field tests that ran a script in a fresh interpreter, seven tests that held the old behaviour corrected, and a test that pinned a module version made version-free
- Manual, README, skill, examples - every sentence the review found wrong or missing, the generated error code table, the figures, new snippets for the calls that had none, twenty-three example heads and five printed page texts, and the names of other PDF libraries taken out of what ships
- Knowingly kept: the empty ToUnicode target for a glyph without a character, since both alternatives fail PDF/A-3u; /MK /R on a rotated page, since it governs a redrawn appearance only; the cluster order of a Nastaliq dot, whose ink lands where HarfBuzz puts it; and the width of hyphenate load for a two-level dictionary check-in: 87727ccab0 user: alex tags: trunk

14:01
Source In as a clip path, and the winding that decides it

- colorFontRegion.tcl - new: the arithmetic behind a clip standing in for a mask - outlines flattened, the winding number asked at the faces of an outline's own arrangement rather than at its vertices, a contour turned to run the other way, and one shape tested for lying inside a union of others
- colorFontPaint.tcl - a Source In whose backdrop is a stack of opaque outlines with a translucent shape laid over it is drawn as a clip on the opaque union where the shape lies inside that union, decided per glyph at run time and never from bounding boxes. Several outlines become one nonzero path only after every contour has been turned to the same winding, since concatenating them as they stand is not their union; an outline that winds both ways keeps the mask, as does a shape that sticks out. Two readers draw a box behind a flag when the same is done with an alpha mask inside a Type 3 glyph; a clip is read the same way by all of them
- glyfPath.tcl - the four lines that turn contours into path operators stand on their own, since a caller that reverses a contour needs them too
- tests/colorFontRegion.test - new: winding, reversal, containment and the self-crossing contour. tests/colorFontPaint.test, tests/glyfPath.test - the clip against the mask, a stack that still masks, a stack with two shapes. Every test with a mutation that turns it red
- examples/08.04-zugferd-signed.tcl - the auto_path line every other example carries, so the script runs from the source tree without TCLLIBPATH
- Knowingly kept: the masks on outlines that wind both ways and on shapes that stick out of their union, since a clip there would be wrong in every reader rather than in two check-in: a4d6335a56 user: alex tags: trunk

12:27
Soft masks out of Type 3 glyphs where a plainer construction exists

- colorFontBand.tcl - new: a gradient with varying stop alphas is drawn as nested bands of constant alpha instead of a luminosity soft mask - level sets, each painting the same shading over a narrower clip with the alpha increment as ca, so that a band edge is an increment over painted ground and not a seam. Strips across a linear axis, even-odd rings for a nested radial, the wedges of the sweep. Two readers anchor the matrix of a soft mask set inside a Type 3 glyph stream to the page rather than the glyph and lose the masked shape; a clip and a constant alpha are misplaced by no reader. The technote of 2026-08-26 records the finding
- colorFontPaint.tcl - a Source In composition takes a constant alpha where its backdrop is one solid shape and a clip path where it is a plain opaque outline; the mask remains for a translucent shape over an opaque stack, for a radial cone whose circles do not nest, and for a colour line that would need more regions than the step allows
- tests/colorFontBand.test - new: the band arithmetic, the three geometries and the step; tests/colorFontPaint.test - the wiring, the shortcuts and the fallbacks. Every test with a mutation that turns it red
- Knowingly kept: the alpha masks on the flags, and the size the bands cost, since a shading painted once per band is larger than one shading under a mask check-in: 40b9899a3b user: alex tags: trunk

10:07
Feature wiring behind the context machine, and emoji sequences

- forms.tcl - the closing stage runs in two hb-order stages, rlig first and then liga with clig under the resolved script, proved with a patched face whose lookup numbering would have caught a shared stage. The DejaVu lam-alef gap is closed; Arabic still matches hb-shape word for word, and -ligatures 0 keeps rlig, since shaping is not typography
- liga.tcl - clig is read beside liga, both on and off together, and a contextual ligature reaches its plain lookup through the chain. Every Latin face with either feature now matches hb-shape over the full word list
- type3.tcl, colorFont.tcl - a glyph may stand for several code points: colorFont applies the face's own ccmp to the text, a joined sequence becomes one glyph with one advance and its whole sequence in ToUnicode, selectors ride on their neighbour, a splitting substitution and the 256th glyph are refused by name, and the code grant survives a glyph script that defines glyphs of its own
- text.tcl, textBlock.tcl - a fallback chain offers a longer-than-one unit only to a face that registered exactly that sequence, so the chain order holds and the cost of a colour face in the chain drops fourfold; the emergency break cuts at unit boundaries, which also ends half-surrogate refusals under Tcl 8.6
- The review round on this build found and fixed eight faults: the chain order loss, the torn sequences, the byte count of a glyph name, a soft hyphen that reached a face, a stage claim the trace disproved, a stale code grant, a manual contradiction and one wrong figure
- examples/02.18-noto-color-emoji.tcl - new, three pages from the real face: the emoji row of 02.09 in colour, a double-size row, sequence units, and two skin-tone ramps of the same icon across all five modifiers. The 25 MB face lives in the tree unversioned and outside the source archive, like the Mustang jar; the example skips itself where the file is absent and says where to get it
- examples/02.09-writing-systems.tcl, examples/02.14-colour-font.tcl - the ligature switch shown on the page, a sequence block over the self-built face, and a label that stands over its own words
- tools/archive.sh - the emoji face is excluded from the source archive, with the reason beside the Mustang line
- doc/tclpdf.md - sequences under colorFont, the ligature paragraph tells the new truth, the mark-order decision carries its measurement, and the two computed INITIALVIEW classes are named under the generated table
- Knowingly kept: harakat stay in input order as decided in round five, now with the measured consequence at the code; two spellings of one emoji cost two register places, since each code carries exactly one ToUnicode entry check-in: 7b0cea6f15 user: alex tags: trunk

05:25
Contextual substitution and COLR version 1

- gsubContext.tcl - new: contextual and chaining substitution, GSUB types 5 and 6 in all six formats plus the extension wrapper, read and matched but never applied here. gsubApply.tcl rebuilt onto one application walk whose nested lookups run through the same machine, with the HarfBuzz shift arithmetic for records that grow or shrink the run. With this the last Arabic refusal falls: rlig over chaining lookups is applied, and the shaped output of every Arabic face in the tree now matches hb-shape exactly, Nastaliq included
- Types 3 and 8 stay out - no face in the tree carries them in a feature this package applies, and the module says so by name
- colrPaint.tcl, colorFontPaint.tcl - new: COLR version 1. The reader turns all thirty-two paint formats into a tree of eight node kinds; the drawer writes that tree as a Type 3 glyph stream - gradients as shadings, the sweep as a fan of Gouraud triangles with two rim vertices at every hard stop, per-stop alpha as a luminance soft mask in glyph space, compositing over blend modes in isolated groups and alpha masks built from form XObjects. colorFont takes a version 1 face, a version 0 face, and a glyph with no colour entry through its plain outline; the empty glyph is still refused
- The review round on this build found and fixed four faults no validator saw: a soft mask inside a composition erased the outer mask, extend repeat flattened to pad after its first period, a hard sweep stop blurred across one wedge, and the nesting cap claimed a HarfBuzz limit that is actually sixty-four
- colr.tcl, colorFont.tcl, type3.tcl, geometry.tcl, liga.tcl, forms.tcl - version 1 acceptance, the three-way glyph switch, resource pairs for streams that carry their own XObjects, matrix inversion, and comments that no longer give type 5 to 8 as the reason clig is off
- tests/helper.tcl - builders for whole GSUB tables, since a context lookup names other lookups by index
- examples/02.17-colour-font-v1.tcl - new: the paint kinds one badge each, drawn from a self-built face. examples/02.09-writing-systems.tcl - lam-alef and the lookups that reach it, with the one remaining difference shown on purpose
- doc/tclpdf.md - colorFont rewritten for version 1, the two deliberate departures from normative wording named, the Arabic section no longer lists a chaining rest
- Knowingly open: liga under the arab script wants a script API, clig stays off as a feature decision, composite outlines and the Plus mode and a palette sentinel inside a gradient stop are refused by name, and COLR variations draw the default instance check-in: 1ffbe3ab12 user: alex tags: trunk

2026-08-25
23:00
A public arc command, and a Tk canvas drawn as vectors

- shape.tcl, geometry.tcl, svgPath.tcl, document.tcl - doc arc with -start, -extent and the styles arc, pieslice and chord, angles counter-clockwise from three o'clock as the page shows them. The Bezier approximation moves out of svgPath into geometry as arcSegments and both callers share it; svgPath writes the same bytes as before. Modules to 1.7, 1.4, 1.2 and 1.14
- -style arc refuses -fill; chord is the word for the shape a fill would produce
- examples/11.01-tk-canvas.tcl, tests/tkcanvas.test - new: a canvas read through type, coords and itemconfigure and drawn as vectors, in a new group for binding other Tcl packages. Both skip themselves where Tk is absent. It is an example and claims no completeness - bitmap and window are passed over by name, stipples, arrows, -smooth and font mapping are not built
- tools/mkerrorcodes.tcl - new: the manual's error code table is generated by a tool in the tree instead of throwaway code, and it reads all three spellings of -errorcode. tools/check.sh step 11 fails when manual and source disagree and names the command that repairs it
- tests/errorcode.test - errorcode-1.3 reads all three spellings, errorcode-1.4 asks the same of a fixture carrying a bad code in each
- examples/01.01-shapes.tcl - a third page for arc: the three styles, the angle scale, and the refusal it can reach
- doc/tclpdf.md, doc/claude - the arc entry, the angle convention, the regenerated table, and arc in the skill reference that make check runs
- Knowingly left: INITIALVIEW builds its class from the option name, so no generated table can name it; the tool warns on every run with file and line
- Knowingly left: a computed class that is the last word of a topic without other classes stays silent, since it cannot be told from a topic that carries its facts after its name
- Knowingly left: the Tk example's test makes no claim about that run's console output check-in: cc048333e7 user: alex tags: trunk

20:24
Every refusal carries an error code, and two examples at the front

- forty-eight modules - the five hundred and eleven refusals that carried no -errorcode have one. With them, every refusal this package makes carries one. Fifty-four topics; where a topic has classes the third word is one, and it says what the caller has to do about it: ARGUMENT a wrong value, STATE a wrong order, NAME or ALIAS something that does not exist, FOREIGN a file this package did not write, DAMAGED a broken one, ROOM too little reserved space
- tests/errorcode.test - new: the property held over the whole tree rather than per module. No refusal without a code, no code whose first word is neither TCLPDF nor Tcl's own, and a counter-check that the guard finds one when there is one. These five hundred accumulated one module at a time and nothing in the suite noticed
- doc/tclpdf.md - the sentence "Only the codes documented here are contracts" was untrue after this and is replaced. The table of every topic and every class is generated from the source, not kept by hand
- tclpdf.tcl - an unknown subcommand of tclpdf itself answers with Tcl's own TCL LOOKUP COMMAND, in Tcl's own wording, so a mistyped command behaves as a mistyped command does everywhere else
- examples/00.02-hello-world-svg.tcl - new: the smallest program that draws a picture. Nine lines, no common.tcl and no footer, and one call more than 00.01. The logo reaches the file as path operators, so pdfimages lists no image at all
- examples/00.03-error-codes.tcl - new: eight refusals from eight topics with the code each one answers, and the same refusal put through three handlers of different width. The message is deliberately not on the page - it is the half that may be reworded, and reading it is the habit the codes replace
- examples/01.16-prepress-control.tcl - the control strip is laid out over two pages by what reads it: flat patches for a densitometer, then the figures a loupe needs. Ramps run across the sheet, the registration marks are four pairs of target and four-square mark, and the head carries a process mark at each end instead of a tinted bar. Yellow takes a black numeral, since white on yellow is not there on paper
- tests/helper.tcl, tests/version.test - eachModule: walking the tree and reading each module's source stood twice and was about to stand a third time check-in: 940d2568a9 user: alex tags: trunk

17:48
Overprint, and the three examples that came out of it

- graphics.tcl, shape.tcl - overprint as a graphics state: doc overprint -fill -stroke -mode writes OP, op and OPM, and every shape takes -overprint for both sides inside its own q and Q. The version asked for is the one the call writes, a call naming none of the three is refused, and the shapes keep their own bracket so an overprint cannot leak into the rest of the page. Modules to 1.6 and 1.5
- doc/tclpdf.md - the overprint entry, the error code TCLPDF GRAPHICS OVERPRINT under contract, and what the switch is worth: black text on a cyan panel keeps its plate underneath, and a light colour overprinted is the mistake a proof exists to catch
- tests/graphics.test - six tests over the three keys, the refusals, the version floor and the bracket. Five mutations tried, all caught
- examples/01.16-prepress-control.tcl - new: a press control strip on two pages. Tone scales and colour bars per plate, ramps as shadings, a slur and doubling gauge, four kinds of registration mark, screens as tiling patterns, screened ramps drawn dot by dot since a tile cannot grow its dot, rosettes, and the two overprint rows that look alike on screen. No image in the file at all
- examples/01.02-colour.tcl - a sixth page: why there are two colour models. Three circles each, Screen on black for light and Multiply on paper for ink, and the fourth plate beside the three inks that stand for it. It says outright that the two black patches look alike on screen, since a reader has no inks
- examples/01.15-text-fit.tcl - new: one line into a given box. The same string in four rectangles, what -spacing does to the arithmetic when it adds an amount that does not shrink with the size, the two refusals a standard face can reach, the one case that is fitted rather than refused, and the four anchors
- examples/01.06-stamp.tcl - the fitting page moves out and the file is a stamp again. Its footer moves to the A5 page, since the last page is an 88 by 55 label turned by Rotate 90
- tests/text.test - text-fit-31.15, the counterpart to 31.13: a box small enough to look wrong is still fitted, and the size written is not nought
- examples/03.06-image-icc.tcl, examples/08.02-signature-visible.tcl - who Erika Mustermann is, in English, in the source and on the page. Where the sources disagree over whether the photograph is of a real person, the comment says so and the page keeps to what is not in dispute. The signature block is a procedure now, called by both documents that script writes
check-in: 502fc448a3 user: alex tags: trunk

14:21
Norm review round 6, and the work it turned up

- annot.tcl, field.tcl, import.tcl, font.tcl, writer.tcl, update.tcl - refusals that stood after an object number was reserved now stand before it, and what cannot be moved gives the number back through the new writer and update release, so an error the caller catches leaves the document writable
- structure.tcl, structureWrite.tcl, structureDest.tcl, structureReport.tcl - thirty-five refusals in seven classes: what the caller has to do about it decides the class, and the third word means the same thing throughout one
- afm.tcl, colorFont.tcl, writer.tcl - six more refusals under contract, and a version floor asked of the writer itself now carries the code every other version refusal has
- option.tcl, page.tcl, graphics.tcl, image.tcl, xObject.tcl - NaN and Inf are turned away where a measurement is read rather than where the arithmetic trips over them; the tests that read the old wording keep it, since only values that are doubles at all are judged here
- xObject.tcl - align and valign anchor a form without -fit, the way they anchor a picture
- tableDraw.tcl, xObject.tcl, structure.tcl - a cell that raises while it draws closes its artifact bracket, a form inside a form puts the suspend flag back, an empty -tag is refused, and the automatic id of a Note steps past one the caller took
- link.tcl, ua.tcl - the link checks read the annotations this module recorded instead of grepping the serialised object body, and Figure and Formula are held to -alt or -actualText wherever they are opened
- text.tcl, textBlock.tcl, leader.tcl - fit measures with the options it draws with and solves for the size where spacing adds a flat amount, align measures the fitted line, ttb refuses fit, a leader whose fill does not lengthen is refused, and balance divides evenly where the line count divides
- xmp.tcl, pdfa.tcl, zugferd.tcl - a control character or an unpaired surrogate is refused, the prefix register is keyed on name and namespace together, conformance A is checked in both orders, a date without a zone keeps the day and drops the time, and a level already claimed survives zugferd
- image.tcl, shading.tcl, shape.tcl, geometry.tcl - an inline image binds the file version both ways, a pattern colour space is refused for a shading, singularity is judged over the numbers actually written, and rect and clip check -at and -size
- color.tcl - the tint transform of a DeviceN colour reached past the tints from the third contributing colourant on, and that plate painted nothing at all
- svgClip.tcl - new module: clip-path as a clipping path with the union of its shapes, mask as a luminosity soft mask; clip-rule and the style a referenced subtree starts from are inherited from its ancestors
- xml.tcl - a node answers for its parent, under both parsers
- svg.tcl, svgElement.tcl, svgPaint.tcl - preserveAspectRatio with all ten alignments, meet, slice and none, reaching the fitting before the box is measured; font-weight; a gradient on text; rect and circle follow the geometry SVG 9.2 and 9.3 lay down; a covered drawing reports the box it was cut back to, as a picture does
- sfnt.tcl, subset.tcl, type1.tcl - a table reaching past the end of the file is named instead of raising a bare Tcl error, a glyph name outside Annex D gets its Differences array, a face without vertical metrics gets no vertical resource, and VORG travels without vhea
- tests/helper.tcl - the guard that counts refusals without a code read one line at a time and called every wrapped one bare; the both-parsers helper moved here, a second file wanting it
- examples - nine defects only a rendered page shows: a footer under the anchor row, captions inside the drawing they name, a total that added a negative row, an ellipse drawn round the corner of its own box, and a paperclip that read as the box a reader draws for a missing glyph
- doc/tclpdf.md - every new refusal described, nineteen error codes into the contract, and four sentences that the work made untrue check-in: 0f9e48cd6d user: alex tags: trunk

05:16
Eight reopened gaps closed: error codes, interpolate under PDF/A, fitting, anchors, emergency hyphen, function pool, geometry annotations, vertical breaking

- sfnt.tcl, type1.tcl, subset.tcl - forty-five refusals given an error code in seven classes, so a script can tell a wrong format from a broken file, an unsupported variant from a missing table, and a face that cannot be subsetted from any of them. Two new test files with them: these were the two largest private modules and neither had one
- sfnt.tcl - a CFF2 reached the sfnt reader and came back as "not a TrueType or OpenType font", because the recogniser asked for major version 1 - so the refusal written for that format could not be reached through font embed at all
- image.tcl, pdfa.tcl - interpolate is refused under PDF/A, which ISO 19005-2 6.2.8 forbids and this package wrote under every profile. Both orders: an image XObject is built on first placement rather than at write time, so refusing only at the write let "place, then pdfa" through
- annot.tcl - a drawing script that raised left the annotation's object number reserved and never filled, and the next write of the whole document died although the caller had caught the error. The appearance is built before the number is reserved now, and named by a counter of its own; annot highlight carried the same defect since the markups were built
- annot.tcl - the appearance stream builder is one method, and was about to be copied a fourth time
- annotShape.tcl - the geometry annotations: line, square, circle, polygon, polyline, and a file attachment that points at an attach object rather than embedding a second copy. Rect grows by half the line width, since a reader may clip to it
- font.tcl, annotMark.tcl - FontDescender beside FontAscender, where annotMark's own comment said it belonged
- xObject.tcl, svg.tcl - a form and a drawing are fitted into a box with the arithmetic the picture already used, contain and cover, with the anchors. For a drawing the defaults are the even split written out, so a script from before writes the same bytes
- text.tcl - anchor middle and bottom, measured against the face's line box rather than the ink the string happens to carry; and fit puts one line into a box, squeezing the letters up to shrinkLimit before it lowers the size
- textBlock.tcl - emergencyHyphen marks the break a word gets when it is longer than the column, off by default, and the hyphen is measured with the piece rather than hung on after it. textLines takes direction ttb and breaks a vertical text into columns, width being the column height; placing them stays a loop of text calls
- tableLayout.tcl - a table refuses direction ttb in its own words instead of borrowing the message text raised, which spoke of an anchor the caller never gave; and its own ten refusals carry error codes, which nine of them did not
- pdfFunction.tcl - a function whose bytes match one already in the file is referenced again instead of repeated, the device the ICC profiles use. This is the decision pdfFunction-5.3 recorded as deferred; it is taken, and the object numbers shift with it
- svgPaint.tcl - the functional colour notation is read. SVG 1.1 lists rgb(255,255,255) first among the four spellings and a drawing using it was refused outright, six of the hundred and sixty-seven files measured; an rgba alpha goes into the opacity of the side it paints
- svgElement.tcl, svg.tcl - what stands under defs is counted, so svg info reports the filters and masks a drawing lost rather than the one skipped element that happened to stand outside; and the frequencies the covered set was chosen by are corrected, since most of the corpus they came from is icon libraries
- doc/claude/skills/tclpdf-tcl - the reference code covers all of the above, under both interpreters
check-in: bc4c7e0cc2 user: alex tags: trunk

2026-08-24
20:55
Nine gaps closed: shadings, annotations, DeviceN, CCITT, inline images, vertical writing, bare CFF

- shading.tcl, shadingMesh.tcl - shading types 1 and 4 to 7: a colour that is a calculation over an area, and the four mesh types that carry their colours in the points. Type 1 needed a PostScript calculator function, since types 2 and 3 take one input by definition; the four mesh types share one packer, and the Decode range comes from the mesh rather than from the page
- annot.tcl, annotMark.tcl - notes, stamps and the four ways of marking a passage: highlight, underline, strikeout, squiggly. A markup gets its quadrilaterals from the text call that set the words, so nobody computes them by hand, and every widget lands in the structure tree under a tagged document
- color.tcl - DeviceN as a multi-channel spot colour, with the tint transform as a type 4 function. Colorants is written because PDF/A requires it, not as a courtesy: veraPDF fails a document without it under 6.2.4.4
- pdfFunction.tcl - the PDF function types were built in two places, in colour and in shading, and are now built in one. Both wrote Domain and Range as bare Tcl values, which any number in exponential notation would have broken
- filter.tcl, filterCcitt.tcl, importRead.tcl - CCITT group 3 and group 4 are read when a foreign page is taken over. Measured against libtiff and poppler over 406 foreign streams and 570 megapixels with no differing pixel, and the fax coding moved out of filter.tcl, which was carrying two topics
- image.tcl - a picture may be written into the content stream as an inline image, asked for rather than decided by size; and -fit puts one into a box with its proportions kept, contain or cover, with -align and -valign as the plain anchor where no box is given
- font.tcl, sfnt.tcl, subset.tcl, text.tcl - vertical writing with Identity-V. The CMap is the smaller half: the metric comes from vhea, vmtx and VORG, and W2 is written only where a glyph differs from the default, which for ordinary Japanese is nowhere. The vertical glyph forms come from the GSUB feature vert
- font.tcl, sfnt.tcl - a bare CFF table with no sfnt around it is embedded, reading the PostScript name, the em, the glyph names and the widths out of the CFF itself; font embed takes -data instead of a path, and font info answers the vertical metric, the format and the axes
- viewerPreferences.tcl, page.tcl - initialView says which panel a reader opens beside the page and where it puts the reader first. An action is not offered, since ISO 19005 forbids most of them
- textBlock.tcl - textLines -hyphens says which line ends on a hyphen the breaker made, which is the other half of text -breakHyphen
- output.tcl, link.tcl, annot.tcl, field.tcl, sign.tcl - the four copies of the Annots entry became one, and it is idempotent, which two of the four were not
- leader.tcl - a vertical leader row was accepted and drawn across the page while being measured down it; refused now, naming the way out
- tests/helper.tcl - one pixel reader for the colour and the shading tests, the unit as an argument, since that was the whole difference between the two copies check-in: bfe4850488 user: alex tags: trunk

12:32
The break hyphen of a table, and two ways into a radio set

- textBlock.tcl, tableLayout.tcl, tableDraw.tcl, text.tcl - a broken table cell brackets its break hyphen the way a text -width paragraph does, so extracting a tagged table gives the word whole. The breaker already knew which line ends on an inserted hyphen and textLines threw it away; it now travels to the cell and on to text, which does the splitting inside one text object after the position of the whole line is fixed. The cell never cuts its own string
- text.tcl - -breakHyphen 0|1 for a caller who breaks his own lines and draws them one at a time, refused together with -width and on a string without a hyphen. The kerning pairs that straddle the hyphen are put back where the split loses them, which closes the same hole on the -width road, where it had been open all along
- fieldButton.tcl, field.tcl - a button of a radio set carries -page, so one field collects the answers of a question repeated on every sheet, and -label, which draws that button's own caption into its own Form element. The entry is two words, then the description, then -name value pairs; the dash is what tells an option from a description
- field.tcl - the declaration takes -labels beside -widgets, one script per widget, and pins PDF 2.0 as soon as one of them is not empty. A label on a set is still refused, now by the button module, so the message can name both roads out
- ua.tcl - the refusal for an undescribed widget names the per-button label as well as the third word
- doc/tclpdf.md, examples/04.04-table-hyphenation.tcl - both said the opposite of what the code now does
- tableDraw 1.6, text 1.17, textBlock 1.11 - the module versions of what changed
- known open: textLines still hands back bare strings, so a caller who breaks his own lines can bracket a hyphen but cannot tell which line has one check-in: 46956cec23 user: alex tags: trunk

11:07
Hyphenation in table cells

- table.tcl, tableLayout.tcl - a cell with more than one line can be hyphenated: hyphenate joins the style keys beside direction, so it reaches a table, a head or body or foot, a column and a single cell, and the more specific one wins. Measuring passes it on to textLines; the drawing side needs nothing, since it sets lines the measure already broke - the same list, so the two cannot disagree
- tableLayout.tcl - hyphenate must stay out of TableFont: two of its four callers measure with textWidth, which refuses a layout option by name
- tests/table.test - group 24, among them one test that holds the measurement against the drawing: same options into table layout and table, then the strings in the content stream against the cell's own line list, the returned y against top plus height, and the show count against the line count
- examples/04.04-table-hyphenation.tcl - the same rows twice side by side, every number on the page read back from table layout rather than written in, and the narrowest column that still fits three lines searched half a millimetre at a time
- doc/tclpdf.md - the style key, what it is worth in millimetres, and the one thing it does not do: the break hyphen of a table carries no empty ActualText, so extracting a tagged table gives the word with the hyphen in it. text -width brackets it; doing the same here needs text to take the breaker's flag
- table 1.7, tableLayout 1.4 - the module versions of what changed. tableDraw keeps 1.5, its diff being comment only check-in: 6c144546da user: alex tags: trunk

10:28
An errorcode for every option refusal, and the text field in a module of its own

- option.tcl - all seven refusals of the shared option parser threw with errorCode NONE, so a misspelt option name was the one error a trap could not catch. They now carry TCLPDF OPTION with six class words: UNKNOWN, PAIRS, DICT, KEY, REQUIRED and POINT, each followed by the facts and the calling context. No message wording changed
- fieldText.tcl - the text field moves out of field.tcl into a module of its own, so the core is the core and every field type is a module. The API, the tests and the bytes are unchanged
- field.tcl - the contract in the header gains what the split brought to light: FieldFont, FieldBorderEntries and FieldColourArray are the core's and are called by the button and choice modules, but stood nowhere - while the text field lived in the same file they looked like its own, and pulling it out would have made one field type depend on another
- importInfo.tcl - the comment naming the writing half now names fieldText.tcl with the rest
- document.tcl, importInfo.tcl, option.tcl, sign.tcl, structure.tcl, ua.tcl - the module versions of everything changed since 1.2, raised by 0.1. The four form modules stay at 1.0, being new rather than changed
- tests/option.test - one test per code, one that traps all seven through the single prefix, and six that go through real commands to show the code reaches the caller
- doc/tclpdf.md - the TCLPDF OPTION class in Error codes; the sentence under TCLPDF FIELD that admitted the gap now names the code instead check-in: 16a39d710f user: alex tags: trunk

09:29
Interactive form fields, and reading one back

- field.tcl - the AcroForm core and the text field: the field tree, /AcroForm on the event bus, the document DA and DR, an appearance stream drawn for every widget, and a declaration that takes several widgets for one field. NeedAppearances is not written at all
- fieldButton.tcl - check box, radio group and push button. A radio field is one field with Kids whose widgets carry no T, and under a PDF/A claim a reset action is refused at the write
- fieldChoice.tcl - list box and combo box; Opt keeps export value and display text apart, V holds the display text
- field.tcl, structure.tcl, ua.tcl - a tagged document encloses every widget in a Form element of its own with OBJR and StructParent. -tooltip is required under a UA claim, -contents describes one widget, -label draws the label inside the Form element and needs PDF 2.0
- importInfo.tcl - ::tclpdf::pdf fields answers the fields of a finished form with the reader this package already has: the type in the words the writing side takes, the value per the type it belongs to, the flags by name, the widgets and their pages. An encrypted file is refused rather than answered with rubbish, and an XFA one needs -xfa
- sign.tcl - /AcroForm has one writer again, and the signature rectangle goes through the shared arithmetic
- document.tcl - the version refusal carries an errorcode; every RequireVersion caller gains one
- tools/formcheck.java, tools/check.sh - a new section over every document that carries a form: qpdf for the structure, PDFBox for whether a field can be filled at all
- tests/helper.tcl - the handcrafted-file fixtures move out of tests/import.test, which is where a second test file needed them
- examples 09.01 to 09.04, the last of them the same field types in an appearance of their own, and a test file per module
- doc/tclpdf.md - the form field section, the reader entry, and the TCLPDF FIELD and TCLPDF VERSION error codes; the TCLPDF IMPORT class gains the two sub-codes it lacked
- doc/claude - a skill chapter for form fields, writing and reading
- known open: a radio set cannot span pages through -buttons, -label is refused on any field with more than one widget, and a misspelt option name is still refused without an errorcode check-in: 5a179b0939 user: alex tags: trunk

2026-08-22
22:14
Correct what openssl can and cannot do under a PAdES claim

- sign.tcl - the refusal of a signing-time attribute said openssl and BouncyCastle offer no switch to leave it out and therefore cannot produce a PAdES signature. Measured with OpenSSL 3.6.3: "cms -sign -cades -no_signing_time" makes an object this package takes under /ETSI.CAdES.detached, and the same signer without the switch is refused. The message, the module comment, the manual and the skill reference now say what the switch is and that the LibreSSL shipped as /usr/bin/openssl on macOS has none
- tests/sign.test - sign-6.8 signs with that switch and writes the document both ways in, guarded by the new constraint signingPades so a machine without such an openssl skips rather than fails; sign-6.7 keeps the other half and its comment says which of the two properties it measures check-in: 6b15011020 user: alex tags: trunk, release, version-1.2

17:14
Version 1.2

- configure.ac, tclpdf.tcl - the package version is 1.2
- pkgIndex.tcl.in - the module lines are in alphabetical order again; the ones added over the last stages had been appended rather than sorted in
- doc/tclpdf.md - a STANDARDS section: the documents the package is written against and, where a validator exists, measured against on every run - the file itself, archiving and accessibility, electronic invoices, signatures, fonts and pictures, each with what it covers here
- doc/tclpdf.md - the TCLPDF IMPORT error codes, the sixteen subclasses the reader refuses a foreign file with
- import.tcl, importInfo.tcl - a file that is not there was refused with a message and without a code, so the one refusal a caller meets first was the one a trap could not catch
- doc/claude - the skill reference gains the four features it did not have: a colour font drawn as Type 3, a TIFF and the strips it is made of, the natural size a picture states about itself, and the import error codes. assets.tcl names a TIFF now, as it already named a JPEG and a PNG check-in: 7e1f8fe1ce user: alex tags: trunk

16:21
Norm review round five: what no validator sees

- importRead.tcl - a foreign file can no longer hang the reader or die in raw Tcl: an unclosed array bracket, a self-referential /Length, an object stream that contains itself, a page tree that names itself among its kids, a subsection count larger than the file, and an /Index whose numbers are not numbers each end in a named refusal now. Every refusal in the reader carries a TCLPDF IMPORT code
- importRead.tcl - an object released by an incremental update stayed alive: a free entry was never recorded, so the older entry of the previous section showed through and withdrawn content reached the import
- import.tcl - the strings of an imported page went into an encrypted document in the clear. They travel through the document's own string seams now; without encryption the bytes are unchanged
- import.tcl, layer.tcl - an imported optional content configuration is written with a Name and an Order, and the groups keep the visibility of the file they came from instead of all being switched on. Without the Name a PDF/A document failed ISO 19005 clause 6.9, which no example had covered
- import.tcl - a refused import no longer leaves its catalog entry and its copied objects behind: the content stream is decoded before anything is written
- type3.tcl - a resource whose name needs escaping fell out of the Resources of a Type 3 font, because the names were read back from the written stream and matched against the raw keys. That cost a PDF/A document its conformance, and it let a glyph name the font it belongs to
- type3.tcl - a d1 glyph is refused an image, as Table 111 requires; font glyph without -script is refused rather than writing a glyph that is nothing but its width operator
- imageTiffStreams.tcl - sixteen-bit samples with TIFF predictor 2 were passed through in one byte order and two independent readers drew noise from them. They are unpacked, unpredicted and written afresh, in both orders
- imageTiff.tcl, image.tcl - refused by name now: a sixteen-bit palette, CCITT whose samples are not bilevel, a predictor beside a compression that has none, an orientation other than the first, and a picture that already wears a mask being asked to be one
- filter.tcl - a package require of the filter module wiped a global variable named n in the caller under Tcl 8.6
- text.tcl - a combining mark drifted one -spacing to the right for every glyph since its base, and in a right-to-left line a mark the face does not anchor threw the mark after it a letter's width off. Both are measured against hb-shape
- hyphenate.tcl - decomposed text is hyphenated like composed text: a word is canonically composed before the patterns see it, the minima count letters rather than characters, and a break never falls between a letter and its own mark. Measured over the words with umlauts of hyph_de_DE.dic, a third of them used to break differently
- hyphenate.tcl, textBlock.tcl - a pattern file that states a minimum below one is refused as it loads, and the line breaker refuses an offer whose head consumes nothing of the word - together they end a loop that never returned
- otLayout.tcl - coverage and class tables in format 2 are read with their ranges checked: an unbounded or overlapping set took fourteen seconds where it now takes ten milliseconds
- markPos.tcl - a null offset in a subtable header is what the norm says it is, absent, rather than an anchor invented from the header bytes
- encrypt.tcl - Length is out of the encryption dictionary: Table 20 allows it only for V 2 or 3 and withdraws it in 2.0, and this package writes revision 6. Two empty passwords are refused, since both empty opens the file to every reader as its owner
- sign.tcl - the signature to fill is chosen by its value rather than by its position, so a caller's own text can no longer be taken for the signature dictionary; a field name written in hexadecimal is compared like any other; a signer that fails leaves the prepared document behind, as the second stage promises
- sign.tcl - digest no longer writes its date into a file that is already signed, which destroyed the signature covering those bytes
- document.tcl - colorFont and GraphicsOpacity reach their topic through the lazy dispatcher: the one documented method that did not load, and a private one whose absence killed a translucent colour font layer inside the write
- tests/document.test - the guard that asks whether a string reaches the file unrecorded now sees the fourth door, the serialiser that writes a foreign object body. It could not see the one violation it was written for
- tools/check.sh, doc/claude - qpdf reads the withdrawn Length entry unconditionally and warns about its absence; exactly that warning is tolerated, and a truncated file still fails the run
- Examples: 08.04 printed a total the attached invoice XML does not state; 06.02 drew its first page upside down at the foot; 04.01 lost a column heading to a cell option dictionary; 01.03 had its rotated labels overlapping; 02.08 named a subset size over a file size; 02.11 claimed a colour its glyphs cannot follow; common.tcl left its footer colour and font behind on the following page
- Module versions +0.1 for the seventeen changed
- Knowingly open, each with its reason in the source: an image behind a form XObject inside a d1 glyph; the extended graphics state a refused placement leaves; the strip lengths of the three compressions that would have to be decoded to check them; the RBGroups of an imported file; and canonical mark order, since sorting would silently rewrite what the caller wrote check-in: 7c8f62ca73 user: alex tags: trunk

06:13
TIFF pictures are read, and a colour font reaches the page

- imageTiff.tcl, new - the structure of a TIFF: tag directories, the IFD chain, the strips with their offsets and lengths, the resolution, an ICC profile. What it will not read it refuses by name rather than half-reading: BigTIFF, tiled files, separate planes, an alpha channel, YCbCr outside a JPEG stream, and any depth, sample format or predictor PDF cannot be told about. The format is recognised by its magic bytes, never by the file name
- imageTiffStreams.tcl, new - the bytes and the PDF filter for each strip. Deflate, PackBits, CCITT Group 3 and 4 and Technote 2 JPEG are passed through untouched; an uncompressed file is deflated on the way out, a FillOrder 2 fax has the bits of every byte turned round, a JPEG strip is wrapped with its APP14 and JPEGTables, and 16-bit samples out of a little-endian file are swapped
- LZW is unpacked and rewritten as Flate, always - ISO 19005-3, 6.1.7.2 forbids LZWDecode, a picture's stream is built when it is first placed, and pdfa may be declared afterwards, so a passed-through LZW stream could no longer be repaired
- image.tcl - a TIFF is embedded, sized and placed like a PNG or a JPEG, and a stateful compression makes it several image XObjects, one per strip, stacked by n cm/Do pairs inside the one q ... Q of the placement. Above 256 parts it is refused, and such a stack can neither be another picture's mask nor wear one
- image.tcl - a defect found while building that: a stacked TIFF already placed on an earlier page became the SMask of its own first strip without a word, because the early return skipped the check
- image.tcl, imagePng.tcl, imageJpeg.tcl - the resolution of a picture is read, from pHYs, from a JFIF density, from Exif and from a TIFF XResolution, and a placement without width or height uses it. A file that states none still falls back on 72, and image info tells the two apart rather than reporting 72 for both
- filter.tcl - an LZW decoder and a predictor for any depth and any number of components. The predictor replaces the private copy in importRead, which read neither Colors nor BitsPerComponent and was quietly wrong for anything but one 8-bit component
- importRead.tcl - LZW content streams are read, with EarlyChange among their parameters. That closes roadmap item 7, and it closes it as a side effect: the decoder a compressed scan needs is the decoder a content stream needs
- colorFont.tcl - a defect: the ExtGState of a translucent layer went through a method the lazy dispatcher does not know, so every translucent layer died with "unknown method" - the whole alpha path had never run
- tests/helper.tcl - the TIFF fixture builders and the refusal helper live here now, since a second and a third test file wanted them
- Examples: 03.08 places a scan at the size its resolution says, next to what image info answers about it, and prints the refusal of a stacked mask on the page; 02.14 builds a two-colour font of its own and sets it in running text
- Module versions +0.1 for the six changed; imageTiff and imageTiffStreams start at 1.0
- Knowingly open: the seam between stacked strips is measured against the samples and against a single-image control, but no validator can see it - make check would pass a picture that is upside down; and the fixture builders of colr.test and colorFont.test still build the same tables twice, in two shapes that are not interchangeable check-in: 129c369a9a user: alex tags: trunk

2026-08-21
17:39
Colour font stage 1, the two readers - plus two defects in the checked-in state

- colr.tcl, new - COLR version 0 and CPAL are read: a base glyph answers its layers bottom-up, a palette entry answers colour and alpha, and the 0xFFFF sentinel answers the caller's text colour. Version 1, an empty table and a damaged one are each refused by name
- glyfPath.tcl, new - a TrueType outline becomes PDF path operators m, l, c and h: implicit on-curve points between two off-curve ones are resolved, a contour that starts off-curve is rotated onto its first on-curve point, and coordinates stay in font units so that the FontMatrix can carry the scale
- font.tcl - a defect in the checked-in state: a face whose every character has an empty outline was embedded silently and drew nothing. It is refused now, and the rule hangs on the outline rather than on the presence of a colour table, since a colour face may carry real monochrome fallbacks
- type3.tcl - a defect in the checked-in state: font glyph refused every astral character under Tcl 8.6, where the length test counted the surrogate pair as two characters. The encoding path was never affected
- doc/tclpdf.md - the refusal is documented at font embed, and TCLPDF FONT OUTLINES is listed among the error codes
- Knowingly open: glyfPath refuses composite glyphs, since resolving them belongs to glyfOutline and its point walker returns no contour ends; the degree elevation from quadratic to cubic now stands twice, here and in svgPath; and colorFont.tcl, the module that joins the two readers, is in the tree but not part of this check-in check-in: d4ba64564b user: alex tags: trunk

17:04
01.14 carries its specimens on both pages, one language each

- examples/01.14-hyphenation.tcl - the specimen block is split by language: the German words close the first page, the English ones the second, both drawn by one shared procedure instead of a single block at the end of the document check-in: c01711095a user: alex tags: trunk

15:44
Combining marks are placed, and words are hyphenated by language

- markPos.tcl, new - GPOS mark attachment, lookup types 4, 5 and 6: a combining mark is put where its anchors say instead of at the pen. otLayout gains the anchor reader, since cursive attachment reads the same table. Script resolution is two-stage - the resolved script stays authoritative and a fallback lookup is offered only where the first stage left a mark alone, because a union lets one script's coverage overrule another's
- font.tcl, text.tcl - the offsets reach the stream: Ts for the rise, a compensating pair of TJ numbers for the horizontal, rise restored to the caller's value rather than to zero. Text without marks writes the bytes it wrote before
- text.tcl - TextCluster keeps a mark with the glyph it hangs on while the pieces around it are turned. Reordering the offsets in step is right for y and wrong for x, where the placement branches on run direction
- textPath.tcl - a path now carries clusters, not characters: a base and the marks after it are one unit, turned as one, and the path advances by the width of the base. Decomposed and precomposed give the same picture
- textPath.tcl - a defect in the checked-in state: -spacing wrote its Tc into the cluster's own text object too, so the drawn line was narrower than the measured one and -align center and right drifted with it
- shaping.tcl, forms.tcl - Hebrew nikud, Arabic harakat, Thaana fili and Samaritan points are set rather than refused, and a face that dots its skeleton with separate mark glyphs is accepted. Tibetan stays refused and says why: its stacks come out of GSUB, and no anchor can produce a glyph the character map does not reach. forms marks and its filter are gone with the refusal they served
- hyphenate.tcl, new - hyphenation by Liang patterns, off by default: hyphenate load takes a libhyphen dictionary per language, hyphenate word answers the points, and -hyphenate turns it on for a block. The patterns are not shipped - every published set carries terms of its own and this package is MIT - so the caller loads what the machine has, and a language that is not loaded is refused by name
- textBlock.tcl - a defect older than the hyphenation: the breaker asked whether the remainder fits the fresh line only after accepting an offer, and split words the new line would have carried whole. textFlow-4.3 had pinned that behaviour and now pins the corrected one
- font.tcl - a defect in the checked-in state: an embedded face together with a Type 3 font could not be written at all. FontWriteOne is a dispatcher now, and a kind it does not know is refused by name instead of dying inside a dict get
- Examples: 02.12 sets a vocalised Hebrew line and an Arabic word in both kinds of face, with the Tibetan refusal on the same page; 02.13 puts the same string precomposed and decomposed on one curve; 01.14 shows hyphenated against unhyphenated columns in two languages; 01.04 and 01.14 gained a second page, 01.14 having drawn six of its rows below the lower edge of the sheet
- Module versions +0.1 for the seven changed since 68110180; markPos and hyphenate stay at 1.0
- Knowingly open: Thaana and Samaritan rest on the mechanism rather than on a rendering, no face on this machine carries either script; forms marks was public and its removal took only a minor step; TCLPDF FONT KIND is not listed among the documented error codes; text.tcl branches on font kind in seven places with a silent else; and ccmp is not applied for NotoMusic, which is why 02.09 changed check-in: e060b6ca76 user: alex tags: trunk

12:33
Layers, an inventory of a foreign file, Lab colour, drawn glyphs, and a fallback chain

- layer.tcl, new - optional content (8.11): layer create declares a group, layer draw -script puts content into it between /OC ... BDC and EMC, and state, radio and configure make the default configuration. It hangs on the bus, so the core knows one line about it. In a tagged document the layer bracket and the structure marks nest rather than cross, which the test asserts by walking the ORDER of the brackets - the right count in the wrong order passes a count
- importRead.tcl, importInfo.tcl, new; import.tcl split three ways - the reader was infrastructure carrying two topics on top of it, and update.tcl and sign.tcl had to require the page takeover to reach it, a topic requiring a topic. Now the reader stands on its own, the takeover and the inventory are its consumers, and neither requires the other
- The inventory itself: ::tclpdf::pdf info, pages, fonts and metadata answer what a finished file says about itself, out of this package's own parser. Held against pdfinfo and pdffonts over a foreign stock, which found two defects here - faces used only by an annotation appearance were passed over, and a Type 3 face without /BaseFont stayed nameless - and one in pdffonts, which does not name an unembedded ZapfDingbats that thirty-five checkbox appearances use. An encrypted file is answered rather than refused, the one place in this package where it is
- color.tcl - a Lab colour {lab L a b} with -whitePoint and -range, as a drawing colour and as a separation alternate, which is how prepress states a spot colour when no colour catalogue may be shipped. Components are clamped rather than refused, as 8.6.5.4 prescribes; clamping them to 0..1 like a device component would paint every Lab colour black and no validator would say so. The white point has no default in the standard, so D50 is a decision and the comment says why
- type3.tcl, new - a font whose glyphs are drawn: font define and font glyph -script, where a glyph is a content stream built from the same calls that draw a page. The subject of font glyph is the CHARACTER, so the code, the glyph name and the ToUnicode entry cannot disagree with one another. Pointing such a font at the shared resource dictionary puts it inside itself, and veraPDF dies of it with no report at all, so it gets its own, built from the names its glyph streams mention
- text.tcl - font -fallback {face ...} sets what the family cannot, per character and only where the family has no glyph, so text the family can set writes the bytes it wrote before. Kerning and ligatures end at a segment boundary and the measurement sees exactly that, because textWidth, the line breaker, the table columns and the drawing walk the same segments. Refused together with -direction rtl, where a face change would reorder only its own piece
- text.tcl - a defect in the checked-in state: TextTJ answered true for every embedded face, so an embedded Type 1 face took the TJ road, where the adjustments are dropped while Tw was suppressed for being "handled by TJ" - -wordSpacing moved nothing at all while textWidth counted it, and justified text fell short of the margin with nothing in the stream to explain it
- Module versions +0.1 for the fifteen changed since 0850a9af that were not raised yet; the five that first appeared since stay at 1.0
- Manual: layers, the inventory, Lab, font define and font glyph, the fallback chain, and what a Figure's BBox says under a rotation
- Knowingly open: the four -script options do not agree on whose frame they run in - structure and layer draw see a document held in a local variable, form create and pattern create do not; the inventory tests still live in tests/import.test; and CJK line breaking, which a fallback chain makes reachable but does not solve check-in: 6811018062 user: alex tags: trunk

08:08
An existing file can be continued, and signed a second time

- update.tcl, new - ::tclpdf::update open gives a session on a finished PDF and appends to it as an incremental update (7.5.6): add and addStream write objects the file does not have, replace writes a second copy of one it does, body hands back what the file currently says, and write puts the original bytes and the appendix behind each other. The original is not touched, which is checked by comparing bytes rather than by asking qpdf - a flipped byte inside a Flate stream leaves qpdf reporting success
- The session speaks the writer's vocabulary on purpose - add, reserve, put, ref, body, count mean what they mean while a document is being written, so a builder written against the writer works on an update unchanged. Without that the second signature would need a second implementation of the objects the first one already builds
- The update is always a classic cross-reference table, also onto a file whose own is a stream. The header comment says what that rests on and what it does not: the sentence in 7.5.8.1 only covers files that use streams entirely, which is circular here, so what carries the decision is that four readers take such a file and that a replaced object overrides even a compressed one inside an object stream
- Refused by name rather than written wrong: deleting an object, because nothing in an update says what still points at it; an encrypted file; an object at a generation other than 0; an object stream or a cross-reference stream as the target of replace. /Info and the metadata stream are never rewritten, so a PDF/A or ZUGFeRD claim survives
- sign.tcl - ::tclpdf::sign add puts a further signature on a finished file, appending the dictionary, the field and the widget, plus a second copy of the catalogue and of the page, because /AcroForm /Fields and /Annots have to name the new field. The older signature keeps covering its own revision; pdfsig then says "Not total document signed" about it, which is the correct answer rather than a complaint
- Infrastructure that a second writer of objects made necessary: import.tcl records the startxref its reader began at, so the update's /Prev is that very offset instead of a second search that could disagree; pdfObj gains stream and checkBytes, and writer.tcl delegates to them - a /Length computed in two places is one that will one day be computed differently
- tests/document.test - the string guard lists sign.tcl with its three direct calls and the reason: a signature dictionary is never inside an encrypted document, measured in both directions and at the reader
- examples/common.tcl makes TWO end certificates under the one test CA, because a reader takes a signer's name from the certificate and not from -name; with one certificate a countersigned document shows the same person twice. The console paragraph helper moves here as well, now that a second example needs it
- examples 06.02, 08.03 and 08.04 - a file continued twice, a document signed twice, and the two subjects that belong together in practice: a ZUGFeRD invoice that is signed, approved and countersigned, whose attached XML both scripts prove untouched
- Knowingly absent: a document timestamp, because no tool here can check one - pdfsig calls its type unknown, veraPDF has no rule for it; a visible field when signing a finished file, since what it would show is a form XObject and drawing one takes a document; and raising a file's version, which lies in the bytes that must not move check-in: e52549b828 user: alex tags: trunk

05:13
Text rendering modes, image masks, and a bounding box that encloses what it says it does

- text.tcl - -render takes a word rather than a number: fill, stroke, fillStroke, invisible, with -stroke and -strokeWidth routed through the existing colour and line paths. All three are font state, so a paragraph, textPath, leader and pageNumbers get them for free; Tr is written only where it differs from the initial mode, so existing documents keep their bytes
- The four clipping modes are refused by name, and the reason is in the message: the clip takes effect at ET and lives until the next Q, this package writes one BT/ET per line, and since the clip is the intersection with the previous one the second line of a paragraph would clip to the empty set
- image.tcl, imagePng.tcl - image embed -stencil turns a one-bit PNG into an image mask with no colour space of its own, so every placement paints the fill colour through its bits: one embedding, any number of colours. Which bit is the ink is read out of the file, and -invert reverses it. -mask names a second embedded picture, a stencil becoming the Mask and anything else the SMask; -interpolate is written only where asked for. Each condition Table 143 puts on a soft mask is a refusal here
- page.tcl, xObject.tcl, svg.tcl, image.tcl - form place and svg now give the Figure its BBox, and image place gives the right one: under -rotate it wrote the rectangle of -at and -width, which left ink outside the box and claimed page the picture never covered. The arithmetic is PlacedBox next to coords, shared by both roads - four corners through the matrix that goes into the stream, and the upright box around them. Without a rotation the four numbers are what they always were
- sfnt.tcl - a table directory that ends in the middle is named instead of crashed on: binary scan fills what it can and leaves the rest of its variables unset, and the count it reports was not read. Found by the test that was meant to prove the TrueType collection refusal hangs on the signature
- tests - text.test, image.test, structure.test and font.test grow with the four subjects, every one of them with a mutation probe; the collection is a built ttcf header rather than a checked-in file, as the CID-keyed CFF fixture already is
- examples 01.12 and 03.07, with two generated PNG assets - the four modes side by side and the refusal printed on the page, and the same one-bit file painted in three colours next to a photograph faded by a mask of its own
- Manual: -render and the refused clipping modes, the three new image options, .ttc named at font embed, and what a Figure's BBox says under a rotation
- Knowingly open: an SVG is not clipped to its viewport, so its Figure box is the viewport rather than the ink; and there is no example for svg -alt check-in: 27f626a181 user: alex tags: trunk

2026-08-20
22:10
Raise the version of every module changed since 1.1.1

- 32 modules, package provide and pkgIndex.tcl.in alike check-in: 0850a9af3b user: alex tags: trunk

20:44
A signature with a timestamp is no longer refused, and an example for the ICC path

- sign.tcl - the check that refuses a signing-time under cades read the whole DER and found the OID inside an RFC 3161 timestamp token as well, so it refused a PAdES B-T signature the standard allows; it now reads the ORDER of two OIDs, since RFC 5652 puts signedAttrs before unsignedAttrs and a time behind the token belongs to the token. What that costs is written at the code: an OID inside a certificate would still be read as the signer's own
- tests/sign.test - the four cases as synthetic byte strings rather than real CMS objects, for both the -signer way and embed: time alone, token before time, time before token, neither. They need no openssl, which is the point - the suite cannot make a timestamp, and openssl cannot either
- examples/03.06-image-icc.tcl - the ICC path for pictures had tests and no example: the same photograph with its profile kept and with -icc 0, what image info says about each, and the profile stream shared rather than copied. The second document it writes is the sharper half - a colour photograph inside a PDF/A file under a GREY output intent, conforming because the picture is bound to its own profile, and the refusal of the same page without the profile is measured at run time instead of claimed check-in: 4d6952ef35 user: alex tags: trunk

19:26
Stage 8b: digital signatures, invisible and visible, in one and in two stages

- sign.tcl - $doc sign writes the signature dictionary, the widget, the /AcroForm entry and a /Contents placeholder of a fixed size; beforeWrite builds the objects and afterWrite fills /ByteRange and the signature into the finished file, both edits length-neutral and checked to be
- The cut: tclpdf prepares, the signature comes from outside - -signer is a command prefix called with the bytes to sign and answering a CMS SignedData object in DER, and ::tclpdf::sign digest and embed do the same across two processes, for a key on a card, in an HSM or behind a signing service; digest fills the /M placeholder before it hands out the bytes, so the time is the moment of signing and is signed with them
- -subfilter chooses what the signature claims to be, pkcs7 by default and cades for a PAdES claim - and a cades document whose CMS object carries a signing-time attribute is refused: ETSI EN 319 142-1, Table 1 forbids it there, while openssl and BouncyCastle write it unasked
- -rect and -appearance make the field visible by hanging a form XObject into it as /AP; the module draws nothing itself, because an appearance is layout and layout belongs to the caller. Refused: a rectangle without an appearance, an appearance without a rectangle, a rectangle wholly off the page
- output.tcl asks state needsFile before writing to a channel, and sign.tcl is the first to set it - a signature has to read its own file back, and saying so after the bytes went down a socket is a sentence spoken too late
- encrypt refuses a document being signed and sign refuses an encrypted one, in whichever order the calls are made; a second signature, a timestamp and DocMDP are knowingly absent
- tests/sign.test adds 69 tests, examples 08.01 and 08.02 show both stages of both kinds, and check.sh gains a pdfsig section that demands "Total document signed" and proves itself on a copy with one byte changed inside the signed range
- 000-tools.test lists pdfsig and openssl, and says when the openssl it found is LibreSSL - which has no "cms -cades" and would skip the signature tests without a word about why
- examples/common.tcl takes the openssl scaffolding both signature examples need; the signature of the German identity card specimen goes in as traced paths and the specimen portrait as a JPEG carrying an ICC profile, both public domain under section 5 of the copyright act check-in: 4b3d4b84af user: alex tags: trunk

12:29
Sample data in the examples: real IBANs, and the manual no longer counts encryption as missing

- Manual: the DESCRIPTION sentence listed encryption among what the package does not do - it names reading an encrypted file instead check-in: e301f13fb4 user: alex tags: trunk

11:13
Stage 8: encryption, the standard security handler in revision 6

- crypto.tcl - AES-256 in CBC and single-block ECB, SHA-256, SHA-384, SHA-512 and random bytes from /dev/urandom, all in pure Tcl and checked against FIPS 197, NIST SP 800-38A and FIPS 180-4; the S-box is computed rather than transcribed, and a missing random source is refused by name instead of weakened
- encrypt.tcl - $doc encrypt with user and owner password, permissions by name and -metadata, writing /V 5 /R 6 with AESV3 for strings, streams and the file; revisions 2 to 4 and RC4 are not written, being deprecated in PDF 2.0
- the seams the cipher hangs in: writer gains encryptor with a metadata flag and runs it between the filters and /Length, document gains trailerEntry as the twin of catalogEntry, and twenty string sites go through Str, HexStr and BytesStr - a guard test names any new call that bypasses them, and the three exemptions carry their reason at the code
- import.tcl no longer builds copied resource streams by hand, so /Length has one producer again and copied streams meet the filter check like every other
- pdfa and zugferd refuse an encrypted document and encrypt refuses theirs, in whichever order the calls are made; encrypt also refuses a document that already carries drawing, a language or a link, whose strings are built at the call and would otherwise stay in the clear inside an encrypted file
- manual gains the Encryption section and qualifies the byte-identical promise, which encryption gives up on purpose; examples 07.01 and 07.02 show both patterns, and check.sh gives the encrypted example its password and checks that it needs one
- knowingly not there: reading someone else's encrypted file, the public-key handler, and SASLprep on the password check-in: 01724f5e6a user: alex tags: trunk

08:29
Add make publish-examples: the example PDFs go to the unversioned area

- new target after the publish pattern: builds the examples, uploads each PDF as examples/name to the unversioned area, syncs and counts - the "examples" wiki page lists and links them; unversioned content is replaced, not versioned, so re-publishing does not grow the repository check-in: 42160d2ef6 user: alex tags: trunk

07:59
The two round-4 leftovers: absolute error position, no orphan profile object

- textBlock - a refused glyph names its position in the string as handed in on every road of the paragraph breaker, found by searching that string rather than by offset arithmetic, and the -avoid road no longer reports position zero; the manual drops its exception sentence
- image and imagePng - an alpha PNG with an ICC profile decodes before the first writer object, so a failing picture leaves no orphan profile object behind; the version refusal names the threshold that actually applies check-in: 832543b270 user: alex tags: trunk

07:52
Retire the measured pdf4tcl comparison in favour of a short notice

- doc/TCLPDF-VS-PDF4TCL.md carries a friendly notice instead of the feature table: the actively maintained fork at github.com/gregnix/pdf4tcl closes publicly named gaps within a day, so keeping a measured list current would cost nearly as much as extending the software - the page now points there, notes that tclpdf is MIT and taking what is useful is welcome, and keeps the one stable statement: the two projects complement each other
- the dated snapshot measured against 0.9.4 remains in this file's earlier revisions check-in: 26d52f034c user: alex tags: trunk

06:29
Norm review round 4: twelve violations fixed, gray areas and hardenings built

- import.tcl - inherited page attributes take the nearest ancestor, CropBox intersected with the MediaBox, name #-escapes decoded and re-escaped, OCG configurations merged across imports and OCMDs resolved to their groups, stream /Length verified against endstream, imported forms carry a transparency group, whitespace reduced to the six PDF bytes, integer-valued /Rotate reals read
- text and textBlock - colour resolved before the first stream byte so a refused -color leaves no open BT or q, paginate closes its structure mark on error, the glyph errorcode position counts in the string as handed in on every paragraph road, no empty structure element when -avoid leaves no line on a page
- structure, svg, pdfa, ua - svg parses before the mark, 2.0 constructs pin the version floor against later lowering, PDF/A level A asks the ua question for undescribed graphics, Ruby and Warichu sequences and THead/TFoot cardinality enforced
- numbers and images - the PDF real range is enforced at the one number writer, version floors pinned after the value checks instead of before, damaged PNG alpha data and duplicate JPEG ICC segments named in their messages
- fonts - cmap format 12 group bounds checked so a damaged face is refused instead of hanging, surrogate code points skipped when reading a cmap
- smaller - one shared segment check for path and textPath, resource categories held to the resource dictionary, xmpSchema and xmpRaw lazy-load, attachment /F fallback one underscore per code point under both interpreters, UTF-16 invoice XML named as such, examples 01.03, 04.03 and 05.05 corrected
- manual follows the fixes with new sentences, PLUGINS names the eight resource categories
- knowingly open, in the TODO: a breaking space without a glyph still reports the chunk position inside a paragraph, and a failing alpha PNG with an ICC profile leaves an orphan profile object behind check-in: c55d5aab3f user: alex tags: trunk

2026-08-19
21:38
Machine-readable errorcode for character refusals

- every character refusal throws -errorcode {TCLPDF FONT GLYPH codepoint position fontname}, standard faces, Type 1 and embedded faces alike, trappable by prefix with try/trap - asked for on the mailing list
- new manual section Error codes: the errorcode is a contract, the message wording stays free to change
- example 02.04 traps the errorcode instead of parsing the message; tests cover each throwing site check-in: 9d2943d59f user: alex tags: trunk

21:14
Add doc/TCLPDF-VS-PDF4TCL.md - the measured tclpdf versus pdf4tcl comparison check-in: 3fedc783b1 user: alex tags: trunk
20:53
Etappe 7: pdf import - a page of an existing PDF becomes a form

- import.tcl: $doc pdf import alias path ?-page n?, placed with form place - real tokenizer, classic xref tables and cross-reference/object streams with PNG predictors and hybrid files, inherited page attributes, CropBox and /Rotate folded into the form's BBox and Matrix, resources copied byte for byte with their filters untouched, content streams decoded and merged, OCProperties written for optional content
- refused by name: encrypted files, content filters the package cannot decode, a page beyond the count - LZW content streams stay deferred until a real file shows up, now a roadmap item
- tests/import.test with handcrafted and qpdf-built fixtures, example 06.01-pdf-import writes and then imports its own letterhead, manual section on importing added, module registered in configure.ac, pkgIndex.tcl.in, tests/all.tcl and the document autoloader check-in: f1eee9a095 user: alex tags: trunk

12:30
Version 1.1.1

- configure.ac, tclpdf.tcl, manual header - the three version sites, configure regenerated check-in: 6a5c9009c1 user: alex tags: trunk, release, version-1.1.1