HotPDF Informacje o wydaniu

Version history for HotPDF user-visible features, fixes, compatibility updates, and documentation changes.

Version 2.137.6

  • Added THotPDF.DACopyFile for large-file workflows that only need a page count and an original byte-for-byte saved copy without building the full normal object graph.
  • Improved file-based encrypted PDF loading so password opens no longer cache the whole source PDF just to recover raw encryption dictionary values.
  • Reduced AES-256 stream decryption memory copying by decrypting from existing buffers where possible, including loaded stream memory for large encrypted image streams.
  • The HugeFileBenchmark demo now includes a direct-copy row while the default operation set still covers the full LoadFromFile + SaveLoadedDocument path.

Version 2.137.5

  • Added Direct File API object-source editing for large-file workflows: applications can open a PDF with DAOpenFileReadOnly, inspect object counts and source bodies, replace or append object bodies, update document information, and save a full rewritten copy.
  • Expanded Direct File API indexing to cover traditional xref tables and xref streams, including compressed object entries, so image-heavy PDFs and object-heavy PDFs can use the lightweight path.
  • Optimized Direct File API full rewrites by copying unchanged direct objects in source-offset order and merging contiguous ranges, greatly reducing random I/O on PDFs with hundreds of thousands of objects.
  • The HugeFileBenchmark demo now includes a direct-rewrite operation and documents HTML/CSV benchmark runs for image-only and mixed text/image huge PDFs.

Version 2.137.4

  • Demo Delphi PreflightReport zostało rozbudowane z console sample do interaktywnego narzędzia VCL GUI dla single-file preflight workflows.
  • GUI obsługuje teraz standardowe raporty text, JSON i HTML, opcjonalne hasła, profile INI files, wbudowane presets, single-file PDF/VT validation, embedded-report PDF output, report preview, status logging, automatic output naming oraz szybki dostęp do wygenerowanych plików.
  • Usunięto stare zachowanie startowe, które automatycznie tworzyło sample PDF i report, gdy nie podano command-line arguments.

2026-05-26 Version 2.137.3

  • PDF/VT validation now recognizes XMP properties written as RDF attributes as well as element text, covering common pdfvtid:GTS_PDFVTVersion, pdfvtid:GTS_PDFVTModDate, and xmp:ModifyDate packet styles.
  • Added Delphi regression coverage that keeps attribute-style PDF/VT metadata detection separate from the remaining structural PDF/VT checks.

2026-05-26 Version 2.137.2

  • Improved embedded preflight report validation so source PDFs that legitimately end with a final line break after %%EOF keep matching their stored fingerprint.
  • Preflight profiles saved as UTF-8 with a BOM now load correctly when the first line is a section header.
  • Hardened PDF literal string escape decoding, improving password validation and unencrypted-copy output for encrypted PDFs with escaped owner or user entries.

2026-05-26 Version 2.137.1

  • Extended CFF parser safety to direct byte-array checks, restoring the reader position after malformed input and clearing stale layout state on failed checks.
  • Hardened the C++Builder wrapper-compatible CFF stream loader so nil stream input returns False and resets the wrapper state instead of dereferencing the stream.

2026-05-26 Version 2.137.0

  • Added THotPDF.ValidatePDFVT with password-aware overloads for focused PDF/VT validation. The text report checks the XMP PDF/VT claim, metadata namespace, PDF/VT modification date matching, PDF/X base marker, output intent, catalog DPartRoot structure, loadable pages, and page-level DPart coverage.
  • The standalone HotPDFPreflight CLI now accepts --pdfvt to write .pdfvt.txt validation reports for a single PDF or recursive directory scan.

2026-05-26 Version 2.136.8

  • Improved Type 1 PFB and CFF parser failure handling so malformed stream checks preserve the input position and clear stale metadata instead of leaving a previous font name visible after a failed check.
  • Added additional Type 1/CFF smoke coverage for malformed CFF and Type 1 streams, including stream-position preservation and stale-metadata cleanup after failed parser calls.

2026-05-26 Version 2.136.7

  • Hardened the Type 1 PFB and CFF parser entry points so stream-based checks preserve the caller's stream position, reject malformed data safely, and expose basic Type 1/CFF font metadata through the Delphi and C++Builder wrapper paths.
  • Expanded the Type 1/CFF smoke test with in-memory CFF data, ASCII Type 1 font data, wrapper-level PFB file loading, and wrapper-level CFF stream loading to protect both Delphi and C++Builder integration surfaces.

2026-05-26 Version 2.136.6

  • Added Delphi feature demos for CJK Unicode text output, large multi-page document generation, PFX signing, and expanded image-compression coverage with CCITT Group 3 1D and 2D output.

2026-05-26 Version 2.136.5

  • Preflight reports now continue when a loaded page entry cannot return a MediaBox. The report marks that page as unavailable instead of aborting, so large or inconsistent page trees still produce diagnostic output.
  • Optimized several byte-level preflight scanners to avoid repeated full-tail string copies and linear duplicate-object searches. The 10,000-page implementation-limit sample now completes within the 120-second stress-test budget.
  • The preflight stress runner now defaults to a 120-second per-file timeout, and the PreflightReport demo rebuilds cleanly after the profile-aware output path.

2026-05-26 Version 2.136.4

  • Added a preflight cookbook, a generated preflight API map, and a large-directory stress-test workflow for the HotPDFPreflight command-line tool. The stress runner isolates each PDF behind a per-file timeout and records JSON, Markdown, CSV, report, and log evidence.
  • Fixed a full rebuild issue in the preflight fingerprint helper by declaring the helper before CreatePreflightReport uses it, so console tools that rebuild HPDFDoc.pas from source compile cleanly.
  • Recorded a 1000-file baseline against D:\PDFdoc\PDF-Samples: 997 passed, 1 failed, 2 timed out, 111.422 seconds elapsed, 8.975 files per second.

2026-05-25 Version 2.136.3

  • The standalone HotPDFPreflight CLI tool gains an --aggregate <file> option that writes a batch summary produced by THotPDF.AggregatePreflightReports after the per-file reports finish. The aggregate lists every processed PDF with status / size / warning counts and totals.
  • Works with single files and recursive directory walks, with or without --profile / --preset. The aggregate is computed from the text body of each report so filtering by profile is reflected in the summary.
  • Example: HotPDFPreflight C:\Archive -r --preset silent-actions -f json -o C:\Archive\reports --aggregate C:\Archive\Aggregate.txt writes JSON reports for every PDF plus a single batch summary.

2026-05-25 Version 2.136.2

  • The standalone HotPDFPreflight CLI tool gains --profile <file> and --preset <name> options to filter each report through a preflight profile before writing it. Works alongside -f text|json|html, -p <password>, and -r recursive directory walks.
  • The two new options are mutually exclusive (passing both, the later one wins). The preset values match what THotPDF.GetBuiltInPreflightProfile recognises so CI pipelines can pull a canonical configuration without staging an INI file.
  • Example: HotPDFPreflight C:\Archive -r --preset silent-actions -f json -o C:\Archive\reports writes JSON reports for every PDF in the directory tree with the silent-actions preset applied.

2026-05-25 Version 2.136.1

  • The Delphi PreflightReport demo now accepts profile=<file> or preset=<name> as an additional CLI argument to filter the report through a profile before writing it. Works alongside the existing json / html / embed output format flag.
  • Examples: PreflightReport.exe Input.pdf Report.txt "" text profile=tuned.ini or preset=compact / preset=silent-actions.
  • The preset values mirror the names recognised by THotPDF.GetBuiltInPreflightProfile so the demo doubles as a profile testbed without having to author an INI file.

2026-05-25 Version 2.136.0

  • Added THotPDF.MergePreflightProfiles for layering profiles. The result is the deduplicated union of both inputs (DisableChecks and DisableWarnings are merged, DisableHints is the logical OR), useful for combining a preset such as compact with project-specific tweaks.
  • Added THotPDF.DiffPreflightProfiles for structural comparison. Returns True when two profiles are equivalent, otherwise the OnlyInA and OnlyInB out parameters list the entries exclusive to each side as newline-separated check:<name> / warn:<name> / option:hints=false tokens.
  • The two helpers complete the profile lifecycle: Load / GetBuiltIn / Save / Apply / Validate / Merge / Diff.

2026-05-25 Version 2.135.0

  • Added THotPDF.CreatePreflightReportWithProfile, a one-stop convenience wrapper that composes CreatePreflightReport, LoadPreflightProfile, ApplyPreflightProfile, and the format converters into a single call.
  • The new overload accepts a source PDF, optional password, optional profile file, and target format (pfText, pfJSON, or pfHTML) and returns the final report body. Passing an empty ProfileFile skips the profile step entirely so callers can keep a single call site regardless of whether they have a profile configured.
  • The four underlying APIs remain available so existing call chains keep working unchanged; the new wrapper is purely additive.

2026-05-25 Version 2.134.0

  • Added THotPDF.SavePreflightProfile for writing a THPDFPreflightProfile record back to an INI file. The output format matches what LoadPreflightProfile consumes, so the two functions are exact inverses for well-formed profiles.
  • Enables a 'load preset, tweak, save' workflow: pull a built-in preset with GetBuiltInPreflightProfile (v2.133.0), modify the lists or flags, then SavePreflightProfile the result for later reuse or sharing across projects.
  • Sections with no content are omitted from the output so an empty profile produces a single comment line instead of three empty section headers.

2026-05-25 Version 2.133.0

  • Added THotPDF.GetBuiltInPreflightProfile, which returns ready-to-use THPDFPreflightProfile records for common workflows so callers do not have to maintain INI files for canonical configurations.
  • Recognised names (case-insensitive): default or empty string returns an empty profile; compact disables every Hint line for shorter reports; silent-actions disables every PDF 1.7 sec 12.6.4 action warning plus EmbeddedFile and RichMedia, intended for workflows that intentionally embed multimedia or interactive actions.
  • Unknown names also return an empty profile so the function never raises on a typo; pair with ValidatePreflightProfile (v2.132.0) when the caller wants typos surfaced as errors.

2026-05-25 Version 2.132.0

  • Added THotPDF.ValidatePreflightProfile for defensive profile checking. The function walks the loaded profile's DisableChecks and DisableWarnings lists and flags any name that is not recognised by the current preflight implementation, returning the unknown names joined by ', ' in the UnknownNames out parameter.
  • Useful to detect profile files authored against a newer or older HotPDF version. Without validation a typo (OpneAction instead of OpenAction) or a renamed check would silently disable nothing.
  • The check is purely defensive; the profile itself is not modified. Callers can decide whether to abort, log a warning, or proceed with the partially-effective profile.

2026-05-25 Version 2.131.0

  • Added THotPDF.ConvertPreflightReportToVeraPDFStyle, a converter that shapes a HotPDF preflight report into a JSON document modeled on veraPDF's validation output: top-level profile, a jobs array containing itemDetails / taskResult / validationResult, plus a ruleViolations array under validationResult.details.
  • HotPDF-styled rather than wire-compatible with veraPDF; the goal is to let downstream tooling that already consumes veraPDF JSON adapt to HotPDF output with minimal field-name remapping rather than re-learning a different data layout.
  • Rule violations carry the same specification ISO clause cross-reference that the native HotPDF JSON output emits, so callers retain spec-level traceability when consuming via the veraPDF-style path.

2026-05-25 Version 2.130.0

  • Added THotPDF.EmbedPreflightReportAsXMP with two overloads (with and without an optional Password argument). The function writes a copy of the source PDF with the preflight report appended as a PDF-style comment block whose payload is XMP / RDF with the xmlns:hotpdf namespace.
  • Each report line becomes a hotpdf:<name> element. PASS / FAIL checks carry the severity as an attribute; warnings, hints, and info lines use warn, hint, and info prefixes on the element name.
  • XMP-aware tools that scan a file for xpacket markers can surface the embedded report. PDF readers continue to ignore the appended bytes because they fall outside the object graph. This is archival-friendly XMP embedding, not a spec-compliant XMP integration; the XMP payload is not referenced from the catalog /Metadata entry.

2026-05-25 Version 2.129.0

  • Added THotPDF.LoadPreflightProfile and THotPDF.ApplyPreflightProfile with a new THPDFPreflightProfile record. Profiles let callers tailor the report output without re-running the analysis, suppressing specific check / warning names or every Hint line.
  • Profile files use a simple INI-style format with [disable-checks], [disable-warnings], and [options] sections. Comments and blank lines are ignored. Empty profiles pass the report through unchanged.
  • After filtering, the trailing Summary and Warnings lines are recomputed so the suppressed entries no longer contribute to the Failed and Warnings totals. The rest of the report passes through unchanged, so JSON / HTML converters, diff helper, validate / embed / aggregate APIs continue to work on the filtered report without code changes.

2026-05-25 Version 2.128.2

  • Added a Delphi PreflightDashboard console demo that scans a directory of PDFs and writes a self-contained static HTML dashboard: one <stem>.preflight.html per file plus an index.html summary table.
  • The dashboard shows total / passed / failed counts at the top and a table of every PDF with status, size, summary, and a link to the detailed report. Failed rows are highlighted; PASS and FAIL badges are colour-coded for quick scanning.
  • Output is a static directory of files (no HTTP server required); publish the directory to a CI artifact page, serve it from any web server, or open index.html directly in a browser.

2026-05-25 Version 2.128.1

  • Added tools/validate-preflight-json.py, a Python utility that validates HotPDF preflight JSON output against the published schema (Docs/preflight-schema.json). The script reports one PASS/FAIL line per input and exits with a non-zero status when any input fails validation, so CI pipelines can gate on schema conformance.
  • Validation is strict because the schema declares additionalProperties: false on every object, so unknown fields cause a hard failure rather than silently slipping through. Required dependency is the standard jsonschema Python package.

2026-05-25 Version 2.128.0

  • Added THotPDF.AggregatePreflightReports for batch summaries. The function accepts an array of per-file preflight reports and emits a single aggregate that lists each file's pass/fail status with byte size and warning count, plus totals for passed, failed, warnings, and bytes scanned.
  • Pairs naturally with the v2.125.1 HotPDFPreflight standalone CLI: run the CLI to produce one report per PDF, then feed the report bodies into AggregatePreflightReports for a dashboard-ready batch summary.
  • Empty input arrays yield an empty result string so callers can append the aggregate to other output unconditionally without guarding against zero-file directories.

2026-05-25 Version 2.127.0

  • Added THotPDF.RepairPDFFromPreflightReport with two overloads (with and without an optional Password argument). The function applies a conservative subset of byte-level repairs to damaged PDFs: it drops trailing bytes that follow the final %%EOF marker, and appends a missing %%EOF when one is not present anywhere.
  • Returns True when at least one repair was applied; RepairsApplied is an out parameter that lists the repairs one per line, so callers can decide whether to keep the repaired file or escalate to a heavier recovery tool.
  • Object-graph repairs (rebuilding xref tables, patching stream lengths, fixing trailers) are intentionally out of scope because such fixes can make a partially-recoverable file less recoverable. The repairs in this overload are limited to operations that are unambiguously safe on well-formed and lightly-damaged PDFs alike.

2026-05-25 Version 2.126.1

  • Published a formal JSON Schema for the preflight JSON output at Docs/preflight-schema.json, conforming to JSON Schema draft 2020-12.
  • The schema documents every top-level field (input, size, pdfVersion, xrefStyle), the checks / info / hints / warnings arrays, and the summary object so consumer pipelines can validate the JSON output before parsing.
  • The help topic now points downstream consumers at the schema file for reference.

2026-05-25 Version 2.126.0

  • Added THotPDF.ComparePreflightReports for line-by-line preflight report comparison. The function emits a unified-diff-like body where shared lines use a two-space prefix, lines unique to ReportA use , and lines unique to ReportB use .
  • Pairs naturally with LoadAndValidatePreflightReport: when a roundtrip validation fails, feed the original and current reports into ComparePreflightReports to surface exactly which lines changed.
  • The diff algorithm is a small greedy walk tuned for the deterministic key/value output of CreatePreflightReport; it produces compact diffs for typical inputs without pulling in a general-purpose LCS implementation.

2026-05-25 Version 2.125.1

  • Added a standalone Delphi console tool HotPDFPreflight for batch-running the preflight report against single files or whole directories. The tool accepts -o output directory, -f text|json|html output format, -p password, -r recursive directory walk, and -v verbose progress flags.
  • Exit codes follow standard CLI conventions: 0 success, 1 usage error, 2 input not found, 3 one or more PDFs failed preflight, so the tool can be integrated into CI pipelines and shell scripts.
  • Each report is named <basename>.preflight.<ext> with the extension picked from the requested format.

2026-05-25 Version 2.125.0

  • Added THotPDF.LoadAndValidatePreflightReport with two overloads (with and without an optional Password argument). The function extracts the report that EmbedPreflightReportInPDF previously appended, re-runs the current preflight algorithm against the source bytes, and returns True when the embedded InputFingerprint still matches the freshly-generated one.
  • Out parameters OriginalReport and CurrentReport receive both reports so callers can diff them when validation fails, identifying what changed between the time the report was embedded and the validation run.
  • Pairs naturally with EmbedPreflightReportInPDF: embed once during archival, validate any time later to detect tampering or accidental modification.

2026-05-25 Version 2.124.2

  • Added two fingerprint lines to THotPDF.CreatePreflightReport for CI stability checks. InputFingerprint emits a 64-bit FNV-1a hash of the source PDF bytes as 16 lowercase hex digits, immediately after the Size: line. ReportFingerprint emits a 64-bit FNV-1a hash of the assembled report (everything above the fingerprint line) at the end of the report.
  • CI pipelines can compare fingerprints across runs to detect unintended drift in either the input file or the report content without parsing every diagnostic line.
  • Both fingerprints are non-cryptographic; FNV-1a is deliberately chosen for low overhead and inline implementation without pulling in an external hashing module.
  • JSON output places InputFingerprint in the info array and ReportFingerprint at the tail of info; both entries carry a spec field labelled "FNV-1a 64-bit (non-cryptographic)".

2026-05-25 Version 2.124.1

  • Added two conditional incremental-update revision diagnostic lines to THotPDF.CreatePreflightReport. When IncrementalUpdates > 0, the report now includes RevisionStartXRefPositions (byte offsets of each startxref keyword in the file, ordered oldest first) and RevisionXRefTargets (the xref offsets each marker points to), so callers can trace the incremental update chain without parsing the file manually.
  • Clean and single-revision PDFs remain byte-stable: the new lines are emitted only when more than one startxref marker is found.
  • ISO 32000-1 sec 7.5.6 spec references are added in the JSON output for the new fields.

2026-05-25 Version 2.124.0

  • Added THotPDF.EmbedPreflightReportInPDF with two overloads, one with an optional Password argument for supported encrypted PDFs. The method writes a copy of the source PDF with the preflight report appended after the last %%EOF as PDF-style comment lines.
  • The report travels with the document for archive workflows: text editors, grep, and audit tools can surface it long after the file leaves the originating workstation.
  • PDF readers continue to render the document as-is because the appended bytes fall outside the object graph; the cross-reference table and trailer dictionary are untouched.
  • The Delphi PreflightReport demo gains an embed CLI argument that writes the embedded variant alongside the standalone report.

2026-05-25 Version 2.123.1

  • Added a regression test that hand-crafts a PDF with duplicate object numbers, a malformed xref row, and an unbalanced stream/endstream pair, then verifies that the three Phase F root-cause diagnostic lines (DuplicateObjectList, FirstMalformedXRefOffset, StreamEndStreamDelta) trigger as expected.
  • No library behaviour change: the test fills a gap in the regression suite that previously only covered the clean and "not a PDF" cases.

2026-05-25 Version 2.123.0

  • Added HTML output format to THotPDF.CreatePreflightReport and THotPDF.SavePreflightReport. The THPDFPreflightFormat enum gains a third value pfHTML alongside pfText and pfJSON.
  • HTML output is a self-contained dashboard-style document with inline CSS, severity-coloured rows (green for PASS, red for FAIL, yellow for WARN), per-section tables for checks, hints, info, and warnings, plus a summary card. No external CSS, JavaScript, or fonts are required.
  • Each table row includes the ISO 32000-1 / ISO 19005 / ISO 15930 / ISO 14289 spec reference when available, matching the JSON output spec field added in v2.122.1.
  • HTML output is byte-safe: ampersand, less-than, greater-than, double-quote, and single-quote are escaped; UTF-8 byte sequences pass through unchanged.
  • The Delphi PreflightReport demo accepts html as the fourth CLI argument to request HTML output.

2026-05-25 Version 2.122.4

  • Added PDF/X subset detection to preflight reports. When a PDF/X claim is detected, the new Hint PDFXVersion line names the specific subset (X-1a:2001, X-1a:2003, X-3:2002, X-3:2003, X-4, X-4p, X-5g, X-5pg, X-5n, or X-5).
  • Added PDF/UA part extraction. When a PDF/UA claim is detected, the new Hint PDFUAPart line reports the value from pdfuaid:part in the XMP metadata.
  • ISO 15930 and ISO 14289 spec cross-references are added to the JSON output for the new hint entries.
  • Clean PDFs with no PDF/X or PDF/UA claim remain byte-stable: the new hints are only emitted when the underlying claim is detected.

2026-05-25 Version 2.122.3

  • Added root-cause diagnostic lines to THotPDF.CreatePreflightReport. When a damaged PDF triggers a defect count, the report now lists actionable detail.
  • DuplicateObjectList appears whenever DuplicateObjectNumbers > 0 and lists up to five repeated object numbers, so callers can pinpoint which objects collide.
  • FirstMalformedXRefOffset appears whenever XRefMalformedEntries > 0 and gives the 1-based byte offset of the first malformed cross-reference row.
  • StreamEndStreamDelta appears whenever the stream and endstream marker counts differ and gives the signed difference, indicating how many markers are missing or duplicated.
  • Clean PDFs remain byte-stable: the new diagnostic lines are only emitted when the underlying defect counters are non-zero.

2026-05-25 Version 2.122.2

  • Extended preflight action warnings to cover the full PDF 1.7 §12.6.4 action set. Added 15 advisory warnings: GoToRAction, GoToEAction, ThreadAction, URIAction, SoundAction, MovieAction, HideAction, NamedAction, SubmitFormAction, ResetFormAction, ImportDataAction, SetOCGStateAction, RenditionAction, TransAction, and GoTo3DViewAction.
  • Warnings are emitted only when the corresponding action token is detected; clean PDFs that contain no actions stay byte-stable.
  • The ISO 32000-1 spec cross-references in the JSON output now cover the new action warnings as well.

2026-05-25 Version 2.122.1

  • Extended THotPDF.CreatePreflightReport JSON output with ISO specification cross-references. Every checks, hints, and warnings entry now carries a spec field naming the ISO 32000-1, ISO 19005, ISO 15930, or ISO 14289 clause for that diagnostic.
  • The plain-text report is unchanged and remains byte-stable; the spec field appears only in pfJSON output.
  • The mapping covers every check, hint, and warning emitted by the report through v2.122.0, so JSON consumers can route findings to the matching spec clause without an external lookup table.

2026-05-25 Version 2.122.0

  • Added machine-readable JSON output to THotPDF.CreatePreflightReport and THotPDF.SavePreflightReport. New THPDFPreflightFormat enum exposes pfText (default plain-text path) and pfJSON (CI-friendly JSON document) values.
  • New format-aware overloads accept the Format parameter while keeping the legacy text overloads byte-stable. Existing applications continue to work without code changes.
  • JSON output groups entries into top-level input, size, pdfVersion, and xrefStyle fields plus checks, info, hints, warnings arrays and a summary object with failed, warnings, and result counters.
  • Built-in JSON encoder escapes ", \, control bytes, and preserves UTF-8 byte sequences without requiring an external JSON library.
  • The Delphi PreflightReport demo accepts a fourth CLI argument json to request JSON output for an input PDF.

2026-05-25 Version 2.121.36

  • Enhanced preflight reports with compliance marker hints. Reports now expose Hint PDFAClaimed, Hint PDFAPart, Hint PDFAConformance, Hint PDFXClaimed, Hint PDFUAClaimed, Hint TaggedPDF, and Hint HasTransparency lines for downstream tooling.
  • Added advisory warnings PDFAWithEncryption, PDFAWithJavaScript, and PDFA1WithTransparency that fire when a PDF/A-claimed document also carries encryption, JavaScript, or PDF/A-1-forbidden transparency markers.
  • Hint lines never increment the Failed counter, and the helpers remain advisory; they do not replace a full PDF/A, PDF/X, PDF/UA, or ISO 32000 conformance engine.

2026-05-25 Version 2.121.35

  • Enhanced preflight reports with content resource integrity diagnostics. Reports now include resource dictionary, font, embedded font program, image XObject, form XObject, ColorSpace, annotation, widget, and link counts.
  • Reports now include filter chain usage counts for FlateDecode, DCTDecode, CCITTFaxDecode, JBIG2Decode, LZWDecode, ASCIIHexDecode, ASCII85Decode, RunLengthDecode, and JPXDecode.
  • Added pass/fail check FontsHaveEmbeddedPrograms that verifies at least one /FontFile, /FontFile2, or /FontFile3 is present whenever font resources are declared.
  • Added pass/fail check AnnotationCountConsistent that verifies the sum of widget and link annotation counts does not exceed the overall annotation count.

2026-05-25 Version 2.121.34

  • Enhanced preflight reports with trailer root type diagnostics. Reports now verify that the trailer /Root indirect reference targets an object declared as /Type /Catalog.

2026-05-25 Version 2.121.33

  • Enhanced preflight reports with trailer ID diagnostics. Reports now count hex string entries in the trailer /ID array and verify that the file identifier is present as a valid two-entry pair when an ID array is supplied.

2026-05-25 Version 2.121.32

  • Enhanced preflight reports with trailer indirect-reference diagnostics. Reports now show the trailer /Root and /Info object references and verify that the referenced objects are defined in the file.

2026-05-25 Version 2.121.31

  • Enhanced preflight reports with stream length coverage diagnostics. Reports now count stream /Length entries and verify that detected streams have length entries available.

2026-05-25 Version 2.121.30

  • Enhanced preflight reports with revision marker diagnostics. Reports now count %%EOF markers and startxref sections, verify that the counts are balanced, and check that the final startxref marker appears before the final EOF marker.

2026-05-25 Version 2.121.29

  • Enhanced preflight reports with page tree consistency diagnostics. Reports now include the declared page tree /Count and pass/fail checks that compare it, and the counted page objects, with the pages loaded by HotPDF.

2026-05-25 Version 2.121.28

  • Enhanced preflight reports with xref table row diagnostics. Reports now include xref subsection, entry, free-entry, in-use-entry, and malformed-row counts, plus pass/fail checks for xref row syntax and whether xref coverage reaches the highest object number.

2026-05-25 Version 2.121.27

  • Enhanced preflight reports with bounded PDF name-pair counting and additional consistency checks. Reports now distinguish catalog, page tree, and page objects; verify that object numbers are unique; and check whether trailer /Size covers the highest object number and whether trailer /Root is present.

2026-05-25 Version 2.121.26

  • Enhanced the preflight report object and trailer diagnostics. Reports now include indirect object definition counts, highest object number, duplicate object number count, balanced stream/endstream checks, and trailer details for /Size, /Root, /Info, /ID, and /Encrypt.

2026-05-25 Version 2.121.25

  • Enhanced THotPDF.CreatePreflightReport and THotPDF.SavePreflightReport with deeper cross-reference diagnostics. Reports now verify that the final %%EOF marker is near the end of the file, parse the last startxref offset, identify whether it targets an xref table or xref stream, and include xref table, xref stream, object stream, trailer, incremental update, and linearization counts.

2026-05-25 Version 2.121.24

  • Expanded the preflight report helpers with password-aware overloads and richer diagnostics. Reports now include the PDF header version, xref style, resolved loaded page MediaBox entries, form field count, stream count, feature presence flags, JavaScript/action/media attachment warnings, warning totals, and pass/fail summaries for damaged files. The Delphi PreflightReport demo accepts an optional password argument for supported encrypted PDFs.

2026-05-25 Version 2.121.23

  • Added library-level preflight report helpers with THotPDF.CreatePreflightReport and THotPDF.SavePreflightReport. Applications can now generate a text summary for an input PDF covering the header, EOF marker, startxref, trailer or XRef stream, loadable page count, encryption state, catalog, page tree, page object, page box, information dictionary, root reference, indirect object count, and overall pass/fail status. The Delphi PreflightReport demo now uses these APIs directly.

2026-05-25 Version 2.121.22

  • Added a Delphi PreflightReport console demo that creates a sample PDF and writes a text report with lightweight structural checks, including the PDF header, EOF marker, loadable page count, encryption state, catalog, page tree, page box, and document information dictionary. The workflow keeps AutoLaunch disabled and can also report on an existing input PDF from the command line.

2026-05-25 Version 2.121.21

  • Added a Delphi SearchAndSelect console demo that builds a report, searches controlled text rows, and marks every matching term with PDF highlight annotations and /QuadPoints. The sample keeps AutoLaunch disabled so it is suitable for command-line and automated validation.

2026-05-25 Version 2.121.20

  • Added password loading for RC4-40 and RC4-128 Standard encrypted PDFs. Applications can now call LoadFromFile or LoadFromStream with a password, or call DecryptLoadedDocument after loading, then save an unencrypted loaded-document copy with SaveLoadedDocument. The new Delphi DecryptPDF console demo creates a password-protected sample and writes a decrypted copy without launching a PDF viewer.

2026-05-25 Version 2.121.19

  • Added a Delphi Printer console demo that generates a print-ready PDF preset. The sample writes print-focused /ViewerPreferences, page BleedBox and TrimBox entries, printable annotation flags, and a print-control ExtGState without launching a PDF viewer or sending a job to a physical printer.

2026-05-25 Version 2.121.18

  • Added a Delphi ConvertTo console demo that converts supported source graphics into PDF output files. The workflow creates separate PDF files from JPEG, BMP, TIFF, and EMF inputs with AutoLaunch disabled, making the sample suitable for command-line and automated checks.

2026-05-25 Version 2.121.17

  • Added loaded page operation helpers with InsertPagesFromDocument, ExtractPagesToFile, and MovePage. Applications can now insert pages from another loaded document, extract selected pages to a new PDF, and reorder loaded pages while keeping subsequent saves and extraction calls in the updated page order. The Delphi PageOperations demo shows the insert, extract, and reorder workflow without auto-launching a PDF viewer.

2026-05-25 Version 2.121.16

  • Expanded loaded AcroForm editing with RemoveFormField and FlattenFormFields. Applications can now remove loaded fields by index or name, or flatten loaded fields into static page content while removing the interactive AcroForm tree and widget annotations. The Delphi FormFields demo now shows descriptions, removal, and flattening without auto-launching a PDF viewer.

2026-05-25 Version 2.121.15

  • Added loaded bookmark title search with THotPDF.FindLoadedBookmarkPageIndex. Applications can now load an existing PDF, search the outline tree by bookmark title, and resolve matching /Dest or local GoTo action destinations to zero-based page indexes.

2026-05-24 Version 2.121.14

  • Added THPDFPage.AddTextWatermark for drawing rotated transparent text watermarks on generated or loaded pages. The new Delphi Watermark demo creates a source PDF, loads it, applies the watermark to every page, and saves the edited document.

2026-05-24 Version 2.121.13

  • Added loaded AcroForm field description inspection with GetFormFieldDescription. Applications can now read a loaded field's /TU tooltip text by index or name alongside field values, options, read-only state, and rename operations.

2026-05-24 Version 2.121.12

  • Added loaded choice-field option inspection with GetFormFieldOptionCount and GetFormFieldOptionValue. Applications can now load an existing combo box or list box field, enumerate its allowed /Opt values, and continue using the same loaded-form update and save workflow.

2026-05-24 Version 2.121.11

  • Expanded loaded AcroForm editing with IsFormFieldReadOnly and RenameFormField. Applications can now inspect a loaded field's read-only flag, rename fields by index or name, save the loaded document, and reload it with the updated field names intact.

2026-05-24 Version 2.121.10

  • Added THotPDF.GetLoadedPageBox for inspecting page boundaries after LoadFromFile. Applications can now read a loaded page's MediaBox, CropBox, BleedBox, TrimBox, and ArtBox, including the standard fallback chain for boxes that are not declared directly.

2026-05-24 Version 2.121.9

  • Added loaded AcroForm field inspection and update APIs. Applications can now load an existing form PDF, enumerate field names and types, read and update field values, toggle a field's read-only flag, and save the loaded document with SaveLoadedDocument. The new Delphi FormFields demo shows the full load, edit, and save workflow.

2026-05-24 Version 2.121.8

  • Added THotPDF.RegisterLineGraphicsState for reusable line drawing ExtGState resources. Applications can now register line width, cap style, join style, and miter limit once, then apply the named state with THPDFPage.SetGraphicsState before stroking paths.

2026-05-24 Version 2.121.7

  • Added annotation interaction helpers for writing annotation /F flags and link /H highlight modes. Link helper methods now update LastAnnotation, so flags, highlight modes, border styles, and popup helpers can be chained consistently after link creation.

2026-05-24 Version 2.121.6

  • Added reusable Form XObject authoring APIs with page-level drawing helpers, including placement, scaling, and rotation support. Added current-transformation-matrix convenience helpers and bounded text helpers for single-line alignment, wrapped text output, and row-count calculation.

2026-05-24 Version 2.121.5

  • Added reusable Type 1 PFB and CFF parser units for HotPDF, including byte-array readers, Type 1 glyph-outline command extraction, CFF dictionary, INDEX, charset, and encoding readers, plus Type 1 PFB metadata and glyph lookup helpers. Delphi and C++Builder package projects now include the new parser units.

2026-05-24 Version 2.121.4

  • PDFACompliance='2A' and PDFACompliance='3A' now enable Tagged PDF basics without implicitly emitting a PDF/UA-1 XMP claim. PDF/A Level A files keep /MarkInfo, /StructTreeRoot, document title, language, and archival metadata, while pdfuaid metadata is emitted only when PDFUACompliance is explicitly enabled.
  • The PDF/A-2/3 smoke coverage now includes PDF/A-3A and asserts that Level A archival output does not advertise PDF/UA unless requested separately. The generated PDF/A-2A and PDF/A-3A smoke files pass veraPDF validation.

2026-05-24 Version 2.121.3

  • THPDFPage.UnicodeTextOut now reuses the RegisterUnicodeTTF Type 0 font resource for supplementary-plane Unicode output, so page text and AcroForm appearance streams share the same internal-CID encoding path.
  • The shared finalization pass now keeps page-level SMP output synchronized through FUnicodeUsedCps, /CIDToGIDMap, descendant font /W, and /ToUnicode, avoiding the older page-local SMP map.

2026-05-24 Version 2.121.2

  • THPDFPage.UnicodeTextOut now routes supplementary-plane Unicode characters through the page font's registered cmap format 12 glyph mapping when RegisterUnicodeTTF has loaded the active font. Emoji and other U+10000+ scalars are emitted as one glyph CID in the page text stream instead of two surrogate-half CIDs.
  • The page font /ToUnicode CMap records those SMP glyph CIDs back to the original UTF-16BE surrogate pair, preserving text extraction and accessibility while keeping the existing /CIDToGIDMap /Identity page-font path.

2026-05-24 Version 2.121.1

  • RegisterUnicodeTTF-backed AcroForm appearance streams now render supplementary-plane Unicode characters such as emoji through one internal CID instead of writing a UTF-16 surrogate pair as two Identity-H CIDs. The generated /CIDToGIDMap points that CID to the real glyph, and /ToUnicode maps it back to the original UTF-16BE surrogate pair for text extraction.
  • The Unicode font finalization pass refreshes /CIDToGIDMap, /W, and /ToUnicode after all appearance streams have registered their late internal-CID mappings, so TrueType subsetting and generated form appearances use the final CID usage set.

2026-05-23 Version 2.121.0

  • PDF 1.7 §12.6.4.13–15 multimedia actions (Rendition / Sound / Movie) gain three new push-button helper methods on THPDFPage: AddPushButtonWithRenditionAction for §12.6.4.13 (binds a Screen annotation to a MediaRendition + MediaClipData describing the audio/video clip and a /OP operation 0..4); AddPushButtonWithSoundAction for §12.6.4.14 with two overloads (file path or raw TBytes payload), caller-supplied THPDFSoundParams record (SampleRate / Channels / BitsPerSample / Encoding), and optional /Volume + /Repeat + /Synchronous; AddPushButtonWithMovieAction for §12.6.4.15 (references a Movie annotation via /Annotation and selects /Operation Play / Stop / Pause / Resume).
  • AddScreenAnnotation and AddMovieAnnotation now return the created annotation dictionary so callers can chain it into the corresponding action helper. The signature change from procedure to function is Delphi ABI-compatible — existing callers that invoke them and discard the result still compile and emit byte-identical PDFs.
  • Standard implementation depth: each helper emits the spec's mandatory entries plus the commonly-needed optional fields (Sound /Volume /Repeat /Synchronous; Rendition /JS). Optional MediaPlayParameters, MediaScreenParameters, Selector Rendition, and Sound /Mix are deliberately omitted — reader defaults cover the 95% case and the helpers stay minimal.
  • PDF/A (all levels) and PDF/X (all profiles) reject all three multimedia actions at the helper's entry with an ISO-referenced diagnostic (ISO 19005-1 §6.6.1 / ISO 19005-2 §6.5.1 / ISO 19005-3 §6.5.1 / ISO 15930). The pre-existing annotation gates on AddScreenAnnotation / AddSoundAnnotation / AddMovieAnnotation remain in place.
  • The Rendition action wires the MediaClipData filespec with /P /TF (TEMPACCESS) MediaPermissions per §13.2.6.1 Table 280 — the minimum permission level that lets the reader hand the payload to platform audio/video codecs.
  • Sound action streams default the optional fields to spec values (Volume=1.0, Repeat=false, Synchronous=false) and emit them only when the caller passes non-default values, keeping the action dict byte-minimal for the common case.
  • Sound stream metadata (/R sample rate / /C channels / /B bits per sample / /E encoding) is caller-supplied via the new THPDFSoundParams record. The v2.16-era AddSoundAnnotation entry point still hard-codes 22050 / 16-bit / Stereo / Signed; the new Sound action path avoids that pitfall so callers can play 44.1 kHz mp3, 8 kHz µ-law telephony audio, or any other source at the correct rate.
  • Verified with the new Win32 + Win64 smoke_multimedia_actions (positive-path coverage of all four action variants + Screen / Movie annotation chaining) and smoke_multimedia_actions_pdfa_pdfx (8 gate-raise assertions covering 3 actions × PDF/A + PDF/X); regression smoke_button_actions still passes with the AddScreenAnnotation / AddMovieAnnotation procedure→function refactor.

2026-05-23 Version 2.120.15

  • Fix: EndDoc serialized the PDF object graph before two post-build mutators (the TrueType subsetter from v2.84.0 and the PDF/UA-1 §7.18.3 /Tabs /S auto-stamper from v2.94.0) ever ran, so neither change reached the emitted file. Documents that called RegisterUnicodeTTF + AddTextField shipped the full font file (~1 MB Latin / ~14 MB CJK) instead of the subset (~150 KB / ~500 KB), /BaseFont lacked the six-letter AAAAAA+ subset prefix, the PDF/A /CIDSet stream on the FontDescriptor was absent (veraPDF flagged ISO 19005-1 §6.3.5 / -2 -3 §6.2.11 failure), and PDF/UA-1 PDFs missing the §7.18.3 /Tabs /S key on annotated pages tripped Matterhorn checker rule 21-001 and PAC 3 "Tabs" failures.
  • Both mutators now run BEFORE SaveToStream / SaveToFile walks the IndirectObjects list and writes the PDF. Output is byte-identical to v2.120.14 for documents that neither register a Unicode TTF nor enable PDFUACompliance; both mutators were already gated no-ops in those cases, so the reordering only changes the documents whose v2.120.14 output was silently broken.
  • Verified with the Win32 smoke_unicode_ttf_subset (subset bytes 33.6% of the on-disk Arial — 676 KB saved — /BaseFont prefix UYENHH+ArialMT consistent across Type 0 / CIDFontType2 / FontDescriptor.FontName) and the Win32 smoke_pdfua_annot_structparents (/Tabs /S stamped on the annotated page dict, all five §7.18.3 + annotation-structure wiring assertions green).

2026-05-23 Version 2.120.14

  • PDF/UA-1 strict structure-role gate (ISO 14289-1 §7.7 + ISO 32000-1 §14.7.3): with PDFUACompliance enabled, AddStructureElement now rejects any /S role that is neither one of the 41 PDF 1.7 §14.8.4 Table 333 standard structure types (Document / Part / Art / Sect / Div / BlockQuote / Caption / TOC / TOCI / Index / NonStruct / Private / H / H1..H6 / P / L / LI / Lbl / LBody / Table / TR / TH / TD / THead / TBody / TFoot / Span / Quote / Note / Reference / BibEntry / Code / Link / Annot / Ruby / RB / RT / RP / Warichu / WT / WP / Figure / Formula / Form) nor a custom role that the caller pre-registered via AddStructRoleMap. The diagnostic exception includes the offending role string, cites PDF/UA-1 §7.7 / Table 333, and suggests the AddStructRoleMap fix. Empty Role is also rejected with a dedicated message. Without this gate, non-conforming roles would silently land in the structure tree and surface as veraPDF / PAC tools "unknown structure type" failures downstream.
  • The new gate fires only when PDFUACompliance is True; non-UA callers retain the pre-v2.120.14 freeform-role behaviour byte for byte. All four AddStructureElement overloads (the 4-arg AnsiString base, the v2.88.0 6-arg /Alt + /ActualText overload, the v2.95.0 7-arg /ID overload, and the v2.119.41 THPDFStandardStructureType enum overload) converge on the same gated path, so a single guard suffices. The enum overload always passes a Table 333 name and therefore always passes the check — callers prefer it to eliminate the typo / case-sensitivity hazard (e.g. 'Para' vs 'P', 'Lbody' vs 'LBody') at compile time.
  • v2.119.41 already shipped the THPDFStandardStructureType enum (41 spec names grouped by 5 PDF role buckets: §14.8.4.1 Grouping / §14.8.4.2 Block-level / §14.8.4.3 Inline-level / §14.8.4.4 Ruby+Warichu / §14.8.4.5 Illustration), StandardStructureTypeToName(T) for enum-to-name lookup, and IsStandardStructureType(Name) for caller-side validation; v2.120.14 wires those capabilities into the AddStructureElement entry-point so the strict check is automatic. A new private helper _IsCustomRoleInRoleMap walks the FStructRoleMap dynamic array with byte-safe CompareMem, matching custom non-ASCII role names correctly on CP_ACP=65001 hosts.

2026-05-23 Version 2.120.13

  • Fix: HotPDF v2.120.12 source did not build on RAD Studio because the two EnableShapingFeatureForSubset overloads landed inside the THotPDF published section, which Delphi rejects with E2266 ("only one of a set of overloaded methods can be published"). v2.120.13 brackets the two overloads with a localised public-section pair so the same public API compiles cleanly on dcc32 / dcc64; the surrounding properties and helpers stay published byte-for-byte. No runtime behaviour change.

2026-05-23 Version 2.120.12

  • EnableShapingFeatureForSubset (Phase 8f completion): THotPDF.EnableShapingFeatureForSubset (FeatureTag) / the THPDFShapingFeature enum overload walk every GSUB lookup wired to the requested feature under the v2.119.49 SetGSUBScript / SetGSUBLanguage selection state, enumerate every potential substitute GID across LookupType 1 (Single — Format 1 delta over Coverage glyphs + Format 2 substituteGlyphIDs array), LookupType 2 (Multiple — Sequence.substituteGlyphIDs[]), LookupType 3 (Alternate — AlternateSet.alternateGlyphIDs[]), LookupType 4 (Ligature — Ligature.ligatureGlyph), LookupType 5 / 6 (Contextual / Chained Contextual — recurse into SequenceLookupRecord nested lookups with depth-8 limit), LookupType 7 (Extension — auto-unwrap to effective lookup type), and LookupType 8 (Reverse Chained — substituteGlyphIDs[]), and OR-merge the GIDs into FUnicodeExtraUsedGlyphs so the v2.84.0 EndDoc subsetter pulls every glyph the feature can produce into the embedded font subset. Phase 8f complements the v2.119.50 per-glyph MarkUnicodeGlyphUsed with feature-level batch opt-in for callers whose producer pipeline enables a feature universally.
  • The enum overload dispatches each THPDFShapingFeature value to its conventional 4-byte OpenType feature-tag set: sfArabicGSUB → init / medi / fina / isol / rlig; sfStandardLigatures → liga; sfContextualLigatures → clig + rlig; sfStylisticAlternates → salt; sfIndicShaping → nukt / akhn / rphf / rkrf / pref / half / vatu / cjct / pres / blws / abvs / psts / haln / pstf; sfContextualAlternates → rclt + calt. The string-tag overload accepts any 4-byte OpenType feature tag for precise targeting beyond the bundled enum cases. Subset-bloat warning: enabling broad features (e.g. aalt Access All Alternates, some fonts wire every glyph in a script) can pull a large fraction of the font's glyph set into the subset, defeating the v2.84.0 size-reduction work — callers should prefer Phase 9 MarkUnicodeGlyphUsed when they know the precise GIDs emitted. Default off / explicit opt-in. Closes the final remaining sub-Phase of the GSUB Engine Roadmap Phase 8.
  • Defensive contract mirrors v2.119.50: no font registered, no GSUB table, non-4-byte tag, feature absent from (script, language) path, empty lookup chain — every "nothing to do" condition is a silent no-op (no exception). Repeated calls are idempotent (the bitmap is OR-merged). Contextual subtable Format 1 / 2 nested-lookup recursion is deferred to a future revision; Format 3 dominates real-world fonts and is implemented. Callers needing precise Format 1 / 2 contextual coverage can fall back to per-glyph MarkUnicodeGlyphUsed.

2026-05-23 Version 2.120.11

  • ToUnicode CMap caller-side reverse mapping (Phase 8d completion): three new public methods on THotPDF let callers register additional (substitute codepoint, original Unicode source codepoint sequence) reverse mappings into the EndDoc Adobe-Identity-UCS ToUnicode CMap. The v2.119.61 / v2.119.62 / v2.119.65 release series added 35 hard-coded bfchar entries for the Arabic ligatures (LAM-ALEF / YEH-HAMZA / Allah / Bismillah) and Latin Alphabetic Presentation Forms (fi / fl / ffi / ffl / ff / ſt / st) that HotPDF's producer-side shaping pipeline emits automatically; v2.120.11 closes Phase 8d by letting callers register their own mappings for the substitute glyphs they drive via the v2.119.43-50 GSUB engine APIs (GetSingleSubstituteGlyph, GetMultipleSubstituteGlyphs, GetAlternateGlyph, ApplyLigatureSubstitution, ApplyContextualSubst, ApplyReverseChainedContextualSubst).
  • RegisterToUnicodeReverseMapping (SubstCodepoint, SourceCodepoints) records one (substitute → source sequence) entry. Typical use cases: after Multiple Substitution (1 GID → N GIDs) emits the substitute sequence, register the reverse mapping so consumer-reader copy / paste round-trips to the original codepoint; after Ligature Substitution (N GIDs → 1 ligature GID) emits the ligature, register the ligature codepoint reversing to the N-element source sequence (e.g. an arbitrary CALT contextual ligature reversing to its component codepoints). ClearToUnicodeReverseMappings drops all caller registrations; ToUnicodeReverseMappingCount reports how many are pending.
  • EndDoc emit appends caller-registered entries as additional bfchar blocks after the built-in 35-entry block, automatically split into 100-entry sub-blocks per Adobe CMap and CIDFont Files Specification (Technical Note #5014) §1.4.2 bfchar operator limit. BMP codepoints emit 4 hex digits; SMP codepoints emit a UTF-16BE surrogate pair (8 hex digits) per Adobe CMap spec. Registration order is preserved so PDF readers' bfchar last-write-wins semantics give callers deterministic override behaviour for collisions with the built-in 35 entries (a caller registering a custom mapping for <FB01> "fi" overrides the v2.119.65 default at the reader). Callers that never call RegisterToUnicodeReverseMapping keep the ToUnicode CMap byte-identical with v2.120.10 baseline. SetFormUnicodeFontDict ('', nil) reset path clears the caller registry so the next Unicode font registration starts clean.

2026-05-23 Version 2.120.10

  • Myanmar text shaper (Phase 8f.10): registers Myanmar (OpenType tag mymr) as the tenth and final Indic script in HotPDF, covering both the Myanmar core block (U+1000-U+109F: Burmese, Mon, Sgaw Karen, Western Pwo, Shan, Rumai Palaung, Pa'o) and Extended-A (U+AA60-U+AA7F). New ApplyMyanmarReorder reorder pre-pass and GetMyanmarCategory codepoint lookup, reusable through the ApplyIndicReorder dispatcher.
  • Most complex syllable structure in the batch: Kinzi 3-CP prefix (U+1004 + U+103A + U+1039) at syllable start is detected, held aside, and emitted at the very start of the output per R8; pre-base vowel E (U+1031) moves to syllable start per R10; four medial consonants Y / R / W / H (U+103B-U+103E) are collected and emitted in fixed Y → R → W → H order per R9 regardless of source order.
  • ASAT (U+103A) and VIRAMA (U+1039) both treated as virama for stacked-consonant handling. DOT BELOW (U+1037) renders below the base; other tone marks render above. ANUSVARA (Bindu) above; VISARGA post. No Repha.
  • Two IndicScripts registry entries (main block + Extended-A) share the same Myanmar reorder functions, so Extended-A consonants and Pa'o Karen tone marks shape transparently through the dispatcher.
  • Completes the 11-Phase non-Devanagari Indic shaping batch (Phases 8f.0-8f.10): infrastructure + 10 registered Indic scripts — Devanagari, Bengali, Gujarati, Tamil, Telugu, Kannada, Malayalam, Sinhala (Brahmic SIA family), Khmer, Myanmar (South-East Asian).
  • Per Unicode 16.0 §16.3 (Myanmar) and the OpenType Myanmar shaping specification.

2026-05-23 Version 2.120.9

  • Khmer text shaper (Phase 8f.9): registers Khmer (OpenType tag khmr, Unicode block U+1780-U+17FF) as the ninth Indic script in HotPDF and the first South-East Asian script. New ApplyKhmerReorder reorder pre-pass and GetKhmerCategory codepoint lookup, reusable through the ApplyIndicReorder dispatcher.
  • Independent syllable structure (not the Brahmic R1-R5 pattern used by Phases 8f.1-8f.8): pre-base vowels (E / AE / AI / OE / OO / AU) move to syllable start; register shifters (MUUSIKATOAN, TRIISAP) and other above-base signs route to the above-base buffer; Bindu (NIKAHIT) renders above; Visarga (REAHMUK, YUUKALEAPINTU) renders post.
  • COENG subscript handling: each COENG (U+17D2) + Consonant pair forms a stacked-consonant cluster that stays in the base buffer in original order, letting the font's 'pres' / 'blws' GSUB features handle subscript positioning. Nested coeng is supported.
  • No Repha: Khmer scripts do not form a Repha visual, so Ra + COENG + Consonant stays in original order rather than rotating to syllable end.
  • Per Unicode 16.0 §16.4 (Khmer) and the OpenType Khmer shaping specification.

2026-05-23 Version 2.120.8

  • PAdES adbe-revocationInfoArchival CMS signed attribute (PAdES Phase 9): emit Adobe's private OID 1.2.840.113583.1.1.8 carrying CRL and OCSP revocation values inside the CMS signature itself. Adobe Acrobat preferentially honours this attribute for signature revocation checking, ahead of DSS dictionary lookups and network OCSP/CRL fetches - critical for offline-validatable PDFs and best Adobe Acrobat interop.
  • New THPDFCMSSignOptions fields:
    • AdobeCRLsDER: array of TBytes - each entry is one X.509 CertificateList (RFC 5280) DER, the same format DSS /CRLs streams contain.
    • AdobeOCSPsDER: array of TBytes - each entry is one BasicOCSPResponse SEQUENCE (RFC 6960 §4.2.1) DER. Note: not the outer OCSPResponse wrapper - callers with full OCSPResponses must unwrap responseBytes.response OCTET STRING first to extract the BasicOCSPResponse.
  • Activation: attribute emitted when at least one of the two arrays is non-empty. Default empty (both arrays) keeps v2.120.7 byte-level stability.
  • ASN.1 DER per Adobe specification: RevocationInfoArchival ::= SEQUENCE { crl [0] EXPLICIT SEQUENCE OF CRL OPTIONAL, ocsp [1] EXPLICIT SEQUENCE OF BasicOCSPResponse OPTIONAL, otherRevInfo [2] EXPLICIT SEQUENCE OF OtherRevInfo OPTIONAL }. HotPDF wires [0] and [1]; [2] (otherRevInfo for non-X.509 revocation systems) remains out-of-scope.
  • Wire shape (both branches populated): 30 <len> A0 <crllen> 30 <...> <CRL1> <CRL2> A1 <ocsplen> 30 <...> <OCSP1> <OCSP2>
  • Coexists with DSS dictionary: this attribute is parallel to (not a replacement for) the PAdES-B-LT DSS dictionary (Catalog-level /DSS /Certs /OCSPs /CRLs /VRI introduced in v2.110.0). Strict PAdES validators (EU DSS, mTOM) tend to honour DSS; Adobe Acrobat honours adbe-revocationInfoArchival. For maximum interop, populate both layers with the same CRL / OCSP bytes via AddPAdESDSSCRL / AddPAdESDSSOCSP plus AdobeCRLsDER / AdobeOCSPsDER.
  • 4-arg HPDFCMSBuildSignedData wrapper zeroes the new fields so legacy CMS bundles stay byte-identical.
  • Per Adobe Acrobat PDF Reference + RFC 5280 (CRL) + RFC 6960 (OCSP).

2026-05-23 Version 2.120.7

  • PAdES content-time-stamp CMS signed attribute (PAdES Phase 8): emit id-aa-ets-contentTimestamp (OID 1.2.840.113549.1.9.16.2.20, ETSI EN 319 122-1 §5.2.8 + RFC 5126 §5.11.4) carrying a pre-signing RFC 3161 TimeStampToken proving the document content existed at TSA-claimed time before the signer signed it. Distinct from signature-time-stamp (Phase 4 unsigned attribute, post-signing).
  • New THPDFCMSSignOptions field GetContentTimeStamp: THPDFCMSTimestampCallback. Mirror-image design of v2.120.3's GetSignatureTimeStamp: HotPDF invokes the callback with the document SHA-256 (the same hash the messageDigest signed attribute carries), and embeds the returned TimeStampToken DER as the value of the content-time-stamp attribute inside SignedAttributes.
  • Default nil = no content-time-stamp emitted. Callers wanting it assign a closure that drives an RFC 3161 TSA over HTTP. Empty TBytes return = skip silently (graceful degradation when TSA unreachable).
  • Free TSA endpoints (no subscription required) for non-qualified use cases: http://timestamp.digicert.com, http://timestamp.sectigo.com, http://timestamp.globalsign.com/tsa/r6advanced1, https://freetsa.org/tsr, http://timestamp.apple.com/ts01. eIDAS-qualified scenarios need a commercial QTSA; non-legal-use signing works with free endpoints (rate-limited but adequate for typical workflows).
  • Callback contract: invoked once per signing operation before SignedAttributes are sealed. TST bytes become part of what's signed, so the signature itself covers the content-time-stamp - this is why it's a signed attribute (vs signature-time-stamp which is unsigned and lives in unsignedAttrs after the signature).
  • 4-arg HPDFCMSBuildSignedData wrapper sets GetContentTimeStamp:=nil so legacy CMS bundles stay byte-identical.
  • Caller responsibility: PAdES placeholders need ContentsBytes sized for the signature plus the content-time-stamp TST (~4-8 KB typical). For B-T workflows combining content-time-stamp + signature-time-stamp, factor in both TSTs - AddPAdESSignatureField(Profile='B-T') default of 16 KB already covers most pairings.
  • Per ETSI EN 319 122-1 V1.2.1 §5.2.8 + RFC 3161 §6 + RFC 5126 §5.11.4.

2026-05-23 Version 2.120.6

  • PAdES signer-attributes-v2 signedAssertions [2] branch (PAdES Phase 7): close out the third and final SignerAttributeV2 OPTIONAL field. Lets the signer attach OID-identified signed assertions (SAML 2.0 Assertions, JWT compact-form tokens, OAuth attestations, org-specific attestation tokens) inside the CMS signature.
  • New THPDFSignedAssertion record: SignedAssertionID (dotted OID identifying the assertion type) plus AssertionBody: TBytes (pre-encoded ANY bytes the OID's schema defines - typically an OCTET STRING wrapping the SAML XML or JWT compact form). Empty AssertionBody omits the ANY field, leaving signedAssertionID alone (spec-valid for boolean-style assertions registered by OID).
  • New THPDFCMSSignOptions field SignedAssertions: array of THPDFSignedAssertion. Multiple assertions are supported; HotPDF wraps the SEQUENCE OF in [2] EXPLICIT (tag 0xA2).
  • SignerAttributeV2 emit condition now triggers on any of the three OPTIONAL fields. Populating all three produces: SignerAttributeV2 SEQUENCE { [0] claimed..., [1] certified..., [2] signedAssertions... } in spec field order.
  • ASN.1 per ETSI EN 319 122-1 §5.2.6: SignedAssertion ::= SEQUENCE { signedAssertionID OBJECT IDENTIFIER, signedAssertion ANY DEFINED BY signedAssertionID OPTIONAL }. Wire shape for a single assertion with body: A2 <len> 30 <len2> 30 <len3> 06 <OIDlen> <OID> <ANYbytes>.
  • Reuses CMSEncodeOIDFromString (v2.120.1) for the assertion OID encoding - any dotted-notation OID accepted.
  • 4-arg HPDFCMSBuildSignedData wrapper zeroes the new field so legacy CMS bundles stay byte-identical.
  • signer-attributes-v2 收口: all three OPTIONAL fields ([0] claimedAttributes, [1] certifiedAttributesV2, [2] signedAssertions) now wired through Phases 5/6/7. Full ETSI EN 319 122-1 §5.2.6 attribute coverage from the PAdES producer side.
  • Per ETSI EN 319 122-1 V1.2.1 §5.2.6 + ANNEX A.1.

2026-05-23 Version 2.120.5

  • PAdES signer-attributes-v2 certifiedAttributesV2 [1] branch (PAdES Phase 6): emit X.509 attribute certificates (RFC 5755) inside the SignerAttributeV2 SEQUENCE alongside or instead of v2.120.4's claimedAttributes [0]. Lets the signer attach AA-issued (Attribute Authority) role certificates that cryptographically back up the claimed identity / capability.
  • New THPDFCMSSignOptions field CertifiedAttributeCertsDER: array of TBytes. Each entry is the complete DER-encoded AttributeCertificate (RFC 5755) the caller received from the issuing AA. HotPDF emits them verbatim into a CertifiedAttributesV2 SEQUENCE OF wrapped by [1] EXPLICIT (tag 0xA1).
  • The signer-attributes-v2 attribute is now emitted when either ClaimedRoles or CertifiedAttributeCertsDER is non-empty. Populating both produces a SignerAttributeV2 SEQUENCE carrying [0] + [1] back to back, matching spec ordering.
  • ASN.1 DER wire shape (CertifiedAttributesV2 alone): A1 <len> 30 <len2> <AC1> <AC2> ... where each ACn is the caller-supplied RFC 5755 AttributeCertificate SEQUENCE verbatim. CHOICE between AttributeCertificate and OtherAttributeCertificate is resolved by the caller's DER input (both alternatives start with a SEQUENCE 0x30 tag; HotPDF doesn't introspect).
  • 4-arg HPDFCMSBuildSignedData wrapper zeroes the new field so legacy CMS bundles stay byte-identical to v2.119.27.
  • Scope: signedAssertions [2] (SAML / JWT / arbitrary-OID tokens) still pending and arrives in the next phase.
  • Per ETSI EN 319 122-1 V1.2.1 §5.2.6 + ANNEX A.1 + RFC 5755.

2026-05-23 Version 2.120.4

  • PAdES signer-attributes-v2 CMS signed attribute (PAdES Phase 5): emit id-aa-ets-signerAttrV2 (OID 0.4.0.19122.1.1, ETSI EN 319 122-1 §5.2.6), allowing the signer to declare claimed roles (e.g. "Chief Financial Officer", "Authorised Representative") inside the CMS bundle. Supersedes the deprecated v1 signerAttr (RFC 5126 §5.10) for new signatures.
  • New THPDFClaimedRole record: RoleOID (attribute type OID in dotted notation, typically an organisational role OID or 1.3.6.1.5.5.7.20.1 id-id-aa-PERMrole) + RoleValue (UTF-8 string carrying the human-readable role label).
  • New THPDFCMSSignOptions field ClaimedRoles: array of THPDFClaimedRole. Multiple roles are supported; each becomes one Attribute inside the claimedAttributes [0] SEQUENCE OF. Empty array (default) suppresses the attribute, keeping v2.120.3 byte-level stability for callers that did not opt in.
  • ASN.1 DER per ETSI EN 319 122-1 §5.2.6 + ANNEX A.1 (module DEFINITIONS EXPLICIT TAGS): SignerAttributeV2 SEQUENCE { [0] EXPLICIT ClaimedAttributes } where ClaimedAttributes ::= SEQUENCE OF Attribute. Each role serialises as Attribute { type OID, values SET OF UTF8String }. EXPLICIT tag means 0xA0 wraps the inner SEQUENCE OF verbatim (the inner 0x30 tag is preserved).
  • OID body encoded by hand: 04 00 81 95 32 01 01 (arc 0.4.0.19122.1.1; base-128 continuation on 19122 → 0x81 0x95 0x32). Future custom values can use CMSEncodeOIDFromString from v2.120.1.
  • Scope boundary: only claimedAttributes [0] is implemented this phase. certifiedAttributesV2 [1] (X.509 attribute certificates per RFC 5755) and signedAssertions [2] (SAML / JWT-style tokens) require additional machinery and stay out-of-scope until concrete demand surfaces.
  • 4-arg HPDFCMSBuildSignedData wrapper zeroes ClaimedRoles length so legacy CMS bundles stay byte-identical to v2.119.27.
  • Per ETSI EN 319 122-1 V1.2.1 §5.2.6 + EN 319 142-1 V1.2.1 §6.3 Table 1 row signer-attributes-v2 (MAY, 0-or-1).

2026-05-23 Version 2.120.3

  • PAdES signature-time-stamp CMS unsigned attribute (PAdES Phase 4): emit id-aa-signatureTimeStampToken (OID 1.2.840.113549.1.9.16.2.14, RFC 3161 §6 + ETSI EN 319 122-1 §5.3) inside the SignerInfo's [1] IMPLICIT unsignedAttrs SET. Provides the signature-time-stamp path for PAdES-B-T trusted-time service (Table 1: B-T shall be provided via signature-time-stamp or document-time-stamp).
  • New THPDFCMSTimestampCallback anonymous-method type: reference to function(const SigValueSHA256: TBytes): TBytes. HotPDF invokes the callback with the SHA-256 of SignerInfo.signatureValue (the RFC 3161 messageImprint per §2.4.2) and embeds the returned TimeStampToken DER as the attribute value.
  • New THPDFCMSSignOptions field GetSignatureTimeStamp. Default nil means no timestamp; PAdES-B-T callers assign a closure that drives the TSA HTTP/RFC 3161 interaction and returns the TST bytes. Empty TBytes return = skip without error (e.g. TSA unreachable but signature should still ship).
  • Network / authentication / TSA-account handling stays in caller code; HotPDF only wires the result into unsignedAttrs. Pairs naturally with the existing AddPAdESSignatureField(Profile='B-T', ContentsBytes >= 16384) placeholder sizing.
  • ASN.1 wire: SignerInfo SEQUENCE now optionally carries a trailing [1] IMPLICIT SET OF Attribute (tag 0xA1) holding one signature-time-stamp Attribute whose value is the caller-supplied TimeStampToken ContentInfo verbatim.
  • 4-arg HPDFCMSBuildSignedData wrapper sets GetSignatureTimeStamp:=nil so legacy CMS bundles stay byte-identical.
  • Per RFC 3161 §6 + ETSI EN 319 122-1 §5.3 + EN 319 142-1 V1.2.1 §6.3 Table 1 note q (B-T may use either signature-time-stamp or document-time-stamp for trusted time).

2026-05-23 Version 2.120.2

  • PAdES commitment-type-indication CMS SignedAttribute support (PAdES Phase 3): emit id-aa-ets-commitmentType attribute (OID 1.2.840.113549.1.9.16.2.16, ETSI EN 319 122-1 §5.2.3 + RFC 5126 §5.11), declaring the type of commitment the signer makes through the signature (e.g. proof of origin / receipt / delivery / sender / approval / creation).
  • New THPDFCommitmentType enum: ctNone (default, suppresses the attribute), ctProofOfOrigin, ctProofOfReceipt, ctProofOfDelivery, ctProofOfSender, ctProofOfApproval, ctProofOfCreation (six standard id-cti-ets-* OIDs from RFC 5126 §5.11.1, arc 1.2.840.113549.1.9.16.6.{1..6}), and ctCustom (caller supplies an arbitrary OID via the new CommitmentTypeOID field).
  • Activation: emitted whenever THPDFCMSSignOptions.CommitmentType is not ctNone. Empty default keeps v2.120.1 byte-level stability for callers that did not opt in.
  • Caller responsibility (PAdES Part 1 §6.3 Table 1 note d): when emitting commitment-type-indication, the Signature Dictionary /Reason entry must be absent (mutual exclusion). HPDFCMS does not see Sig Dict state - the caller chooses one or the other via AddPAdESSignatureField(Reason='', ...) plus CommitmentType or by leaving CommitmentType=ctNone and supplying Reason.
  • ASN.1 DER structure: CommitmentTypeIndication SEQUENCE { commitmentTypeId OBJECT IDENTIFIER }. CommitmentTypeQualifier (RFC 5126 §5.11.2) is omitted since the six standard id-cti-ets-* OIDs do not define qualifiers. Custom commitments needing qualifiers can extend the helper.
  • 4-arg HPDFCMSBuildSignedData wrapper zeroes the new CommitmentType field so legacy CMS bundles stay byte-identical to v2.119.27.
  • Per ETSI EN 319 122-1 §5.2.3 + RFC 5126 §5.11 + ETSI EN 319 142-1 V1.2.1 §6.3 Table 1 note d.

2026-05-23 Version 2.120.1

  • PAdES signature-policy-identifier CMS SignedAttribute support (PAdES Phase 2): emit id-aa-ets-sigPolicyId attribute (OID 1.2.840.113549.1.9.16.2.15, ETSI EN 319 122-1 §5.2.9 + RFC 5126 §5.8) declaring the signature policy under which the signature was produced. Required by PAdES-E-EPES (Part 2 V1.2.1 §5.4 Table 2: shall be present); may be present in all other PAdES levels.
  • New THPDFCMSSignOptions record fields: SignaturePolicyOID (dotted-OID string of the policy document), SignaturePolicyHash (digest of the policy document), SignaturePolicyHashAlgOID (digest algorithm OID, defaults to SHA-256 when empty), and SignaturePolicyURI (optional SPuri qualifier per RFC 5126 §5.8.1, OID 1.2.840.113549.1.9.16.5.1, pointing at the policy document URL).
  • Activation: the policy attribute is emitted only when both SignaturePolicyOID and SignaturePolicyHash are populated by the caller. Empty defaults mean the attribute is suppressed - baseline signers stay byte-level stable with v2.120.0 output.
  • New helper CMSEncodeOIDFromString encodes arbitrary dotted-notation OIDs to DER per X.690 §8.19 (first two arcs combined as 40*arc1 + arc2, subsequent arcs base-128 with continuation bits). Policy OIDs are business-specific so the producer cannot hard-code them as byte literals.
  • ASN.1 DER structure: SignaturePolicyId SEQUENCE { sigPolicyId OBJECT IDENTIFIER, sigPolicyHash OtherHashAlgAndValue [, sigPolicyQualifiers SEQUENCE OF SigPolicyQualifierInfo] }. The SPuri qualifier wraps the URL as IA5String (tag 0x16) inside a SigPolicyQualifierInfo.
  • Compatibility: v2.120.0 callers that did not populate the new policy fields produce byte-identical output. The 4-arg HPDFCMSBuildSignedData wrapper zeroes the new fields so legacy CMS bundles stay unchanged.
  • Per ETSI EN 319 122-1 §5.2.9 + ETSI EN 319 142-2 V1.2.1 §5.4 Table 2 + RFC 5126 §5.8.

2026-05-23 Version 2.120.0

  • PAdES baseline CMS attribute compliance (PAdES Phase 1): SignPDFWithPFX now emits the ESS signing-certificate-v2 signed attribute (RFC 5035 / OID 1.2.840.113549.1.9.16.2.47) by default, protecting the signing certificate identity against substitution attacks. ETSI EN 319 142-1 V1.2.1 §6.3 Table 1 lists this as shall be provided for all baseline levels (B-B / B-T / B-LT / B-LTA).
  • PAdES baseline now omits the CMS signing-time attribute by default. Table 1 mandates cardinality 0 for baseline profiles (the claimed signing time is carried by the Signature Dictionary /M entry per Table 1 note g). The previous v2.119.27 path included signing-time regardless of profile, which strict PAdES verifiers (EU DSS, Adobe Acrobat PAdES baseline mode) reject.
  • New public API: THPDFPAdESLevel enum (8 values covering baseline B-B / B-T / B-LT / B-LTA, extended E-BES / E-EPES / E-LTV, and legacy adbe.pkcs7), THPDFCMSSignOptions record (level + signingTime + signing-cert-v2 toggles + UTC timestamp), HPDFCMSDefaultOptions(Level) helper that returns spec-compliant defaults per level, and HPDFCMSBuildSignedDataEx options-driven CMS builder.
  • Backward compatibility: the v2.119.27 four-argument HPDFCMSBuildSignedData signature is preserved as a thin wrapper and emits byte-identical output (signing-cert-v2 absent, signingTime as requested). Callers that hashed the v2.119.27 CMS bundle in tests stay green.
  • Behavior change in signed PDF output: any caller of THotPDF.SignPDFWithPFX against a PAdES placeholder (AddPAdESSignatureField, SubFilter ETSI.CAdES.detached) now produces signatures that pass strict PAdES baseline validation. Callers wanting the v2.119.27 legacy attribute set should drive HPDFCMSBuildSignedDataEx directly with HPDFCMSDefaultOptions(palLegacy_adbePkcs7).
  • ASN.1 DER structure per RFC 5035 §3: SigningCertificateV2 SEQUENCE { certs SEQUENCE OF ESSCertIDv2 } with the SHA-256 AlgorithmIdentifier omitted (X.690 §11.5 DEFAULT-value DER rule), SHA-256 cert hash, and IssuerSerial reusing the existing X.509 issuer + serialNumber extraction from CMSExtractIssuerAndSerial.
  • Per ETSI EN 319 142-1 V1.2.1 §6.3 Table 1 + RFC 5035 §3.

2026-05-23 Version 2.119.77

  • Sinhala shaper added (Phase 8f.8). Sinhala ('sinh', U+0D80-U+0DFF) becomes 8th registered Indic script. New ApplySinhalaReorder + GetSinhalaCategory THotPDF methods.
  • Sinhala features: has Repha (Ra=U+0DBB + Halant=U+0DCA AL-LAKUNA); three pre-base matras (E=U+0DD9, EE=U+0DDA, AI=U+0DDB) — unique in having three pre-base matras; 3 split matras (U+0DDC O, U+0DDD OO, U+0DDE AU) with canonical Unicode decomposition; U+0DDD OO is 3-part (E + AA + AL-LAKUNA).
  • ApplyIndicReorder dispatcher picks up Sinhala transparently (IndicScripts registry now array[0..7]).
  • 21 new DUnitX tests. Total 187/187 tests pass on Win32 + Win64.
  • Per Unicode 16.0 §12.11 (Sinhala) and OpenType Sinhala shaping spec.

2026-05-23 Version 2.119.76

  • Malayalam shaper added (Phase 8f.7). Malayalam ('mlym', U+0D00-U+0D7F) becomes 7th registered Indic script. New ApplyMalayalamReorder + GetMalayalamCategory THotPDF methods.
  • Malayalam features: has Repha (Ra=U+0D30 + Halant=U+0D4D CHANDRAKKALA); I-matra (U+0D3F) is POST-base like Tamil (unique vs Devanagari/Bengali/Gujarati pre-base); E/EE/AI pre-base; 3 split matras (U+0D4A O / U+0D4B OO / U+0D4C AU) with canonical pre+post decomposition; chillu letters (U+0D54-U+0D56, U+0D7A-U+0D7F) and DOT REPH (U+0D4E) classified as consonants.
  • ApplyIndicReorder dispatcher picks up Malayalam transparently (IndicScripts registry now array[0..6]).
  • 21 new DUnitX tests including chillu/DOT-REPH category coverage. Total 166/166 tests pass on Win32 + Win64.
  • Per Unicode 16.0 §12.10 (Malayalam) and OpenType Malayalam shaping spec.

2026-05-23 Version 2.119.75

  • Kannada shaper added (GSUB engine roadmap Phase 8f.6). Kannada ('knda', U+0C80-U+0CFF) becomes the sixth registered Indic script. New public methods ApplyKannadaReorder and GetKannadaCategory on THotPDF.
  • Kannada has Repha (Ra=U+0CB0 + Halant=U+0CCD) like Devanagari / Bengali / Gujarati / Telugu. Like Telugu, Kannada has no pre-base matras — every Kannada matra is above-base, below-base, post-base, or split.
  • 5 split matras with Unicode 16.0 canonical decomposition: U+0CC0 II → U+0CBF (above) + U+0CD5 (post); U+0CC7 EE → U+0CC6 (above) + U+0CD5 (post); U+0CC8 AI → U+0CC6 + U+0CD6 (both above); U+0CCA O → U+0CC6 (above) + U+0CC2 (post); U+0CCB OO is three-part: U+0CC6 (above) + U+0CC2 (post) + U+0CD5 (post) — the first three-part split routed by the Phase 8f shaper family.
  • Above-base matras (R3): I (U+0CBF), E (U+0CC6), AU (U+0CCC), AI length mark (U+0CD6). Below-base matras (R4): Vocalic R/RR (U+0CC3-U+0CC4), Vocalic L/LL matras (U+0CE2-U+0CE3). Post-base matras (R5): AA (U+0CBE), U/UU (U+0CC1-U+0CC2), post-base length mark (U+0CD5).
  • ApplyIndicReorder dispatcher transparently picks up Kannada via the registry (now array[0..5]).
  • Tests: 21 new DUnitX cases including all 5 split-matra cases plus a dedicated three-part-OO verification. Total 145/145 tests pass on Win32 + Win64.
  • Per Unicode 16.0 §12.9 (Kannada) and OpenType Kannada shaping spec.

2026-05-23 Version 2.119.74

  • Telugu shaper added (GSUB engine roadmap Phase 8f.5). Telugu ('telu', U+0C00-U+0C7F) becomes the fifth registered Indic script. New public methods ApplyTeluguReorder and GetTeluguCategory on THotPDF.
  • Telugu has Repha (Ra=U+0C30 + Halant=U+0C4D) like Devanagari / Bengali / Gujarati. Unlike all prior Indic scripts: NO pre-base matras — every Telugu matra is above-base, below-base, or split.
  • Above-base matras (R3): AA/I/II/E/EE/O/OO/AU plus length mark U+0C55. Below-base matras (R4): U/UU/Vocalic R/RR (U+0C41-U+0C44), AI-length-mark (U+0C56), Vocalic L/LL matras (U+0C62-U+0C63).
  • 1 split matra: U+0C48 AI decomposes to U+0C46 (E above-base) + U+0C56 (AI length mark below-base) during reorder.
  • ApplyIndicReorder dispatcher transparently picks up Telugu via the registry (now array[0..4]).
  • Tests: 17 new DUnitX cases. Total 124/124 tests pass on Win32 + Win64.
  • Per Unicode 16.0 §12.8 (Telugu) and OpenType Telugu shaping spec.

2026-05-23 Version 2.119.73

  • Tamil shaper added (GSUB engine roadmap Phase 8f.4). Tamil ('taml', U+0B80-U+0BFF) becomes the fourth registered Indic script. New public methods ApplyTamilReorder and GetTamilCategory on THotPDF.
  • Tamil divergences from other Brahmic scripts: NO Repha (Tamil traditionally does not form Repha visual); I-matra (U+0BBF) is POST-base, unique among Brahmic scripts; II (U+0BC0) above-base; E/EE/AI (U+0BC6-U+0BC8) pre-base; 3 split matras (U+0BCA O = U+0BC6+U+0BBE, U+0BCB OO = U+0BC7+U+0BBE, U+0BCC AU = U+0BC6+U+0BD7).
  • Halant is named PULLI in Tamil at U+0BCD. ApplyIndicReorder dispatcher transparently picks up Tamil via the registry (now array[0..3]).
  • Tests: 20 new DUnitX cases. Total 107/107 tests pass on Win32 + Win64.
  • Per Unicode 16.0 §12.7 (Tamil) and OpenType Tamil shaping spec.

2026-05-23 Version 2.119.72

  • Gujarati shaper added (GSUB engine roadmap Phase 8f.3). Gujarati ('gujr', U+0A80-U+0AFF) becomes the third registered Indic script after Devanagari and Bengali. New public methods ApplyGujaratiReorder and GetGujaratiCategory on THotPDF.
  • Reorder rules: R1 Repha (Ra=U+0AB0 + Halant=U+0ACD), R2 pre-base (U+0ABF I), R3 above-base (U+0AC5 CANDRA E, U+0AC7 E, U+0AC8 AI — note above-base in Gujarati like Devanagari, NOT pre-base like Bengali), R4 below-base (U+0AC1-U+0AC4, U+0AE2-U+0AE3), R5 post-base (U+0ABE AA, U+0AC0 II, U+0AC9 CANDRA O, U+0ACB-U+0ACC O/AU). No split matras.
  • ApplyIndicReorder dispatcher transparently picks up Gujarati via the IndicScripts registry (now array[0..2]). BuildUnicode*FieldContent integration sites with sfIndicShaping set cover Gujarati with zero integration changes.
  • Tests: 18 new DUnitX cases including R3 above-base coverage to distinguish Gujarati's E/AI handling from Bengali's. Total 87/87 tests pass on Win32 + Win64.
  • Per Unicode 16.0 §12.6 (Gujarati) and OpenType Gujarati shaping spec.

2026-05-23 Version 2.119.71

  • Bengali shaper added (GSUB engine roadmap Phase 8f.2). Bengali ('beng', U+0980-U+09FF) becomes the second registered Indic script after Devanagari. New public methods ApplyBengaliReorder and GetBengaliCategory on THotPDF, mirroring the Phase 8e Devanagari API.
  • Bengali reorder rules: R1 Repha (Ra=U+09B0 + Halant=U+09CD), R2 pre-base matras (I=U+09BF, E=U+09C7, AI=U+09C8 — note E/AI are pre-base in Bengali unlike Devanagari above-base), R4 below-base (U+09C1-U+09C4, U+09E2-U+09E3), R5 post-base (U+09BE, U+09C0, U+09D7). No above-base matras in Bengali main block.
  • Split-matra decomposition: U+09CB Oo and U+09CC AU expand to their visual components during reorder (U+09C7 + U+09BE / U+09C7 + U+09D7) so the GSUB pipeline sees them in canonical pre+post positions.
  • ApplyIndicReorder dispatcher transparently routes Bengali codepoints to the Bengali entry; BuildUnicode*FieldContent helpers with sfIndicShaping set now cover Bengali alongside Devanagari with no integration changes.
  • Tests: 18 new DUnitX cases covering Bengali R1/R2/R4/R5, split decomposition, conjunct preservation, mixed-script pass-through, idempotency, dispatcher equivalence. Total 69/69 tests pass on Win32 + Win64.
  • Per Unicode 16.0 §12.2 (Bengali) and OpenType Bengali shaping spec.

2026-05-23 Version 2.119.70

  • Devanagari complete-shaper upgrade (GSUB engine roadmap Phase 8f.1): ApplyDevanagariReorder now applies the full 5-rule reorder (R1 Repha + R2 pre-base + R3 above-base + R4 below-base + R5 post-base) instead of v2.119.55's R1 + R2-only subset. Output cluster order per syllable: [pre-matras] + [base + halant + nukta + bindu/visarga/ modifier] + [above-matras] + [below-matras] + [post-matras] + [Repha].
  • Conjunct preservation: consonant-halant-consonant clusters stay grouped in the base block; reorder only moves matras and Repha. Single-pass and idempotent.
  • Behavior change vs v2.119.69: for syllables containing above/below/ post-base matras (or multi-matra clusters), the PDF byte stream now reflects the canonical reorder layout. Visual rendering after GSUB/GPOS is unchanged. Inputs with only R1 Repha or R2 pre-base I-matra (the v2.119.55 subset) produce byte-identical output.
  • Tests: 8 new DUnitX cases covering R3 / R4 / R5 individually, multi-matra ordering, conjunct preservation under matra, Repha + above + post mixed, and multi-matra idempotency. Total 51/51 tests pass on Win32 + Win64.
  • Per Unicode 16.0 §12.1 (Devanagari) and OpenType Devanagari shaping spec.

2026-05-23 Version 2.119.69

  • Indic shaping infrastructure (GSUB engine roadmap Phase 8f.0): refactored the v2.119.55 / v2.119.67 Devanagari reorder pre-pass into a script-agnostic dispatch framework. New types TIndicScriptInfo / TIndicCategoryFunc / TIndicFindSyllableFunc / TIndicReorderFunc and a new IndicScripts registry let upcoming Indic scripts plug in via function pointers.
  • New public method ApplyIndicReorder(Wide) dispatches every codepoint of Wide to its matching registered script and applies that script's syllable + reorder callbacks. Non-Indic content passes through byte-identical.
  • The three BuildUnicode*FieldContent helpers now invoke ApplyIndicReorder (instead of the Devanagari-only wrapper) when sfIndicShaping is in FShapingFeatures. Phase 8f.0 ships Devanagari only; subsequent Phases 8f.2 through 8f.10 add Bengali, Gujarati, Tamil, Telugu, Kannada, Malayalam, Sinhala, Khmer, Myanmar without changing this integration site.
  • Existing ApplyDevanagariReorder retained as a Devanagari-only wrapper for v2.119.55 backward compatibility. Output unchanged from v2.119.67.
  • Per Unicode 16.0 §12 (South Asian scripts) and ISO 32000-1 §9.10.

2026-05-22 Version 2.119.68

  • Phase 8c.6 — Producer-side GID-level emit via Private Use Area synthetic codepoint mapping: two new public methods on THotPDF let callers emit substitute GIDs that have no natural Unicode codepoint by allocating synthetic PUA (U+E000-U+F8FF) codepoints on demand. AssignSyntheticCodepointForGID (GID; out SyntheticCP): Boolean allocates the next available PUA slot, mirrors the assignment into FUnicodeCpToGid + FAcroFormUnicodeAdvances + a new FUnicodeSyntheticCpForGID per-GID lookup table so the existing producer-side hex encoding pipeline emits the synthetic codepoint as a 4-hex-digit token that the consumer reader resolves through /CIDToGIDMap to the substitute glyph. GetSyntheticCodepointForGID (GID): Word queries existing assignments (returns 0 when no synthetic assigned). Both methods are idempotent — repeat calls with the same GID return the same synthetic codepoint.
  • Unlocks producer-side emission for font-specific GSUB substitutes that don't have Unicode Presentation Form codepoints: Devanagari cluster shapes (Indic 'akhn' / 'rphf' / 'pres' / 'blws' / 'psts' / 'haln' outputs typically land on font-internal GIDs), stylistic alternates ('salt' / 'ss01-20' substitutes without codepoints), discretionary ligatures ('dlig' / 'hlig' font-specific glyphs), and CJK ideographic variation sequence substitutes. Combined with the v2.119.43-50 GSUB engine APIs and the v2.119.50 MarkUnicodeGlyphUsed subsetter closure, callers can now build complete producer-side shaping pipelines that emit arbitrary substitute GIDs through the existing codepoint-based hex pipeline.
  • State lifetime: synthetic mappings persist until RegisterUnicodeTTF is called again or BeginDoc fires (both reset all per-font state including the synthetic table). PUA range U+E000-U+F8FF provides 6400 slots, sufficient for any practical font's GSUB substitute set. ToUnicode CMap reverse mapping is the caller's responsibility — synthetic PUA codepoints have no source codepoints to reverse-map; callers concerned about text extraction should wrap text emit in PDF marked content with /ActualText property specifying the original source codepoints. PDFs whose callers don't invoke AssignSyntheticCodepointForGID emit byte-identical to v2.119.67 output (the new methods are stateless query / explicit-allocation helpers; no automatic side effects). This commit completes the GSUB engine roadmap Phase 8 capability matrix.

2026-05-22 Version 2.119.67

  • Devanagari reorder pre-pass auto-application (GSUB engine roadmap Phase 8e): the three BuildUnicode*FieldContent helpers now automatically invoke the v2.119.55 ApplyDevanagariReorder method when sfIndicShaping is included in FShapingFeatures. The pre-pass walks the input Wide left-to-right, applying the two Devanagari-specific reorders (Repha relocation for syllable-initial Ra-Halant, and pre-base I-matra U+093F movement to the start of its syllable cluster) so the emitted codepoint stream matches the visual reading order. Consumer reader's GSUB engine then applies the Indic shaping chain ('nukt' / 'akhn' / 'rphf' / 'rkrf' / 'pref' / 'half' / 'vatu' / 'cjct' / 'pres' / 'blws' / 'abvs' / 'psts' / 'haln') against the reordered codepoints to produce the final cluster glyphs.
  • Scope: Phase 8e delivers the producer-side reorder only. Producer-side GSUB chain application (where the producer commits the cluster shaping at PDF emit time rather than relying on the reader) is deferred to Phase 8c.6 because most Devanagari GSUB substitutes land on font-specific GIDs with no Unicode Presentation Form codepoint — Unlike Arabic / Latin where Forms-A / Forms-B blocks provide codepoint-reachable substitutes, Devanagari has no equivalent Presentation Form range in Unicode. Phase 8c.6 PUA synthetic codepoint mapping will unlock producer-side GSUB emit for Devanagari and other Indic / South-East-Asian scripts where the substitute GIDs are font-specific.
  • Non-Devanagari content passes through ApplyDevanagariReorder byte-identical (the method only walks Devanagari syllables; other codepoint ranges are emitted unchanged). PDFs whose callers leave sfIndicShaping off (the default) emit byte-identical to v2.119.66 output. Combined with the v2.119.55 capability layer (GetDevanagariCategory + ApplyDevanagariReorder as public methods), this commit completes the Devanagari producer-side reorder integration: callers enabling sfIndicShaping no longer need to pre-reorder upstream or call ApplyDevanagariReorder manually before BuildUnicode*FieldContent — HotPDF does it automatically.

2026-05-22 Version 2.119.66

  • GSUB 'rclt' (Required Contextual Alternates) automatic integration: the new THotPDF.ApplyArabicGSUBContextualRefinement (Wide) method parallels v2.119.63 ApplyArabicGSUBRefinement but applies contextual substitution (GSUB Type 5 / 6) via the v2.119.47 ApplyContextualSubst API instead of ligature substitution (Type 4). 'rclt' encodes required context-dependent substitutions — typically used by Arabic fonts for additional positional variants beyond the v2.85.0 static walker's Forms-B mapping, and by South-East-Asian / Indic fonts for cluster-level reordered shapes that depend on neighbor context.
  • Variable-length output handling: unlike ApplyLigatureSubstitution (N input GIDs -> 1 substitute GID), ApplyContextualSubst can produce M substitute GIDs from N input GIDs. When a contextual rule fires, ALL substitute GIDs must be reachable through Unicode Presentation Form codepoints via reverse cmap (FB00-FDFF + FE70-FEFF, ~770 codepoints scanned) for the substitution to commit; if any substitute lacks a reverse mapping, the input window passes through unchanged (partial emission would corrupt the sequence). Reverse cmap range expanded to cover Latin / Armenian / Hebrew Presentation Forms (FB00-FB4F) plus the full Arabic ranges so 'rclt' substitutes from any script can be emitted.
  • New THPDFShapingFeature enum member sfContextualAlternates (added at end for backward compatibility — existing callers using FShapingFeatures set arithmetic with the v2.119.59 5-member enum compile unchanged). Wired into the three BuildUnicode*FieldContent helpers gated on sfContextualAlternates in FShapingFeatures (opt-in, default off). Independent of FAutoShapeArabic — 'rclt' can apply to non-Arabic scripts (Latin / Hebrew / Indic). Substitute GIDs flow through MarkUnicodeGlyphUsed for v2.84.0 subsetter closure. Defensive no-op when font has no 'rclt' rules or substitute GIDs lack Presentation Form codepoints. PDFs whose callers leave sfContextualAlternates off emit byte-identical to v2.119.65 output.

2026-05-22 Version 2.119.65

  • Latin 'liga' Standard Ligatures emit (GSUB engine roadmap Phase 8b): the new THotPDF.ApplyLatinLigatureRefinement (Wide) method parallels v2.119.63 ApplyArabicGSUBRefinement but targets the Latin / Armenian / Hebrew Alphabetic Presentation Forms in U+FB00-U+FB4F. Walks the input Wide string, builds a parallel GID array via FUnicodeCpToGid, and queries the font's GSUB 'liga' (Standard Ligatures) feature via the v2.119.43-50 ApplyLigatureSubstitution API at each position. If a ligature substitution fires AND the substitute GID is reachable through a Unicode Alphabetic Presentation Form codepoint, the source codepoint window is replaced with the substitute's codepoint. Wired into the three BuildUnicode*FieldContent helpers (single-line, multi-line, and comb AcroForm /AP appearance-stream builders) gated on sfStandardLigatures in FShapingFeatures (opt-in, byte-stable when not set).
  • Optional second pass for 'clig' (Contextual Ligatures): when sfContextualLigatures is also included in FShapingFeatures, the method re-walks the post-'liga' result with 'clig' applied to pick up font-specific contextual ligation beyond the standard fi / fl / ffi / ffl set. Each emitted substitute GID is passed through MarkUnicodeGlyphUsed for v2.84.0 TTF subsetter closure. Reverse cmap is a linear scan over ~80 codepoints (FB00-FB4F); substitutions are sparse, cost is negligible.
  • ToUnicode CMap bfchar block extended from 28 to 35 entries: 7 new entries cover the Latin Alphabetic Presentation Forms FB00 (ff -> f + f), FB01 (fi -> f + i), FB02 (fl -> f + l), FB03 (ffi -> f + f + i), FB04 (ffl -> f + f + l), FB05 (ſt -> long s + t), FB06 (st -> s + t). Consumer-side text extraction (copy / paste / accessibility) now restores the original ASCII letter sequence when a Latin ligature codepoint appears in the PDF text stream. PDFs whose callers leave sfStandardLigatures off (the default) emit byte-identical to v2.119.64 output. Scope: only emits ligature substitutes reachable through Alphabetic Presentation Form codepoints — decorative / discretionary Latin ligatures with no Unicode codepoint (font-specific GIDs) cannot be emitted through this path and remain reserved for Phase 8c.6 GID-level emit.

2026-05-22 Version 2.119.64

  • Public codepoint-advance query API (GSUB engine roadmap Phase 8c.5): the new THotPDF.GetCodepointAdvance (CP: Cardinal): Single method exposes the v2.76.0 /W cache so callers building custom word-wrap math outside the BuildUnicodeMultilineFieldContent helper can query the real scaled em advance for any codepoint, derived from the font's hmtx table via the cached cmap. After the v2.119.32 / v2.119.58 / v2.119.60 / v2.119.62 static ligature post-pass chain + v2.119.63 GID-level GSUB refinement emit ligature codepoints (LAM-ALEF FEF5-FEFC, YEH-HAMZA FBEA-FBFB, Allah FDF2, Bismillah FDFD, and GSUB-substituted Presentation Forms in FB50-FDFF / FE70-FEFF), the method returns the real ligature glyph's hmtx advance — typically narrower than the sum of source-codepoint advances — so callers' wrap budgets align with consumer-side rendering.
  • BuildUnicodeMultilineFieldContent heuristic fallback refinement: the v2.65 fallback path (used when the /W cache is unavailable, e.g., RegisterUnicodeFontDict path instead of RegisterUnicodeTTF) previously treated all codepoints >= U+2E80 as wide (1.0 em). Arabic Presentation Forms (FB50-FBFF Forms-A letters, FC00-FDFF Forms-A ligatures + Quranic FDF0-FDFD, FE70-FEFF Forms-B basic shapes + LAM-ALEF) are actually narrow (~0.55 em average across mainstream Arabic fonts), not wide. v2.119.64 routes these three Arabic Presentation Form ranges to the narrow path (0.5 em) in the heuristic fallback, fixing the edge case where Arabic ligatures emitted by the v2.119.32 / v2.119.58 / v2.119.60 / v2.119.62 / v2.119.63 post-passes would otherwise get wrong 1.0 em advances when the cache isn't populated.
  • Returns 0 for the typical no-information path (no font registered, cache not yet populated, codepoint above BMP, codepoint has no glyph in font's cmap). Pure-functional, no side effects, safe to call from any context after RegisterUnicodeTTF succeeds. PDFs whose loaded font has the /W cache populated and that don't use the new query method emit byte-identical to v2.119.63 output. Phase 8c.5 closes the GSUB engine roadmap §8c producer-side auto Arabic GSUB shaping pipeline at the capability level — Phase 8c.1-8c.5 together deliver static-table post-passes (4 ligature families) + ToUnicode reverse mapping + GID-level GSUB refinement + public advance query, all working together to make producer-side Arabic ligature rendering byte-accurate with consumer-side display.

2026-05-22 Version 2.119.63

  • GID-level GSUB 'rlig' refinement post-pass (GSUB engine roadmap Phase 8c.2): the new THotPDF.ApplyArabicGSUBRefinement (Wide) method walks the input Wide string left-to-right, building a parallel GID array via the cached cmap (FUnicodeCpToGid), and at each non-zero-GID position queries the font's GSUB 'rlig' (Required Ligatures) feature via the v2.119.43-50 ApplyLigatureSubstitution API. If a ligature substitution fires AND the substitute GID is reachable through a Unicode Arabic Presentation Form codepoint (reverse cmap scan across U+FB50-U+FDFF + U+FE70-U+FEFF, about 690 codepoints), the source codepoint window is replaced with the substitute's codepoint. Substitute glyphs are also passed through MarkUnicodeGlyphUsed for v2.84.0 TTF subsetter closure.
  • Wired into the three BuildUnicode*FieldContent helpers immediately after the v2.85.0 / v2.119.32 / v2.119.58 / v2.119.60 / v2.119.62 static codepoint-level post-pass chain (LAM-ALEF / YEH-HAMZA / Allah / Bismillah). Complements the static table by picking up font-specific 'rlig' rules that the four hard-coded families don't encode — typical examples include contextual variants in Persian / Urdu / Sindhi / Kurdish fonts (Vazirmatn, Markazi Text, Lateef, Scheherazade, Amiri) that map to Unicode Presentation Forms beyond the LAM-ALEF / YEH-HAMZA / Allah / Bismillah set. Runs only when FAutoShapeArabic is true AND sfArabicGSUB is NOT in FShapingFeatures (mutex preserves the v2.119.59 "caller-driven" path) AND the loaded font has a GSUB table.
  • Scope boundary: only substitutes whose GIDs ARE reachable through a Presentation Form codepoint via the cmap are emitted. Font-specific GSUB substitutes that land on arbitrary glyph IDs (no codepoint reverse map) cannot be emitted through this path — they need full GID-level emit in the producer pipeline reserved for Phase 8c.5+. Reverse cmap is a linear scan over ~690 codepoints per substitution attempt; substitutions are rare in practice so the cost is negligible. PDFs whose loaded font has no GSUB 'rlig' content or whose substitutes don't have Presentation Form codepoints emit byte-identical to v2.119.62 output (the method is a defensive no-op in those cases). With this slice, the GSUB engine's producer-side automatic Arabic shaping pipeline gains font-aware ligature folding beyond the hard-coded static-table chain.

2026-05-22 Version 2.119.62

  • Bismillah phrase-level ligature post-pass (GSUB engine roadmap Phase 8c.4): the v2.85.0 producer-side Arabic shaper now folds the 22-codepoint canonical Bismillah phrase "بسم الله الرحمن الرحيم" ("In the name of God, the Most Gracious, the Most Merciful") into a single U+FDFD ARABIC LIGATURE BISMILLAH AR-RAHMAN AR-RAHEEM codepoint. Runs FIRST in the post-pass chain (before LAM-ALEF / YEH-HAMZA / Allah folds) so the full phrase is detected before the الله sub-phrase gets folded by the v2.119.60 Allah post-pass. Each letter position matches the base codepoint or any of its v2.85.0 Forms-B shaped variants (R-joining letters 3 variants each, D-joining 5 variants each); the three space positions require U+0020 exactly. Phrase-level detection on the canonical un-vowelled spelling.
  • ToUnicode CMap extended with a 28th bfchar entry mapping U+FDFD back to the full 22-codepoint source sequence (BEH + SEEN + MEEM + SPACE + ALEF + LAM + LAM + HEH + SPACE + ALEF + LAM + REH + HAH + MEEM + NOON + SPACE + ALEF + LAM + REH + HAH + YEH + MEEM). Consumer-side text extraction (copy / paste / accessibility) now restores the original 22-codepoint phrase rather than the single ligature glyph, completing the round-trip behaviour for the four static-table ligature families (LAM-ALEF / YEH-HAMZA / Allah / Bismillah).
  • Known limitations (deliberate scope reductions): tatweel (U+0640) between letters not matched; harakat / diacritics between letters not matched (canonical fold targets un-vowelled Bismillah); non-breaking space U+00A0 or multiple spaces between words not matched (only single ASCII U+0020); alternative spellings (الرحمٰن with superscript alef) not matched; phrase must appear exactly without trailing modifiers. False-positive risk is essentially zero because the canonical 22-codepoint sequence is uniquely Bismillah by intent. Other Quranic honorifics (FDFA SALLALLAHOU ALAYHE WASALLAM, FDFB JALLAJALALOUHOU) and single-word ligatures (FDF3 AKBAR / FDF4 MOHAMMAD / FDF5 SALAM / etc.) remain out of scope because those source words also appear as ordinary Arabic terms in non-religious text and would generate false positives.

2026-05-22 Version 2.119.61

  • ToUnicode CMap reverse-mapping for Arabic ligatures (GSUB engine roadmap Phase 8c.3): the v2.83.0 RegisterUnicodeTTF Adobe-Identity-UCS CMap is now extended with 27 bfchar entries that override the bfrange identity mapping for every ligature codepoint emitted by the v2.85.0 producer-side Arabic shaper post-pass chain. Each entry restores the source codepoint sequence on consumer-side text extraction, fixing the limitation where copy / paste from PDF readers previously returned a single ligature character (e.g., "ﷲ" for Allah) rather than the original source word ("الله"). Bfchar takes precedence over bfrange per Adobe CMap and CIDFont Files Specification.
  • Coverage: 8 LAM-ALEF entries U+FEF5-U+FEFC (v2.119.32 mandatory ligature, 4 ALEF variants × 2 forms) → LAM + ALEF / ALEF MADDA / ALEF HAMZA ABOVE / ALEF HAMZA BELOW source pairs; 18 YEH-HAMZA family entries U+FBEA-U+FBFB (v2.119.58, includes the initial forms FBF8 and FBFB that the auto post-pass does not produce but callers may use directly) → YEH-HAMZA + ALEF / AE / WAW / U / OE / YU / E / ALEF MAKSURA source pairs; 1 Allah entry U+FDF2 (v2.119.60) → ALEF + LAM + LAM + HEH four-codepoint source sequence. With this slice, copy / paste / accessibility round-trip is complete for the three static-table ligature families.
  • Byte-stable for non-Arabic content: the bfchar block adds ~700 bytes of uncompressed PostScript text to the CMap stream, which FlateDecode typically reduces to ~150-200 bytes. PDFs without Arabic content still emit the same /ToUnicode stream layout — the extension is additive within the same indirect FlateDecode stream object. Consumer readers (Adobe Reader, Foxit, PDF.js, Apple Preview, etc.) all honour bfchar precedence over bfrange and apply the reverse mapping correctly. The 27-entry block is hardcoded in the CMap text (no per-PDF customisation needed); future Phase 8c.4 will append additional bfchar entries for the U+FDFD Bismillah ligature.

2026-05-22 Version 2.119.60

  • Allah ligature static post-pass: the v2.85.0 producer-side Arabic shaper now folds the four-codepoint sequence ALEF + LAM + LAM + HEH (الله in standard Arabic) into the single codepoint U+FDF2 ARABIC LIGATURE ALLAH ISOLATED FORM after the v2.119.32 LAM-ALEF and v2.119.58 YEH-HAMZA post-passes finish. Same model as the two earlier ligature post-passes: detect the source sequence in any combination of raw and shaped forms, emit a single codepoint output, trust the font to have the ligature glyph at the Unicode-defined cmap entry. This is the first slice of the GSUB engine roadmap Phase 8c (producer-side automatic Arabic GSUB shaping pipeline) — codepoint-level ligature folding for the most prevalent Arabic religious ligature.
  • Source-letter form matching covers all valid combinations after the v2.85.0 walker has shaped the sequence: ALEF in raw U+0627 or isolated FE8D or final FE8E form (R-joining, no init / medi); LAM in raw U+0644 or any of the four shaped forms FEDD-FEE0 (D-joining, full 4 positional forms); HEH in raw U+0647 or any of the four shaped forms FEE9-FEEC (D-joining). A typical standalone "الله" word emerges from the walker as FE8D + FEDF + FEE0 + FEEA (ALEF isol + LAM init + LAM medi + HEH final), which folds to FDF2. PDFs without the Allah codepoint sequence emit byte-identical to v2.119.59 output.
  • Known limitations of codepoint-level ligature folding: (1) Output is the isolated form FDF2 only — Unicode does not define final / initial / medial forms for the Allah ligature, but in practice Allah is almost always rendered standalone, so the single FDF2 codepoint suffices for visual rendering. (2) Consumer-side text extraction (copy / paste / accessibility) yields the single character "ﷲ" rather than the four-character word "الله" because the ToUnicode CMap has not yet been extended to reverse-map FDF2 to its 4-codepoint source. A future Phase 8c.3 installment will add the bfchar reverse mapping. (3) Font dependency: the loaded TTF / OTF must have a cmap entry mapping U+FDF2 to its Allah glyph; mainstream Arabic fonts (Noto Sans Arabic, Amiri, Scheherazade, Lateef, Markazi Text, Tahoma / Times New Roman Arabic) all include it. Other Quranic ligatures (FDF0 / FDF1 / FDF3-FDFB Allah-family honorifics, FDFD Bismillah) plus GID-level GSUB integration for font-specific substitutions are reserved for future Phase 8c.2-8c.4 installments.

2026-05-22 Version 2.119.59

  • Producer-side shaping pipeline opt-in framework (GSUB engine roadmap Phase 8a): the new THPDFShapingFeature enumeration plus THPDFShapingFeatures set type model the planned feature flags for Phase 8a-8e producer-side text shaping integration. The five enum members are sfArabicGSUB (implemented this release), sfStandardLigatures (Phase 8b, future), sfContextualLigatures (Phase 8b, future), sfStylisticAlternates (Phase 8d, future), and sfIndicShaping (Phase 8e, future). THotPDF gains a ShapingFeatures property of THPDFShapingFeatures type with default value [] (all features off); v2.119.32-58 callers see byte-identical output as long as the property is left at its default.
  • sfArabicGSUB acts as a mutex against the v2.85.0 static-table Arabic shaper. When sfArabicGSUB is included in ShapingFeatures, the three BuildUnicode*FieldContent helpers (single-line, multi-line, and comb AcroForm /AP appearance-stream builders) skip _ApplyArabicShaping entirely — no v2.85.0 four-position codepoint rewriting, no v2.119.32 LAM-ALEF post-pass, no v2.119.58 YEH-HAMZA + vowel post-pass. The caller is responsible for driving Arabic shape selection through the v2.119.43-50 GSUB engine APIs (SetGSUBScript ('arab') + GetSingleSubstituteGlyph against 'init' / 'medi' / 'fina' / 'isol' feature tags + MarkUnicodeGlyphUsed for v2.84.0 subsetter closure). Combined with sfArabicGSUB, callers using Noto Sans Arabic / Amiri / Scheherazade / Markazi Text / similar Arabic fonts can now drive full font-side GSUB Arabic shaping at producer time instead of relying on the static-table fallback.
  • Companion Arabic capability API: two new public methods GetArabicJoiningClass (CP) and GetArabicPosition (Wide, Index) mirror the v2.119.53 GetSyriacJoiningClass / v2.119.54 GetMongolianJoiningClass pattern, exposing the internal joining-class table and 4-position contextual walker the static shaper uses. Callers driving Arabic GSUB shaping manually no longer need to re-derive joining-class data per codepoint — they can reuse the cumulative v2.85.0 / v2.119.35 / v2.119.52 / v2.119.57 table that already covers basic Arabic (U+0600-U+06FF), Arabic Supplement (U+0750-U+077F), and Arabic Extended-A (U+08A0-U+08FF). With this slice landed, the user's two-item request "Forms-A FBEA-FBFB 装饰类合字 (v2.119.58) + Producer-side 自动 Arabic GSUB shaping pipeline (this release)" is fulfilled at the capability + opt-in framework layer. Full producer-side automatic GSUB application inside TextOut / BuildUnicode*FieldContent (auto cmap-to-GID + per-position GSUB substitute + substitute GID emit, with no caller code) remains reserved for the deeper Phase 8c integration commits.

2026-05-22 Version 2.119.58

  • YEH-HAMZA + vowel-letter ligature post-pass: the v2.85.0 producer-side Arabic shaper now folds the 8 ligature pairs encoded in the Arabic Presentation Forms-A block at U+FBEA-U+FBFB into single ligature codepoints after the 4-position walker and the v2.119.32 LAM-ALEF post-pass run. Same model and integration shape as LAM-ALEF (mandatory static-table substitution, 2-form output per ligature). Covered pairs: YEH-HAMZA + ALEF -> FBEA/FBEB (standard Arabic / Persian / Urdu), YEH-HAMZA + AE U+06D5 -> FBEC/FBED (Kashmiri / Uyghur), YEH-HAMZA + WAW -> FBEE/FBEF (standard Arabic), YEH-HAMZA + U U+06C7 -> FBF0/FBF1 (Uyghur / Kazakh / Kyrgyz), YEH-HAMZA + OE U+06C6 -> FBF2/FBF3 (same), YEH-HAMZA + YU U+06C8 -> FBF4/FBF5 (same), YEH-HAMZA + E U+06D0 -> FBF6/FBF7 (same), YEH-HAMZA + ALEF MAKSURA -> FBF9/FBFA (standard Arabic).
  • YEH-HAMZA is matched in raw form U+0626 or in any of its v2.85.0 shaped forms (FE89 isol, FE8A final, FE8B init, FE8C medial); the following vowel letter is matched in raw form or any of its v2.119.57 Forms-A shaped variants (FBD7-FBD8 for U, FBD9-FBDA for OE, FBDB-FBDC for YU, FBE4-FBE7 for E) or v2.85.0 Forms-B shaped variants (FE8D-FE8E for ALEF, FEED-FEEE for WAW, FEEF-FEF0 for ALEF MAKSURA). Final-form selection (base + 1) follows the LAM-ALEF rule: when the YEH-HAMZA has been shaped to FE8A final or FE8C medial form (i.e., it is preceded by a dual-joining letter), the ligature is emitted in its final form; otherwise the isolated form.
  • Scope boundaries: only the isolated and final forms (2-form output) are emitted by this post-pass. The Unicode-defined initial forms FBF8 (YEH-HAMZA + E) and FBFB (UIGHUR YEH-HAMZA + ALEF MAKSURA) are not produced — callers needing those three-form variants drive the font's 'rlig' / 'clig' chained-contextual GSUB lookups via ApplyContextualSubst. Other Arabic ligature regions (Allah U+FDFA / FDFB, decorative ligatures at FC00-FDC7) require GSUB 'rlig' / 'dlig' driving and remain out of scope for the static-table shaper. PDFs without YEH-HAMZA followed by one of the eight supported vowel letters emit byte-identical to v2.119.57; the new post-pass only fires on the specific 2-codepoint sequences.

2026-05-22 Version 2.119.57

  • Persian / Urdu / Sindhi / Kashmiri / Uyghur / Kazakh / Kyrgyz Arabic Extended letter coverage: the v2.85.0 producer-side Arabic shaper joining-class table now spans the entire U+0672-U+06D5 range per Unicode 16.0 ArabicShaping.txt, closing the "Persian/Urdu Form-B 扩展" work-item that was deferred when v2.119.35 shipped the 9-letter Persian/Urdu core and v2.119.52 added the ALEF WASLA / NOON GHUNNA / HEH variants. About 80 letters are added to the joining-class table covering REH / DAL / SEEN / SAD / TAH / AIN / FEH / QAF / KAF / GAF / LAM / NOON / HEH / WAW / YEH variants — so adjacent letters now pick correct positional forms regardless of which Arabic Extended-A letter sits next to them.
  • 26 of the newly-classified letters additionally have static Presentation Forms-A entries in U+FB52-U+FBFC and are now mapped directly by the static-table shaper without requiring a font GSUB shaper: 15 D-joining 4-form letters (TTEHEH U+067A → FB5E-FB61, BEEH U+067B → FB52-FB55, TEHEH U+067F → FB62-FB65, BEHEH U+0680 → FB5A-FB5D, NYEH U+0683 → FB76-FB79, DYEH U+0684 → FB72-FB75, TCHEHEH U+0687 → FB7E-FB81, VEH U+06A4 → FB6A-FB6D, PEHEH U+06A6 → FB6E-FB71, NG U+06AD → FBD3-FBD6, NGOEH U+06B1 → FB9A-FB9D, GUEH U+06B3 → FB96-FB99, RNOON U+06BB → FBA0-FBA3, HEH DOACHASHMEE U+06BE → FBAA-FBAD which is the standard Urdu 'h' letter, E U+06D0 → FBE4-FBE7 for Kazakh/Kyrgyz Arabic), and 11 R-joining 2-form letters (DAHAL U+068C → FB84-FB85, DDAHAL U+068D → FB82-FB83, DUL U+068E → FB86-FB87, KIRGHIZ OE U+06C5 → FBE0-FBE1, OE U+06C6 → FBD9-FBDA, U U+06C7 → FBD7-FBD8, YU U+06C8 → FBDB-FBDC, KIRGHIZ YU U+06C9 → FBE2-FBE3, VE U+06CB → FBDE-FBDF, YEH BARREE U+06D2 → FBAE-FBAF which is the Urdu word-final yeh, YEH BARREE WITH HAMZA ABOVE U+06D3 → FBB0-FBB1).
  • The two letters HEH DOACHASHMEE and YEH BARREE deserve special mention because their Forms-A slots (FBAA-FBAD and FBAE-FBAF) were what the v2.119.52 bug wrongly assigned to U+06C2 HEH GOAL WITH HAMZA ABOVE and U+06C3 TEH MARBUTA GOAL. With v2.119.56 backing out those incorrect mappings and v2.119.57 binding the slots to the correct source letters, the audit trail is clean and the conflation is permanently resolved. Letters in the U+0672-U+06D5 range without Presentation Forms-A entries in Unicode 16 (about 50 additional codepoints, mostly REH / DAL / SEEN / SAD / TAH / AIN / FEH / QAF / KAF variants with no pre-encoded shaped forms) still participate in joining-class analysis so neighbours shape correctly; the letters themselves pass through as raw codepoints and rely on the font's GSUB lookup tables under the v2.119.43-50 GSUB engine for their own shape selection. Compiled at 78018 lines; PDFs without these new codepoints emit byte-identical to v2.119.56.

2026-05-22 Version 2.119.56

  • Bug fix for Arabic shaping: v2.119.52 introduced incorrect Arabic Presentation Forms-A mappings for two Urdu/Sindhi-region characters. U+06C2 HEH GOAL WITH HAMZA ABOVE was mapped to FBAA-FBAD, which is actually the codepoint range for U+06BE HEH DOACHASHMEE; consumer readers would render the Urdu Goal-Heh-with-Hamza glyph as the standard Urdu Heh-Doachashmee glyph instead. U+06C3 TEH MARBUTA GOAL was mapped to FBAE-FBAF, which is actually the codepoint range for U+06D2 YEH BARREE; readers would render Teh-Marbuta-Goal as the Urdu word-final Yeh-Barree. Per Unicode 16.0, neither U+06C2 nor U+06C3 has pre-encoded Presentation Forms-A entries; both must pass through unchanged as raw codepoints (consumer readers display them correctly via the font's cmap and any GSUB-driven shaping).
  • Fix: the two erroneous case entries in the v2.85.0 producer-side Arabic shaper's _ArabicShape Presentation Forms-A lookup table have been removed; U+06C2 and U+06C3 now fall through to the passthrough path and emit unchanged. The joining-class entries from v2.119.52 (D for U+06C2, R for U+06C3) are retained so adjacent letters still pick correct positional forms when these characters appear in a word. The fix only affects PDFs that contain U+06C2 or U+06C3 with AutoShapeArabic enabled; PDFs without these two codepoints are byte-identical to v2.119.55 output. The companion v2.119.57 release expands the Forms-A coverage to additional Persian / Urdu / Sindhi / Kashmiri / Uyghur / Kazakh / Kyrgyz letters, including the genuinely Forms-A-encoded HEH DOACHASHMEE and YEH BARREE that the v2.119.52 mistake was conflating.

2026-05-22 Version 2.119.55

  • Devanagari Indic shaping capability: two new public methods on THotPDF cover the Indic shaping paradigm that does not fit the 4-position joining-class walker used by Arabic / Syriac / Mongolian (v2.85.0 + v2.119.32-54). GetDevanagariCategory (CP) returns the simplified Indic syllabic category (0 = Other, 1 = Consonant, 2 = Independent Vowel, 3 = Matra, 4 = Virama, 5 = Nukta, 6 = Bindu, 7 = Visarga, 8 = Danda, 9 = Digit, 10 = ZWJ, 11 = ZWNJ, 12 = Modifier) per Unicode 16.0 §12.1 + IndicSyllabicCategory.txt; the table covers the full Devanagari block U+0900-U+097F including the Marwari / Sindhi / Vedic extensions in U+0978-U+097F.
  • ApplyDevanagariReorder (Wide) walks the input string left-to-right and applies the Devanagari reorder pre-pass to every Devanagari syllable encountered, returning a reordered UnicodeString ready for cmap + GSUB consumption. Non-Devanagari content (Latin, digits, punctuation, other scripts) passes through byte-identical. Two reorders are implemented (the two that dominate real-world Devanagari shaping): (1) Repha — when a syllable starts with Ra (U+0930) + Halant (U+094D) + Consonant, the (Ra, Halant) pair is moved to the END of the syllable so the font's 'rphf' GSUB feature substitutes it with the Repha glyph that visually attaches above-right of the syllable's base; (2) Pre-base I-matra — U+093F is moved to the very START of the syllable (after any Repha removal) so left-to-right GSUB processing sees it in front of the consonant cluster, matching its visual rendering order.
  • Caller pipeline: ApplyDevanagariReorder on the logical-order text -> SetGSUBScript ('deva') -> walk reordered codepoints applying GSUB features ('nukt' / 'akhn' / 'rphf' / 'rkrf' / 'half' / 'vatu' / 'cjct' / 'pres' / 'blws' / 'abvs' / 'psts' / 'haln') via GetSingleSubstituteGlyph + ApplyLigatureSubstitution + ApplyContextualSubst -> emit the resulting GIDs to the PDF text stream while calling MarkUnicodeGlyphUsed (GID) per emit for v2.84.0 subsetter closure. With Phase A (v2.119.52 Arabic Extended-A closure) + Phase B (v2.119.53 Syriac capability) + Phase C (v2.119.54 Mongolian capability) + Phase D (v2.119.55 Devanagari Indic capability) all landed, the matrix gap "Mongolian / Syriac / Devanagari shaping" is fully closed. Out of scope: non-Devanagari Indic scripts (Bengali, Tamil, Telugu, Gujarati — each has its own category data and reorder rules), reorder rules beyond Repha + pre-base I-matra, and producer-side automatic Devanagari shaping inside TextOut / BuildUnicode*FieldContent (reserved for Phase 8 of the GSUB engine roadmap).

2026-05-22 Version 2.119.54

  • Mongolian shaping capability: two new public methods on THotPDF expose Mongolian (U+1800-U+18AF) joining-class lookup and 4-position contextual analysis so callers can drive producer-side shaping of Mongolian text. GetMongolianJoiningClass (CP) returns the Unicode 16.0 §13.5 Mongolian joining class (0 = U non-joining, 2 = D dual-joining, 4 = T transparent / variation selector) for any codepoint; the table covers basic Mongolian plus the Todo, Sibe, Manchu, and Ali Gali letter extensions (U+1820-U+1878 + U+1887-U+18A8 + ALI GALI DAGALGA U+18A9 + MANCHU ALI GALI LHA U+18AA) and classifies NIRUGU (U+180A), the three Free Variation Selectors FVS1-FVS3 (U+180B-U+180D), and the Ali Gali BALUDA / THREE BALUDA vowel marks (U+1885-U+1886) as transparent. GetMongolianPosition (Wide, Index) walks the surrounding text skipping transparent marks and returns 0 = isolated, 1 = final, 2 = initial, or 3 = medial for the letter at the 0-based Index.
  • Like Syriac (v2.119.53), Mongolian has NO pre-encoded Presentation Forms in Unicode — every Mongolian-capable font (Mongolian Baiti, Noto Sans Mongolian, Noto Serif Mongolian, etc.) drives positional shaping via OpenType GSUB lookups in its 'init' / 'medi' / 'fina' / 'isol' features under the 'mong' script tag. v2.119.54 therefore follows the v2.119.53 capability-only model; callers compose the position output with the v2.119.43-50 GSUB engine APIs (SetGSUBScript ('mong'), GetSingleSubstituteGlyph against the appropriate positional feature tag, MarkUnicodeGlyphUsed for v2.84.0 subsetter closure) to produce shaped GIDs that emit directly into the PDF text stream.
  • Mongolian is written vertically top-to-bottom in its native writing mode; the walker operates on LOGICAL codepoint order (predecessor / successor in the codepoint stream), independent of visual orientation. The actual top-to-bottom rendering is handled at PDF emit time via /WMode 1 (vertical writing) or font-matrix rotation, not by the shaper. Free Variation Selectors classification as transparent means the walker correctly resolves positional forms even when an FVS follows a letter; the font's GSUB lookup under the 'fina' / 'medi' / 'init' / 'isol' feature picks the FVS-conditioned variant automatically when a FVS occurs in the input run. Devanagari (Indic) shaping remains reserved for future versions because it requires an Indic reorder pre-pass per UAX #38 (virama / consonant cluster / pre-base reordering), which does not map onto the 4-position walker used by Arabic / Syriac / Mongolian. Automatic Mongolian shaping inside TextOut / BuildUnicode*FieldContent remains reserved for Phase 8 of the GSUB engine roadmap.

2026-05-22 Version 2.119.53

  • Syriac shaping capability: two new public methods on THotPDF expose Syriac (U+0700-U+074F) joining-class lookup and 4-position contextual analysis so callers can drive producer-side shaping of Syriac text. GetSyriacJoiningClass (CP) returns the Unicode 16.0 SyriacShaping.txt joining class (0 = U non-joining, 1 = R right-joining, 2 = D dual-joining, 4 = T transparent / NSM) for any codepoint; the table covers all 35 Syriac letters across the base block plus the Persian and Sogdian extensions (PERSIAN BHETH / GHAMAL / DHALATH at U+072D-U+072F, SOGDIAN ZHAIN / KHAPH / FE at U+074D-U+074F) and classifies the SUPERSCRIPT ALAPH (U+0711) and Syriac combining marks (U+0730-U+074A) as transparent. GetSyriacPosition (Wide, Index) walks the surrounding text skipping transparent marks and returns 0 = isolated, 1 = final, 2 = initial, or 3 = medial for the letter at the 0-based Index.
  • Unlike Arabic (which has pre-encoded Presentation Forms-B U+FE70-U+FEFF and Forms-A U+FB50-U+FBFF blocks that let HotPDF rewrite codepoints to shaped variants directly), the Syriac block has NO pre-encoded presentation forms in Unicode — every Syriac-capable font (Estrangelo Edessa, Serto Jerusalem, East Syriac Adiabene, Noto Sans Syriac, etc.) drives positional shaping via OpenType GSUB lookups in its 'init' / 'medi' / 'fina' / 'isol' features. v2.119.53 therefore ships only the capability layer; the caller composes the position output with the v2.119.43-50 GSUB engine APIs (SetGSUBScript ('syrc'), GetSingleSubstituteGlyph against the appropriate positional feature tag, MarkUnicodeGlyphUsed for v2.84.0 subsetter closure) to produce shaped GIDs that emit directly into the PDF text stream.
  • Alaph terminal-form scope: Unicode §9.3 documents two contextual terminal features ('fin2' applied after DALATH / RISH, 'fin3' applied after FINAL LAMADH) that Syriac fonts use to swap Alaph's final glyph. v2.119.53 classifies Alaph (U+0710) as basic right-joining and returns 0 (isolated) or 1 (final) only; callers needing 'fin2' / 'fin3' differentiation drive the font's chained-contextual lookups via ApplyContextualSubst with those feature tags. Mongolian and Devanagari (Indic) shaping remain reserved for future versions: Mongolian uses Free Variation Selectors plus vowel harmony, Devanagari requires Indic reorder pre-pass per UAX #38, neither maps onto the Syriac-style 4-position walker. Automatic Syriac shaping inside TextOut / BuildUnicode*FieldContent remains reserved for Phase 8 of the GSUB engine roadmap.

2026-05-22 Version 2.119.52

  • Arabic family shaping extension: the v2.85.0 producer-side static Arabic shaper now covers Arabic Extended-A remaining characters (ALEF WASLA U+0671, NOON GHUNNA U+06BA, HEH variants U+06C0-U+06C3), the Arabic Supplement block U+0750-U+077F (African Arabic letters used in Hausa, Wolofal, and other African languages), and the Arabic Extended-A new region U+08A0-U+08FF (Quranic Arabic + Wolofal/Hausa extended letters + Quranic combining marks). Joining classes are sourced from Unicode 16.0 ArabicShaping.txt and applied during the v2.85.0 four-position positional analysis so neighbours pick the correct init / medi / fina / isol form regardless of which Arabic variant they sit next to.
  • Letters that have pre-encoded static presentation forms in the Arabic Presentation Forms-A block (U+FB50-U+FBFF) now map to those forms directly without requiring an OpenType GSUB shaper: ALEF WASLA -> FB50/FB51 (right-joining, two forms), NOON GHUNNA -> FB9E/FB9F (dual-joining per Unicode but only two pre-encoded forms; init/medi gracefully degrade to isol), HEH WITH YEH ABOVE -> FBA4/FBA5 (right-joining, two forms), HEH GOAL -> FBA6/FBA7/FBA8/FBA9 (dual-joining, full four forms), HEH GOAL WITH HAMZA ABOVE -> FBAA/FBAB/FBAC/FBAD (dual-joining, full four forms), TEH MARBUTA GOAL -> FBAE/FBAF (right-joining, two forms). Combined with the v2.119.32 LAM-ALEF mandatory ligature post-pass and the v2.119.35 Persian/Urdu core 9-letter subset, the static shaper now covers virtually all modern Arabic, Persian, Urdu, Sindhi, Pashto, Kurdish, Uyghur, Quranic, and African Arabic (Hausa, Wolofal, etc.) text without depending on the font's GSUB table.
  • Arabic Supplement (U+0750-U+077F) and the newer Arabic Extended-A letter region (U+08A0-U+08C7) have no pre-encoded static presentation forms; their joining-class entries let neighbours shape correctly, but the letters themselves remain as raw codepoints and rely on a GSUB-aware font (or the v2.119.43-50 GSUB engine APIs driven by the caller) for their own positional shaping. The Arabic Extended-A Quranic combining marks region (U+08CA-U+08E1, U+08E3-U+08FF) is classified as transparent (T-joining) so neighbour positional analysis skips them, matching the existing U+064B-U+065F harakat treatment. Text that does not include these extended characters remains byte-identical to v2.119.51 output. Mongolian / Syriac / Devanagari shaping (different shaping models from Arabic) remain reserved for future revisions.

2026-05-22 Version 2.119.51

  • XFA (XML Forms Architecture) producer-side container support: PDF 1.7 §12.7.6 + §12.7.8 lets an AcroForm carry an /XFA entry alongside (or instead of) the static /Fields array. The new THotPDF.AddXFAPacket (PacketName, XMLBytes) registers one named XFA packet — typical names are 'template' (form layout + scripts), 'datasets' (data binding), 'config' (viewer configuration), 'connectionSet', 'localeSet', 'stylesheet', 'xfdf', 'xmpmeta', 'signature', and 'sourceSet'. Each registered packet becomes one indirect FlateDecode-compressed stream at EndDoc, and the /XFA array alternates packet-name PDF strings with stream references in registration order so the layout is byte-deterministic. THotPDF.ClearXFAPackets and THotPDF.XFAPacketCount round out the API surface.
  • Container-level support only — HotPDF does NOT parse the XFA XML, does NOT implement the XFA dynamic-layout engine, does NOT execute FormCalc or JavaScript inside the XFA template, and does NOT validate template structure. The caller authors the XFA XML themselves (typically via Adobe LiveCycle Designer or hand-rolling against the XFA 3.3 specification) and HotPDF emits the bytes verbatim. Consumer readers with an XFA engine (older Acrobat / Reader, FormsCentral, some kiosk products) render the form; readers without an XFA engine (Adobe Reader DC from 2017 onward, most open-source readers) display the fallback AcroForm fields if the document is a hybrid AcroForm + XFA workflow.
  • PDF/A and PDF/X compliance gates: ISO 19005-1 §6.4.2 / ISO 19005-2 §6.4.2 / ISO 19005-3 §6.4.2 all explicitly disallow XFA forms (the dynamic-layout engine is not deterministically archivable). ISO 15930 prepress workflows reject interactive forms entirely. AddXFAPacket raises immediately under any non-empty PDFACompliance or PDFXCompliance with the relevant spec reference. Duplicate packet names raise. Empty PacketName or empty XMLBytes raise. RequirePDFVersion (pdf15) auto-bumps the document version. The AcroForm dict is now emitted when either /Fields has at least one entry OR at least one XFA packet has been registered; pre-v2.119.51 callers that register no packets keep byte-identical /AcroForm output.

2026-05-22 Version 2.119.50

  • TTF subsetter closure for GSUB substitute glyphs: the new THotPDF.MarkUnicodeGlyphUsed (GID: Word) opts a glyph into the v2.84.0 EndDoc subsetter regardless of whether the cmap has any codepoint reaching that glyph. The v2.84.0 closure derives its used-glyph set from FUnicodeUsedCps via the cmap, so every GSUB-introduced substitute glyph — stylistic alternates, ligatures, contextual variants, the entire output of Phase 1-6 query APIs — was previously invisible to the subsetter and the consumer reader rendered .notdef in their place. After emitting any GID returned by GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst into a PDF text stream, the caller now calls MarkUnicodeGlyphUsed (GID) once per emitted GID to pull it into the embedded subset.
  • The helper is idempotent (repeated calls with the same GID are a no-op), defensive (GID >= FUnicodeNumGlyphs is silently dropped, calls before any RegisterUnicodeTTF succeed are no-ops), and integrates with the v2.84.0 composite-glyph closure pass inside _BuildSubsetTTF: callers only need to mark the top-level substitute GID — composite components are auto-pulled by the existing _TTFWalkCompositeClosure walk. The set is allocated lazily on first call and reset to empty on every RegisterUnicodeTTF ('', nil) along with the rest of the subset state. With Phase 9 landed, every Phase 1-7 GSUB query API can be safely paired with subset emission without the substitute glyphs falling out of the embedded font.

2026-05-22 Version 2.119.49

  • OpenType GSUB Script / LangSys selection API: five new public methods on THotPDF let callers pin GSUB lookups to a specific script and language instead of the historic DFLT-script / default-LangSys fallback. SetGSUBScript ('latn' / 'arab' / 'cyrl' / 'hani' / 'kana' / 'deva' / 'beng' / 'taml' / etc.) selects an OpenType script tag; the default empty string keeps the v2.119.43-48 baseline (prefer 'DFLT', otherwise the first script). SetGSUBLanguage ('ENG ' / 'TUR ' / 'AZE ' / 'JAN ' / 'KOR ' / 'ARA ' / etc., trailing-space padded to 4 bytes) selects an OpenType language tag; empty string is the script's default LangSys. Both settings persist across GSUB queries and are reset to empty on RegisterUnicodeTTF ('', nil).
  • Three new enumeration helpers expose what the loaded font advertises: GetGSUBScripts: TGSUBStringArray returns every script tag in ScriptList order; GetGSUBLanguages (ScriptTag) returns every language tag under the given script (with a '' placeholder at index 0 when the script has a default LangSys); GetGSUBFeatures (ScriptTag, LangTag) returns every feature tag (e.g. 'liga', 'salt', 'aalt', 'ss01' through 'ss20') advertised by the (script, language) path in LangSys featureIndices order. Empty ScriptTag / LangTag use the same DFLT-first / default-LangSys fallback as the substitution APIs, so callers can call GetGSUBFeatures ('', '') to enumerate the default-path features without first inspecting the script list.
  • Strict-vs-fallback selection semantics: when SetGSUBScript is set to a tag the loaded font does not advertise, subsequent GSUB queries return empty / no-op results — the engine does NOT silently fall back to DFLT, so callers can clearly tell their chosen script is unavailable. When SetGSUBLanguage is set to an unrecognised tag, it DOES fall back to the script's default LangSys per the OpenType spec recommendation. The seven existing GSUB substitution methods (GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyphCount / GetAlternateGlyph / ApplyLigatureSubstitution / ApplyContextualSubst / ApplyReverseChainedContextualSubst) keep their public signatures byte-identical; only the internal _GSUBFindFeatureLookups helper grows two trailing parameters that all seven call sites now route through. With Script / LangSys API and the LookupType 1-8 matrix landed, the GSUB engine is feature-complete for capability-only use; the remaining roadmap phases (auto shaping pipeline + TTF subsetter closure) target producer-side integration rather than new query surface.

2026-05-22 Version 2.119.48

  • OpenType GSUB Reverse Chained Contextual Single Substitution (LookupType 8): the new THotPDF.ApplyReverseChainedContextualSubst (const InputGIDs; StartIndex; FeatureTag; out OutGID): Boolean is the last GSUB lookup type to land. Type 8 does context-aware 1:1 single substitution whose substitute is selected by the input glyph's Coverage index, and whose distinguishing feature is that callers must apply it in REVERSE scan order over a multi-glyph run (end → start) because each substitute may depend on FUTURE lookahead context that must not have been substituted yet. The helper is a single-point applier — the caller drives the reverse scan loop. Coverage Format 1 + 2, LookupFlag honor (input + backtrack + lookahead all skip-aware), and Extension wrapping (LookupType 7) are all supported.
  • Canonical use case: certain Arabic / Syriac / N'Ko / Indic contextual alternates whose final form depends on the following glyph being a specific shape. Some fonts also wire it for Latin sequence disambiguation. Unlike LookupType 5/6, Type 8 has no nested-lookup machinery since the substitution is intrinsically 1:1. No match returns False, OutGID = InputGIDs[StartIndex] (best-effort passthrough matching the v2.119.43 GetSingleSubstituteGlyph contract).
  • Closes the LookupType matrix: with v2.119.48, every OpenType GSUB LookupType (1 through 8) has a dedicated public capability on THotPDF. Script / LangSys selection API, automatic shaping pipeline integration, and TTF subsetter auto-pull of substitute glyphs remain reserved for Phase 7-9; callers are still responsible for marking chosen substitute glyphs into the embedded font subset and for driving the reverse-scan loop themselves.

2026-05-22 Version 2.119.47

  • OpenType GSUB Contextual + Chained Contextual Substitution (LookupType 5 + 6): the new THotPDF.ApplyContextualSubst (const InputGIDs; StartIndex; FeatureTag; var OutGIDs; out ConsumedLen): Boolean is a single entry point covering both LookupType 5 (input-only context) and LookupType 6 (backtrack / input / lookahead context) across all three subtable formats — Format 1 (literal glyph-ID sequences), Format 2 (ClassDef class sequences), and Format 3 (Coverage-table sequences, the canonical form used by most contemporary fonts). The canonical users are 'rclt' (Required Contextual Alternates — Arabic positional forms init/medi/fina/isol when the font drives them through GSUB), 'clig' (Contextual Ligatures), 'calt' (Contextual Alternates), and the Indic shaping features 'pres' / 'blws' / 'psts' / 'half' / 'pstf' / 'cjct'.
  • SequenceLookupRecord nested lookup dispatcher: when a contextual rule matches, the SequenceLookupRecord entries each fire a nested lookup at a specific position inside the matched input sub-sequence. The new _GSUBApplyNestedLookup helper re-enters the GSUB LookupList, resolves Extension wrappers transparently, and dispatches into Single (Type 1) / Multiple (Type 2) / Alternate (Type 3, first alternate) / Ligature (Type 4) appliers. The dispatcher tracks live MatchPositions[] indices so subsequent SequenceLookupRecord entries continue to address the correct working positions even when an earlier Multiple Substitution expands 1→N or a Ligature Substitution collapses N→1 glyphs around them. Nested LookupType 5 / 6 / 8 (recursive contextual) is intentionally deferred; most real fonts wire nested lookups to Type 1 / 4 anyway.
  • On a match the method returns True together with the output glyph sequence that should replace InputGIDs[StartIndex..StartIndex+ConsumedLen-1]. OutGIDs length can differ from ConsumedLen when nested 1→N or N→1 lookups fire. ConsumedLen is always the total input span the contextual rule absorbed (including LookupFlag-skipped marks between significant glyphs); a typical scan-loop caller advances by ConsumedLen and emits OutGIDs. No match returns False, empty OutGIDs, ConsumedLen = 1 — the same safe no-op contract as v2.119.43-46 helpers. LookupFlag honor (Phase 4) applies throughout the contextual match: backtrack / input / lookahead walks all skip glyphs flagged for ignore. Script / LangSys remain pinned to DFLT default for this slice; the selection API is Phase 7's scope.

2026-05-22 Version 2.119.46

  • OpenType GSUB Extension Lookups (LookupType 7): the GSUB engine now transparently unpacks Extension Substitution subtables — a pure indirection layer the OpenType spec defines for fonts so large their actual substitution subtable lives beyond the 16-bit reach of the LookupList's Offset16. Every v2.119.43-45 public API (GetSingleSubstituteGlyph / GetMultipleSubstituteGlyphs / GetAlternateGlyphCount / GetAlternateGlyph / ApplyLigatureSubstitution) automatically follows the 32-bit Offset32 indirection to the real LookupType 1 / 2 / 3 / 4 subtable. This is the binary-layout change that unblocks heavy CJK / Indic OpenType fonts (Noto Sans CJK, Noto Sans Devanagari, etc.) whose GSUB tables exceed 64 KB and have long shipped their lookups behind LookupType 7 wrappers; previously such fonts looked feature-empty to HotPDF.
  • OpenType GDEF (Glyph Definition) table parsing: RegisterUnicodeTTF now also caches the font's GDEF table when present. Three GDEF sub-tables drive the LookupFlag honor logic — GlyphClassDef classifies each GID as base (1) / ligature (2) / mark (3) / component (4); MarkAttachClassDef tags every mark glyph with an attachment class; MarkGlyphSetsDef (v1.2+) declares named sets of mark glyphs. ClassDef Format 1 (startGlyphID + classValueArray) and Format 2 (sorted classRangeRecords) are both implemented; GDEF v1.0 / v1.1 / v1.2 headers are all accepted (v1.3 itemVariationStore is ignored). Fonts without a GDEF table fall back to the v2.119.43-45 baseline behaviour ("no glyph is ignored") so byte-identical output is preserved for callers using fonts that lack GDEF.
  • OpenType GSUB LookupFlag honor: all five GSUB public APIs now read each Lookup table's LookupFlag (and the optional trailing markFilteringSet uint16 when LookupFlag.useMarkFilteringSet is set) and skip input glyphs flagged for ignore. The spec-defined flag bits are honored: 0x0002 ignoreBaseGlyphs (skip GDEF.GlyphClassDef class 1), 0x0004 ignoreLigatures (skip class 2), 0x0008 ignoreMarks (skip class 3), 0x0010 useMarkFilteringSet (only marks in the named MarkGlyphSet participate), and the high byte markAttachmentType (only marks with the requested MarkAttachClassDef class participate). For Ligature Substitution (LookupType 4) this is most consequential: Arabic 'rlig' / Indic 'akhn' ligatures whose components are separated by mark glyphs (LAM + Fatha + ALEF, for example) now match correctly with LookupFlag.ignoreMarks set, and the returned ConsumedCount includes the skipped marks so the caller's scan loop advances past every absorbed input glyph. Bit 0x0001 (rightToLeft) is GPOS-specific and remains ignored for GSUB lookups.
  • Same defensive contract continues to apply: fonts without a GSUB table, callers passing a non-4-byte tag, features the font's DFLT script does not advertise, GIDs no subtable covers, and now LookupFlag-ignored input glyphs all return the v2.119.43-45 safe no-op result. The five public API signatures are byte-stable; only the internal lookup walk picks up Extension dispatch + LookupFlag honor + GDEF classification. Script / LangSys selection API, automatic shaping pipeline integration, and TTF subsetter auto-pull of substitute glyphs remain reserved for future revisions.

2026-05-22 Version 2.119.45

  • OpenType GSUB Ligature Substitution (LookupType 4): the new THotPDF.ApplyLigatureSubstitution (const InputGIDs: array of Word; StartIndex; FeatureTag; out OutGID; out ConsumedCount): Boolean folds a multi-glyph component sequence into a single ligature glyph. Canonical users are 'liga' (Standard Ligatures — fi / fl / ffi / ffl), 'clig' (Contextual Ligatures), 'dlig' (Discretionary Ligatures), 'hlig' (Historical Ligatures), 'rlig' (Required Ligatures — Arabic LAM-ALEF and similar when the font drives shaping through GSUB), and the Indic-script ligature features ('akhn', 'pres', 'blws', 'psts'). The caller passes the post-cmap glyph-ID run and a 0-based start position; on a complete match the helper returns True, OutGID = the ligature replacement glyph, and ConsumedCount = the total number of input components consumed (>= 2 in practice). No match returns False, OutGID = 0, ConsumedCount = 1 so the caller can advance one step and continue scanning. The implementation honors the OpenType convention that LigatureSet entries are typically listed longest-first, letting fonts with overlapping prefixes (e.g. a three-component 'ffi' ligature alongside a two-component 'ff' ligature) get the longer match. Same defensive contract as v2.119.43/.44 helpers: empty / non-4-byte tag, no GSUB, feature not advertised, StartIndex out of range, and no LookupType 4 subtable matching at the position all return the safe no-op (False / OutGID = 0 / ConsumedCount = 1). LookupFlag mark-skipping, GDEF GlyphClassDef integration, automatic application during text emit, and TTF subsetter auto-pulling of ligature glyphs remain reserved for future revisions.

2026-05-22 Version 2.119.44

  • OpenType GSUB Multiple Substitution (LookupType 2): the new THotPDF.GetMultipleSubstituteGlyphs (InputGID, FeatureTag, var OutGIDs) returns one input glyph as a sequence of substitute glyphs. The canonical user is the 'ccmp' Glyph Composition / Decomposition feature, which splits precomposed accented Latin letters into base + combining marks for downstream mark positioning. The helper walks the same DFLT script / default LangSys / FeatureList / LookupList path as v2.119.43 GetSingleSubstituteGlyph and applies every LookupType 2 subtable wired to the requested feature against InputGID; the first match wins. OutGIDs is reset on entry so callers do not need to pre-clear. Spec-permitted empty Sequence (glyphCount = 0, Unicode-canonical glyph deletion) is reported as Result = True with Length(OutGIDs) = 0 so the caller can drop the input glyph from the run.
  • OpenType GSUB Alternate Substitution (LookupType 3): two new methods expose the full alternate-glyph cycle for stylistic features. THotPDF.GetAlternateGlyphCount (InputGID, FeatureTag) returns the number of alternate forms a font offers for InputGID under FeatureTag, and THotPDF.GetAlternateGlyph (InputGID, FeatureTag, AlternateIndex) returns the alternate glyph at the requested 0-based index. The canonical users are 'aalt' (Access All Alternates), 'salt' (Stylistic Alternates), 'titl' (Titling Alternates), and 'ss01' through 'ss20' (Stylistic Sets 1-20) when wired with LookupType 3 instead of LookupType 1; v2.119.43 GetSingleSubstituteGlyph silently skipped these subtables, so 'aalt' callers previously only saw the LookupType 1 transitive replacements. The pair lets callers walk decorative-glyph cycles (different 'a' / 'g' / '7' shapes), titling-cap variants, and stylistic-set alternates explicitly. Out-of-range / negative AlternateIndex returns InputGID unchanged so callers can probe safely.
  • Both helpers honor the same defensive contract as v2.119.43: fonts without a GSUB table, callers passing a non-4-byte tag, features the font's DFLT script does not advertise, and GIDs no LookupType 2 / 3 subtable covers all return the safe no-op (False + empty OutGIDs for Multiple; 0 / InputGID for Alternate). LookupType 1 / 4-8 subtables encountered on the feature's lookup chain are skipped silently in this slice. LookupFlag mark-filtering, GDEF GlyphClassDef integration, Script / LangSys selection API, automatic shaping pipeline integration, and TTF subsetter auto-pulling of selected substitute glyphs remain reserved for future revisions; callers are still responsible for marking the chosen glyphs into the embedded font subset.

2026-05-22 Version 2.119.43

  • OpenType GSUB stylistic-alternates lookup: the new THotPDF.GetSingleSubstituteGlyph (InputGID, FeatureTag) walks the font's GSUB ScriptList / FeatureList / LookupList chain under the DFLT script's default LangSys and applies every LookupType 1 (Single Substitution) subtable wired to the requested feature, returning the substituted glyph ID. The most common stylistic-alternates feature tags are 'salt' (Stylistic Alternates), 'ss01' through 'ss20' (Stylistic Sets 1-20), 'aalt' (Access All Alternates), 'titl' (Titling Alternates), 'subs' / 'sups' (Subscript / Superscript), 'frac' (Fractions), and 'ordn' (Ordinals); any 4-byte OpenType feature tag whose feature chain contains LookupType 1 subtables is supported. Coverage Format 1 (sorted glyph array, binary-searched) and Format 2 (range records) are both honored. Single Substitution Format 1 (delta) and Format 2 (substitute array) are both implemented. RegisterUnicodeTTF now caches the GSUB table offset / length alongside cmap so query-time GSUB walks avoid re-scanning the table directory. Fonts without a GSUB table, callers passing a 4-byte tag the font's DFLT script does not advertise, and GIDs not in the lookup's Coverage all return InputGID unchanged so the API is safe to call as a best-effort substitute-or-passthrough. LookupType 2-8 subtables encountered on the feature's lookup chain are skipped silently in this first slice — Multiple Substitution / Alternate Substitution / Ligature Substitution / Contextual chains and the automatic shaping pipeline integration are reserved for future revisions.

2026-05-22 Version 2.119.42

  • AcroForm /DR Resources now accepts multiple Unicode fonts. v2.56.0 SetFormUnicodeFontDict only tracks a single caller-supplied font that becomes the AcroForm /DA default; multilingual forms needing different scripts per field (Arabic + CJK + Latin in one PDF) had to author /DR /Font dict by hand. The new THotPDF.RegisterAcroFormFont (LogicalName, FontDict) registers an additional Unicode font under any logical name, makes it available for per-field /DA strings (/ 12 Tf), and emits it into the AcroForm /DR /Font dict alongside the v2.56.0 default font. Registration order is preserved so the emitted /DR is deterministic. Name collisions with the SetFormUnicodeFontDict default or a previously-registered font raise with the offending name. Empty LogicalName / nil FontDict also raise. SetFormUnicodeFontDict ('', nil) reset path now also clears the additional-font registry so a fresh Unicode workflow starts from a known-clean state. Allowed in all PDF/A / PDF/X levels — composite Unicode fonts are structural form metadata, not interactive script.

2026-05-22 Version 2.119.41

  • Standard structure-type enum covering PDF 1.7 §14.8.4 Table 333: the new THPDFStandardStructureType enum lists the ~40 spec-prescribed structure roles grouped by their PDF role bucket (Grouping: Document / Part / Art / Sect / Div / BlockQuote / Caption / TOC / TOCI / Index / NonStruct / Private; Block-level: H / H1-H6 / P / L / LI / Lbl / LBody / Table / TR / TH / TD / THead / TBody / TFoot; Inline-level: Span / Quote / Note / Reference / BibEntry / Code / Link / Annot; Ruby + Warichu: Ruby / RB / RT / RP / Warichu / WT / WP; Illustration: Figure / Formula / Form). Two unit-level helpers turn the enum into spec-exact PDF names — StandardStructureTypeToName (T) returns the case-sensitive name used as the structure element's /S key, and IsStandardStructureType (Name) tests an arbitrary AnsiString against the standard set so callers can pre-flight custom role names and route them through AddStructRoleMap when needed. A new AddStructureElement enum overload accepts THPDFStandardStructureType directly, eliminating the typo / case-sensitivity hazard ('Para' vs 'P', 'Lbody' vs 'LBody') of the open-string overloads while forwarding to the same base implementation. The existing AnsiString-based overloads remain byte-identical for callers using custom or RoleMap-routed names.

2026-05-22 Version 2.119.40

  • Structure element attribute setters: three new helpers fill out the per-element PDF text-string attributes that pair with v2.88.0 /Alt + /ActualText and v2.95.0 /ID. THotPDF.SetStructureElementLanguage (Elem, BCP-47 Lang) writes PDF 1.7 §14.9.2 /Lang, overriding the document-default language for content beneath this element until another /Lang override applies — typical use is quoted passages in another language or sections of mixed-script content. THotPDF.SetStructureElementExpansionText (Elem, ExpansionText) writes §14.9.5 /E, the abbreviation expansion read by screen readers in place of an acronym ('NASA' → 'National Aeronautics and Space Administration') and the value harvested by content extraction. THotPDF.SetStructureElementTitle (Elem, Title) writes §14.7.5.2 /T, the human-readable title that distinguishes the element from same-role siblings in the structure tree (Acrobat's Tags pane displays /T next to the role name). All three are simple setters with empty-value no-op semantics, nil-Elem raises, and are allowed in every PDF/A and PDF/X level since the keys are structural metadata.

2026-05-22 Version 2.119.39

  • AcroForm form-field trigger family completion: three new helpers join AttachFieldKeyStrokeAction (v2.119.37) to close PDF 1.7 §12.7.5.3 Table 246. THotPDF.AttachFieldFormatAction installs /AA /F (format event, fires when the field's display value needs reformatting after a commit — typical use is to apply currency, date, or thousands-separator masks via AFNumber_Format / AFDate_FormatEx). THotPDF.AttachFieldValidateAction installs /AA /V (validate event, fires after the user commits a new value — typical use is to range-check or regex-validate input via event.rc := false). THotPDF.AttachFieldCalculateAction installs /AA /C (calculate event, fires when any dependency field changes per the Catalog /AcroForm /CO calculation order — typical use is rolling up totals or computing taxes). All four wrappers share the same FFormFields /T lookup, idempotent last-call-wins replacement semantics, and PDF/A + PDF/X JavaScript guards. Other trigger keys on the same /AA dict (/E /X /D /U /Fo /Bl annotation triggers and the three sibling form-field triggers) are preserved untouched. Internal refactor: AttachFieldKeyStrokeAction now delegates to a shared _InstallFieldTriggerJSAction helper; existing v2.119.37 callers see byte-identical exception messages.

2026-05-22 Version 2.119.38

  • RegisterUnicodeTTF now captures Supplementary Multilingual Plane (SMP, U+10000-U+10FFFF) codepoint-to-glyph mappings from the loaded font's cmap format 12 subtable. Previous releases silently dropped SMP entries because the codepoint-to-glyph lookup was a BMP-sized array; v2.119.38 adds a parallel sparse list (FUnicodeCpToGidSMP) populated alongside the BMP array. The new public method GetUnicodeGlyphForCodepoint(Cp) returns the glyph ID for any Unicode codepoint up to U+10FFFF (binary-searches the SMP list for codepoints above the BMP), or 0 (.notdef) when the codepoint is not mapped in the loaded font's cmap. This is a capability-only slice: the producer-side hex encoding pipeline, /CIDToGIDMap stream, and ToUnicode CMap emit remain BMP-anchored, so writing an SMP character into a PDF text stream still requires callers to emit UTF-16BE surrogate pairs themselves. Full surrogate-aware text stream emit is reserved for a future revision. Format 4-only fonts get an empty SMP list as expected.

2026-05-22 Version 2.119.37

  • New AcroForm keystroke trigger helper: THotPDF.AttachFieldKeyStrokeAction(FieldName, JavaScriptBody) locates the named form widget by its /T value and installs an /AA /K JavaScript action so PDF readers fire the script on every keystroke during text entry. Use cases include realtime input validation (numeric / date / regex), input masking, and calculated-field updates triggered by edits in another field. The helper is idempotent: repeated calls replace the /K entry while preserving any other trigger keys (/F format, /V validate, /C recalc, /Bl blur, /Fo focus, etc.) already on the same /AA dict. PDF/A all levels (ISO 19005-1 §6.6.1, -2/-3 §6.5.1) and PDF/X all levels (ISO 15930 prepress) forbid JavaScript actions, so the helper raises immediately with the relevant spec reference when either compliance flag is non-empty. Backed by the PDF 1.7 §12.6.3 trigger event dictionary + §12.7.5.3 Table 246 form-field /K key.

2026-05-22 Version 2.119.36

  • BeginTaggedContent now accepts four optional PDF 1.7 §14.8.4 Table 322 marked-content sequence properties (Lang / Alt / ActualText / ExpansionText) and emits them as a single inline BDC properties dict together with the existing MCID entry. Lang carries the BCP-47 natural-language tag for the run (used by screen readers and language-aware text extraction); Alt is the alternate description for non-textual or decorative runs; ActualText is the replacement text used by content extraction and copy-paste in place of the visual glyph sequence (essential for ligatures and stylised glyphs where the visible character differs from the underlying semantic text); ExpansionText carries the /E key that expands abbreviations. Empty parameters skip the corresponding entry so the inline dict only contains keys the caller explicitly populates, and the legacy two-argument call path remains byte-identical with v2.119.35 output. PDF literal string escaping (parentheses and backslash) is automatic; for non-ASCII payloads (CJK ActualText, Indic Alt, etc.) callers pre-encode their bytes as UTF-16BE with the U+FEFF BOM and pack the result into the AnsiString argument. The matching low-level page-stream API BeginMarkedContentMCIDProps is also exposed for callers that need direct BDC control without the high-level StructElem wiring.

2026-05-22 Version 2.119.35

  • Arabic Extended-A core 9-letter Persian / Urdu subset now participates in the v2.85.0 producer-side four-position shaper. With AutoShapeArabic enabled, the following letters are mapped to their U+FB50-U+FBFF Arabic Presentation Forms-A glyphs based on the same joining-context algorithm used for the basic Arabic block: PEH U+067E, TCHEH U+0686, JEH U+0698, KEHEH U+06A9, GAF U+06AF, FARSI YEH U+06CC (Persian core), plus TTEH U+0679, DDAL U+0688, RREH U+0691 (Urdu retroflex core). Dual-joining letters expose the full isolated / final / initial / medial set; right-joining letters (JEH / DDAL / RREH) expose only isolated / final per the Arabic shaping model. Other Arabic Extended-A letters (ALEF WASLA U+0671, NOON GHUNNA U+06BA, HEH variants U+06C0-U+06C3, full Arabic Supplement U+0750-U+077F) remain pass-through and are deferred to a future revision. Non-Persian / Urdu Arabic text remains byte-identical with v2.119.34 output.

2026-05-22 Version 2.119.34

  • PDF name handling now decodes ISO 32000-1 #XX escape sequences when importing or copying PDF objects. Resource names, spot color names, structure role names, and other name objects such as /PANTONE#20216#20CVC now round-trip through HotPDF as their intended byte values instead of being copied back out with a double-escaped number sign.

2026-05-22 Version 2.119.33

  • PDF date handling now follows ISO 32000-1 date-string rules more closely. Document information, XMP-derived dates, and signature timestamps use the caller-supplied TDateTime instead of silently substituting the current clock time, while the parser accepts standard optional date fields and timezone suffixes without corrupting the local date/time fields.
  • PDF import filter handling now has explicit ASCIIHexDecode, ASCII85Decode, and RunLengthDecode helpers, and the LZWDecode default EarlyChange value now matches the PDF default of 1. This improves compatibility with imported or copied pages that use legacy PDF stream filters.

2026-05-21 Version 2.119.32

  • Arabic LAM-ALEF mandatory ligature folding now lands inside the v2.85.0 producer-side shaper. With AutoShapeArabic enabled, any sequence of U+0644 LAM (in raw form or any of its four shaped forms FEDD-FEE0) followed by one of the four ALEF variants (MADDA U+0622, HAMZA-above U+0623, HAMZA-below U+0625, plain U+0627; in raw or post-shape form) collapses into a single ligature glyph from the U+FEF5-U+FEFC block. The connected-vs-isolated ligature form is selected based on whether the LAM has been shaped to a final (FEDE) or medial (FEE0) form by the four-position walker. Other Arabic mandatory ligatures (Allah U+FDFB and decorative honorific ligatures) still require a full GSUB engine and remain out of scope for the static-table shaping model. Non-LAM-ALEF text and AutoShapeArabic-off callers remain byte-identical with v2.119.31 output. Three AcroForm Unicode /AP helpers (single-line, multi-line, comb) inherit the change automatically through the shared _ApplyArabicShaping entry point.

2026-05-21 Version 2.119.31

  • New DevExpress ExpressPrinting System export adapter: dxHotPDFExportReportLinkToFile / dxHotPDFExportReportLinkToStream take any TBasedxReportLink (the abstract link family that bridges cxGrid / cxRichEdit / cxScheduler / cxPivotGrid and similar printable components to TdxComponentPrinter) and pipe each rendered page through HotPDF instead of DevExpress's own dxPSExportToPDF emitter. The adapter mirrors DevExpress's internal export loop: RebuildReport → for each page → PaintPage onto a TMetafileCanvas → ShowEnhancedMetafile into HotPDF. Options record exposes Title / Author / Subject / Keywords / PDFVersion / Compression / RenderDPI. Same metafile-bridge limitations as the FastReport / QuickReport / ReportBuilder adapters: no AcroForm fields, no outlines, no PDF/A / PDF/X gates. Adapter lives in Lib/Addons/DevExpress/ and is not in the main HotPDF*.dpk.

2026-05-20 Version 2.119.30

  • New ReportBuilder device class: TppHotPDFDevice descends from TppGraphicsDevice so it plugs into ReportBuilder's standard publisher → device chain when assigned to TppReport.PrinterDevice or a TppPublisher.Device. Each page is rendered onto a transient TMetafileCanvas (created in BeforeRenderPage, finalised in AfterRenderPage), captured as a TMetafile, and routed through HotPDF's EMF importer (HPDFEmf.ShowEnhancedMetafile). Properties expose Title / Author / Subject / Keywords / PDFVersion / CompressionLevel / Compressed; SetOutputStream routes PDF bytes to a caller-supplied TStream. Same metafile-bridge limitations as the FastReport / QuickReport adapters: no AcroForm fields, no outlines, no PDF/A / PDF/X gates. Adapter lives in Lib/Addons/ReportBuilder/ and is not in the main HotPDF*.dpk.

2026-05-20 Version 2.119.29

  • New QuickReport export filter: TQRHotPDFExportFilter descends from TQRExportFilter so it plugs into the regular MyReport.ExportToFilter(MyFilterInstance) workflow with no QuickReport core changes. The adapter harvests the per-page TMetafiles that QuickReport already caches in QRPrinter.PageList and feeds each through HotPDF's EMF importer (HPDFEmf.ShowEnhancedMetafile). Properties expose Title / Author / Subject / Keywords / PDFVersion / CompressionLevel / Compressed; SetOutputStream routes the PDF bytes to a caller-supplied TStream instead of disk. Same metafile-bridge limitations as the FastReport adapter: no AcroForm fields, no outlines, no PDF/A / PDF/X gates. Adapter lives in Lib/Addons/QuickReport/ and is not in the main HotPDF*.dpk.

2026-05-20 Version 2.119.28

  • New FastReport 4 / FastReport VCL export adapter: TfrxHotPDFExport descends from TfrxCustomExportFilter so it plugs into the regular MyReport.Export(MyExportInstance) workflow with no FastReport core changes. The adapter routes each prepared page through HotPDF's EMF importer (via FastReport's TfrxPreviewPages.DrawPage → TMetafileCanvas → HotPDF.ShowEnhancedMetafile), so vector + raster page content rendered by FastReport's standard layout engine is preserved with no per-object emit code. Properties expose Title / Author / Subject / Keywords / PDFVersion / CompressionLevel / RenderDPI for control over the resulting PDF. Limitations: no AcroForm fields, no outlines, no PDF/A / PDF/X gates (those need a per-object emit pipeline, planned for a future revision). The adapter ships in Lib/Addons/FastReport4/ and is NOT in the main HotPDF*.dpk; users add it to their own project alongside the matching FastReport package.

2026-05-20 Version 2.119.27

  • New end-to-end PFX-based PDF signing: THotPDF.SignPDFWithPFX(InputPDFPath, OutputPDFPath, PFXFilePath, Password) loads an unsigned PDF that already contains an AddSignedSignatureField placeholder, parses the PFX / PKCS#12 file, builds a complete CMS SignedData (RFC 5652) over the /ByteRange-covered bytes, and writes the signed PDF in one call. A TStream-based overload is also provided for in-memory workflows. The PDF /Contents budget must be large enough to hold the CMS DER hex; default 8192 bytes covers 1024 / 2048-bit RSA. PBES2 + AES-256-CBC PFX files only (the v2.119.26 PKCS#12 parser limit).
  • New HPDFCMS unit: CMS SignedData builder. HPDFCMSBuildSignedData composes ContentInfo → SignedData → SignerInfo with id-data detached content, contentType + messageDigest + signingTime signed attributes (sorted by DER ascending per RFC 5652 §5.4), IssuerAndSerialNumber signer identifier extracted from the X.509 cert, RSA + SHA-256 signature algorithm, and the X.509 cert wrapped in [0] IMPLICIT certificates. Also exposes HPDFCMSSHA256ByteRanges for streamed two-window digest and HPDFCMSBytesToHex for /Contents placeholder injection. Closes the in-library signing stack started with HPDFASN1 (v2.119.23) + HPDFRSA (v2.119.24) + HPDFCrypt extensions (v2.119.25) + HPDFPFX (v2.119.26).

2026-05-20 Version 2.119.26

  • New HPDFPFX unit: PKCS#12 / PFX container parser. Reads .pfx / .p12 files from OpenSSL ≥ 3.0, Windows 11+ certutil, and macOS Keychain Access, decrypts PBES2 (PBKDF2-HMAC-SHA-256 + AES-256-CBC) SafeBag payloads, and extracts the X.509 certificate DER + RSA private key parameters. Returns a THPDFPFXKeyMaterial record with CertDER, Modulus, PublicExponent, PrivateExponent that downstream CMS / signing code can consume directly. Wrong password is detected via the PKCS#7 unpadding failure that follows a bad AES key. Legacy PBE-SHA1-3DES files raise with a clear migration hint to re-export with `-keypbe AES-256-CBC -certpbe AES-256-CBC`.

2026-05-20 Version 2.119.25

  • New crypto primitives: AES-256 inverse cipher with AES256DecryptBlock + AES256CBCDecryptPKCS7 + AES256CBCDecryptNoPad (FIPS-197 §5.3, complementing the existing encryption helpers), HMAC-SHA-256 (RFC 2104), and PBKDF2-HMAC-SHA-256 (RFC 8018 §5.2). Verified against RFC 4231 HMAC vectors and standard PBKDF2-SHA-256 reference outputs. Together with the v2.119.23 ASN.1 decoder and v2.119.24 RSA primitives, this completes the cryptographic foundation needed for the PBES2-AES-256 SafeBag decryption path used by modern (OpenSSL ≥ 3.0 / Windows 11+) PKCS#12 / PFX files.

2026-05-20 Version 2.119.24

  • New HPDFRSA unit: multi-precision integer arithmetic + RSA modular exponentiation + PKCS#1 v1.5 EMSA encoding. Big enough to sign with real-world 2048 / 4096-bit RSA keys. Together with v2.119.23's ASN.1 decoder this completes the cryptographic primitives needed for the upcoming in-library PFX-based PDF signing pipeline. No user-facing API uses this yet — it is internal infrastructure waiting for the PKCS#12 + CMS layers.

2026-05-20 Version 2.119.23

  • New HPDFASN1 unit: a compact BER/DER decoder for the ASN.1 subset HotPDF needs to read PKCS#12 keystores, X.509 certificates, and CMS/PKCS#7 SignedData containers. Exposes THPDFASN1Node (parsed Tag-Length-Value descriptor), THPDFASN1ParseNode / ParseAt, Content / ToInteger / ToBigInt / ToOID / ToString accessors, and FirstChild / NextSibling / Children iterators. Rejects indefinite-length BER form and high-tag-number (>= 31) encodings explicitly, mirroring DER's strict subset. This is internal infrastructure for the in-library PFX signing pipeline arriving in upcoming releases — no user-facing API depends on it yet.

2026-05-20 Version 2.119.22

  • New PolyLine + Polygon annotation overloads with full metadata: THPDFPage.AddPolylineAnnotation and AddPolygonAnnotation gain two new overloads that accept an array of THPDFCurrPoint vertices and an optional THPDFPolyExtras record. The extras record exposes the ISO 32000-1 §12.5.6.9 optional entries — /IC interior colour, /BE cloudy border (besSolid / besCloudy with /I intensity), /IT intent (PolyLineDimension / PolyLineFlight / PolygonCloud / PolygonDimension), and /LE line-ending styles for PolyLine. Use HPDFDefaultPolyExtras() for a zero-initialised starting record. The new overloads validate that at least two vertices are supplied and auto-bump the document version (PDF 1.5 / 1.6 / 1.7 depending on intent). The previous interleaved-Single overloads continue to compile unchanged.

2026-05-20 Version 2.119.21

  • New Line annotation extras: AddLineAnnotation gets a third overload accepting a THPDFLineExtras record that controls the optional PDF 1.6/1.7 §12.5.6.7 Table 175 entries — /IC interior colour (filled arrowheads, diamonds, etc.), /LL leader-line length, /LLE leader extension, /LLO leader offset (PDF 1.7), /Cap caption flag with /CP placement (/Inline or /Top, PDF 1.7) + /CO caption offset, and /IT intent (LineArrow / LineDimension). Use HPDFDefaultLineExtras() to start from a zero-initialised record. The new overload auto-bumps the document version to PDF 1.6 (or 1.7 when /LLO / /CP / /CO are requested). The two earlier overloads continue to emit byte-identical output, and only the entries the caller actually asks for are written.

2026-05-20 Version 2.119.20

  • New baShow / baImportData push-button actions: THPDFButtonAction now includes baShow (mirror of baHide that emits /H false to re-show a hidden widget) and baImportData (/S /ImportData /F filespec to load FDF form data on click, ISO 32000-1 §12.7.5.4). baShow is spec-allowed under PDF/A and PDF/X; baImportData is blocked under both per ISO 19005-1 §6.6.1 and ISO 15930 prepress respectively.
  • New multi-field Hide/Show overload: THPDFPage.AddPushButtonWithHideAction(FieldName, Caption, Targets, Hide, Rectangle, Flags) accepts an array of target field names and an explicit Hide boolean so a single button can toggle visibility on multiple widgets in one click. Empty Targets array raises. PDF/A and PDF/X both allow this overload (widget visibility only).

2026-05-20 Version 2.119.19

  • New SubmitForm /Flags user control: THPDFPage.AddPushButtonWithSubmitAction(FieldName, Caption, URL, Rectangle, SubmitFlags) exposes the full ISO 32000-1 §12.7.5.2 Table 237 flag set via the new THPDFSubmitFormFlags set type. Callers can now mix sffExportFormatHTML / sffXFDF / sffSubmitPDF (submission format), sffGetMethod (HTTP GET vs POST), sffIncludeAnnotations, sffSubmitCoordinates, sffCanonicalFormat, sffIncludeNoValueFields, sffIncludeAppendSaves, sffExclNonUserAnnots, sffEmbedForm and others, replacing the hardcoded /Flags 0 (FDF + POST) of the legacy baSubmitURL path. Existing AddPushButtonWithAction(.., baSubmitURL, ..) continues to emit /Flags 0 for byte-level backward compatibility. Same PDF/A / PDF/X gates as baSubmitURL: allowed under PDF/A, blocked under PDF/X.

2026-05-20 Version 2.119.18

  • Extended push-button action types: THPDFButtonAction now includes baNamed and baHide. baNamed emits a /S /Named /N <name> action (ISO 32000-1 §12.6.4.11) suitable for the four standard navigation commands NextPage / PrevPage / FirstPage / LastPage (PDF/A allows all four per ISO 19005-1 §6.6.1) and Acrobat extensions like Print and SaveAs. baHide emits a /S /Hide /T <field> /H true action (§12.6.4.10) that hides the named form field when the button is clicked. Both action types are spec-allowed under PDF/A and PDF/X; only baJavaScript / baResetForm / baSubmitURL retain their existing compliance gates.

2026-05-20 Version 2.119.17

  • New named-destination registry: THotPDF.RegisterNamedDestination(Name, PageIndex, FitMode, ...) registers a symbolic destination in the Catalog /Names /Dests name tree (ISO 32000-1 §12.3.2.3). GoTo / GoToR actions and outline /Dest entries can then reference a destination by name instead of hard-coding page numbers, which keeps cross-references stable across page insertion / deletion / reorder. New THPDFDestinationFitMode enum covers all eight spec fit modes (XYZ, Fit, FitH, FitV, FitR, FitB, FitBH, FitBV). The method raises on empty Name, out-of-range PageIndex, and duplicate Name; auto-bumps the document version to PDF 1.2 when needed. No PDF/A / PDF/X gate — navigation aids are spec-allowed across all profiles.

2026-05-20 Version 2.119.16

  • New line annotation /LE line-ending styles: THPDFPage.AddLineAnnotation now has an overload accepting begin/end line-ending styles. The full ISO 32000-1 §12.5.6.7 Table 176 set is exposed via the new THPDFLineEndingStyle enum: None, Square, Circle, Diamond, OpenArrow, ClosedArrow, Butt, ROpenArrow, RClosedArrow, Slash. Line annotations can now render as proper arrows, dimensioning markers, or pointer callouts inside Adobe Acrobat / Foxit without the host having to manually draw arrowheads on the page. The legacy four-parameter overload still works byte-identically.

2026-05-20 Version 2.119.15

  • New outline (bookmark) styling: THPDFDocOutlineObject now exposes Color, Bold, and Italic properties (ISO 32000-1 §12.3.3 Table 153 /C and /F). The outline panel can now render bookmarks in colour and with bold or italic emphasis, useful for grouping chapters, marking incomplete or attention-needed sections, and matching brand styling. Color defaults to clNone (no /C entry emitted, byte-identical with prior output); Bold and Italic default to false. Setting properties after AddChild updates the live outline dictionary in place, and setting Color back to clNone or both Bold and Italic to false removes the /C / /F entries.

2026-05-20 Version 2.119.14

  • New document-level named JavaScript registry: THotPDF now exposes RegisterDocumentJavaScript(Name, Body). Each registered entry is serialised into the Catalog /Names /JavaScript name tree (ISO 32000-1 §7.7.4.4 + §12.6.4.16) as an indirect /S /JavaScript /JS dict. AcroForm widget actions and document-open actions can invoke the registered function by name through the viewer's JavaScript engine (Acrobat, Foxit). Useful for sharing a library of validation, formatting, or calculation helpers across many widgets without duplicating the JS source in each /A action dictionary. The method raises under any PDFACompliance level (ISO 19005-1 §6.6.1) and any PDFXCompliance level (ISO 15930 prepress workflows), and rejects empty Name / Body as well as duplicate Name registration.

2026-05-20 Version 2.119.13

  • New page thumbnail support: THPDFPage now exposes SetPageThumbnail(ImageIndex) and ClearPageThumbnail. PDF viewers (Acrobat, Foxit, browser viewers) use the page /Thumb entry (ISO 32000-1 §12.3.4) to render a pre-built preview in their navigation pane without rasterising the page, which is especially useful for image-heavy archives where the viewer's own rasteriser would be slow. The helper re-uses any image registered through AddImage / AddImageFromFile, so callers can prepare a downsampled preview bitmap and attach it with a single call; SetPageThumbnail(-1) or ClearPageThumbnail removes a previously attached thumbnail. Out-of-range image indices raise a descriptive exception.

2026-05-20 Version 2.119.12

  • PDF/A compliance fix: AddPushButtonWithAction now raises when baJavaScript or baResetForm action types are used under any PDFACompliance level (PDF/A-1/2/3). ISO 19005-1 §6.6.1 / ISO 19005-2 §6.5.1 / ISO 19005-3 §6.5.1 prohibit JavaScript, ResetForm, and ImportData actions across all PDF/A levels. baSubmitURL (SubmitForm), baURI, and baNone remain allowed under PDF/A — SubmitForm is in the §6.6.1 allowed-actions list. Previously, push-button widgets could carry forbidden actions even with PDFACompliance enabled, since the v2.119.5 guard only covered the Catalog /AA and document-level OpenAction paths.
  • PDF/X compliance fix: AddPushButtonWithAction now raises when baJavaScript, baResetForm, or baSubmitURL action types are used under any PDFXCompliance level. ISO 15930 prepress workflows exclude interactive scripting, form mutation, and form submission to external endpoints.

2026-05-20 Version 2.119.11

  • PDF/A compliance fix: PDF/A annotation type guards completion. AddScreenAnnotation, Add3DAnnotation, and AddRichMediaAnnotation now raise when PDFACompliance is enabled at any level (PDF/A-1/2/3). ISO 19005-1 §6.5.2 / ISO 19005-2 §6.6.1 / ISO 19005-3 §6.6.1 prohibit Screen, 3D, and RichMedia (multimedia / Adobe Extension Level 3) annotation subtypes across all PDF/A levels. The same guards are mirrored under PDFXCompliance — ISO 15930 prepress workflows reject multimedia subtypes.
  • PDF/A-1 compliance fix: AddWatermarkAnnotation and AddRedactAnnotation now raise when PDFACompliance is set to PDF/A-1. Watermark (PDF 1.6) and Redact (PDF 1.7) are post-PDF-1.4 annotation subtypes incompatible with PDF/A-1's PDF 1.4 base (ISO 19005-1 §6.5.2). PDF/A-2 and PDF/A-3 (PDF 1.7 base) continue to allow both annotation types.

2026-05-20 Version 2.119.10

  • PDF/A compliance fix: AddImageWithSMask and AddImageWithSMask32 now raise when PDFACompliance is set to PDF/A-1. ISO 19005-1:2005 §6.4 prohibits all transparency effects; an image /SMask stream activates soft-mask transparency compositing. Pre-composite the image onto a background colour and pass the result to AddImage(), or use PDF/A-2 or later for transparency support. PDF/A-2 and PDF/A-3 continue to allow image soft-mask transparency.

2026-05-20 Version 2.119.9

  • PDF/A compliance fix: SetTransparencyGroup and SetTransparencyGroupICC now raise when PDFACompliance is set to PDF/A-1. ISO 19005-1:2005 §6.4 prohibits all transparency effects; page-level transparency groups (page /Group /S /Transparency dict) activate the transparency compositing model for the entire page, which is a direct §6.4 violation. PDF/A-2 and PDF/A-3 continue to allow page transparency groups.

2026-05-20 Version 2.119.8

  • PDF/A compliance fix: RegisterHalftoneState now raises when PDFACompliance is active and a custom halftone dictionary is supplied (i.e., HTDefaultName = false). ISO 19005-1:2005 §6.2.9 and ISO 19005-2:2011 / ISO 19005-3:2012 §6.3.7 permit only the /Default name-literal for ExtGState /HT; custom halftone dictionaries (Type 1, 5, 6, 10, 16) are forbidden. Passing HTDefaultName=True to reset to the default halftone continues to work under all PDF/A levels.
  • PDF/A compliance fix: RegisterSoftMaskState now raises when PDFACompliance is set to PDF/A-1 and a custom soft-mask dictionary is supplied (SMaskDict parameter). ISO 19005-1:2005 §6.4 prohibits all transparency effects; a non-None /SMask value activates soft-mask transparency. Passing SMaskNone=True to reset soft masks is still permitted. PDF/A-2 and PDF/A-3 continue to allow soft-mask transparency.

2026-05-20 Version 2.119.7

  • PDF/A compliance fix: AddDocumentAttachment (document-level file embedding via Catalog /Names /EmbeddedFiles) now raises when PDFACompliance is active for PDF/A-1 or PDF/A-3. ISO 19005-1:2005 §6.1.11 explicitly forbids the /EmbeddedFiles key in the document Names dictionary under PDF/A-1. For PDF/A-3, document attachments must be registered through AddPDFA3AssociatedFile() to include the required Catalog /AF array and /AFRelationship key per ISO 19005-3:2012 Annex E. PDF/A-2 continues to permit document attachments provided the attached files are themselves PDF/A conformant.

2026-05-20 Version 2.119.6

  • PDF/A compliance fix: using PDF standard fonts (Arial, Helvetica, Times New Roman, Courier New, Symbol, ZapfDingbats) with StandardFontEmulation enabled now raises a descriptive exception when PDFACompliance is active. ISO 19005-1:2005 §6.3 and ISO 19005-2:2011 / ISO 19005-3:2012 §6.3 require all fonts to be embedded in conforming files; the standard-font emulation path emits an unembedded font reference, which is a direct §6.3 violation. Users should set StandardFontEmulation := false or switch to RegisterUnicodeTTF() with actual font files for PDF/A output.
  • PDF/A compliance fix: non-standard fonts are now always embedded when PDFACompliance is active, regardless of the FontEmbedding property setting. The FontEmbedding = false optimization is silently overridden in PDF/A mode to satisfy the §6.3 all-fonts-embedded requirement.

2026-05-20 Version 2.119.5

  • PDF/A compliance fix: JavaScript actions and Catalog-level /AA (Additional Actions) are now blocked when PDFACompliance is active. ISO 19005-1:2005 §6.6.1/§6.6.2 and ISO 19005-2:2011 / ISO 19005-3:2012 §6.5.1/§6.6.2 prohibit all JavaScript action types and forbid the /AA key in the Catalog dictionary. Calling SetActionScript with any PDFACompliance value now raises a descriptive exception. As a secondary defense, the OpenAction JavaScript and Catalog /AA emission paths in EndDoc are also gated to silently skip output when PDFACompliance is active, ensuring no non-conformant keys are written even via internal paths.

2026-05-20 Version 2.119.4

  • PDF/A compliance fix: AcroForm /NeedAppearances is now forced to false when PDFACompliance is active, regardless of the AutoFormAppearances setting. ISO 19005-1:2005 §6.9 and ISO 19005-2:2011 / ISO 19005-3:2012 §6.4 require that this key be absent or false in conforming files. Previously, if AutoFormAppearances was disabled while PDFACompliance was active, /NeedAppearances would be set to true, producing a non-conformant file.

2026-05-20 Version 2.119.3

  • PDF/A compliance fix: embedded CIDFont subsets now include a /CIDSet stream on their FontDescriptor when PDFACompliance is active. ISO 19005-1:2005 §6.3.5 and ISO 19005-2:2011 / ISO 19005-3:2012 §6.2.11 require that CIDFont subsets carry a /CIDSet bitstream indicating which CID values are present in the embedded font program. HotPDF uses Identity-H encoding where CID equals the Unicode codepoint, so the CIDSet is built directly from the set of codepoints used in the document. The bitstream is FlateDecode-compressed and attached to the existing FontDescriptor as an indirect stream object. This fixes a previously undetected gap in all PDF/A levels (the earlier compliance audit incorrectly stated this was already in place).

2026-05-20 Version 2.119.2

  • PDF/A-2 and PDF/A-3 compliance fix: annotation appearance dictionary (/AP /N) now automatically emitted for non-Link, non-Popup annotation types (TextNotes, FreeText, Line, Square, Circle, Stamp, FileAttachment) when PDFACompliance is set to a PDF/A-2 or PDF/A-3 level. ISO 19005-2:2011 §6.3.3 and ISO 19005-3:2012 §6.3.3 require every such annotation to have at least one appearance dictionary (with the N-entry form XObject). A minimal empty Form XObject is emitted to satisfy the structural requirement; most PDF viewers use their own built-in rendering for standard annotation types regardless of the /AP stream content. Link and Popup annotations are explicitly exempted by the standard and remain unchanged. This requirement does not apply to PDF/A-1 (ISO 19005-1 §6.5.3 only constrains /AP format when present, not its presence).

2026-05-20 Version 2.119.1

  • PDF/A compliance fix: all non-Widget annotation types (text notes, stamps, lines, shapes, hyperlinks, GoTo links, GoToR links, URI links) now automatically set the /F Print flag (bit 3 = 1, value 4) when PDFACompliance is active. ISO 19005-1:2005 §6.5.3 and ISO 19005-2:2011 / ISO 19005-3:2012 §6.3.2 mandate that every annotation dictionary shall contain an /F key with the Print bit set. Previously only Widget annotations (form fields) had this flag; all other annotation types omitted it, making produced files non-conformant.
  • PDF/A-1 compliance fix: optional content groups (layers) are now blocked when PDFACompliance is set to a PDF/A-1 level ('A' or 'B'). ISO 19005-1:2005 §6.1.13 forbids the /OCProperties key in the Catalog dictionary; calling RegisterOptionalContentGroup under PDF/A-1 now raises a descriptive exception directing callers to use PDF/A-2 or later if optional content is required. The EndDoc /OCProperties emission is also gated to prevent silent non-conformance even if OCGs were somehow registered.

2026-05-20 Version 2.119.0

  • XAdES-in-PDF container support per ETSI EN 319 142-2 V1.2.0 §6.2 (B/C/D series closure 3 of 3). Embeds caller-supplied XAdES-signed XML bytes as a PDF EmbeddedFile, registers the Filespec in Catalog /AF array, sets /AFRelationship per the caller-specified PDF 2.0 enumeration. Producer-side wrapper only — the actual XAdES signature construction (XML-DSig + ETSI EN 319 132-1 QualifyingProperties + RFC 3161 timestamp embedded in xades:UnsignedSignatureProperties) is the caller's XML cryptographic library responsibility (Apache Santuario, .NET SignedXml, libxmlsec, Saxon with XAdES extensions, etc.).
  • New method `THotPDF.AddXAdESAssociatedFile(FileName, XMLBytes, Description, MimeType, Relationship)`. Defaults: MimeType `'application/xml'` (also commonly `'application/vnd.etsi.asic-e+zip'` for ASiC-E archives), Relationship `'Source'` (the XAdES-signed XML is the document's primary content). Returns the indirect Filespec dict so callers can attach additional metadata or cross-reference from custom Catalog entries.
  • Independent of PDF/A: unlike v2.108's `AddPDFA3AssociatedFile` (which requires PDFACompliance='3*'), this helper has no PDF/A gate — XAdES-in-PDF is its own profile family per PAdES Part 2 V1.2.0 §6.
  • Critical bug fix in `_EscapePDFNameBytes` (PDF 1.7 ISO 32000-1 §7.3.5 + Table 2). The previous escape only converted bytes outside [0x21..0x7E] and the `#` char; PDF delimiters (`/ ( ) < > [ ] { } %`) are spec-required to be `#XX` escaped inside names but were left literal. Names like `/Subtype /application/xml` were therefore parsed by strict readers as two separate name tokens (/application followed by /xml), breaking EmbeddedFile /Subtype recognition. v2.119 escapes all 10 delimiters per spec; non-delimiter names (the overwhelming majority of HotPDF emissions) remain byte-identical. PDF/A-3 Annex E (v2.108) MIME types now emit correctly (`/application#2Fxml` instead of broken `/application/xml`).
  • PAdES B/C/D series closed at 3 commits: v2.117 (Extended profiles E-BES / E-EPES / E-LTV per Part 2 §5) + v2.118 (Seed Values per ISO 32000-1 §12.7.5.5 + Part 2 §4.2.6) + v2.119 (XAdES-in-PDF + delimiter escape fix per Part 2 §6.2). Combined with v2.109 - v2.116, HotPDF now covers the full PAdES producer scope including baseline profiles, extended profiles, document timestamp, DSS validation info, ESIC extensions, signing constraints via Seed Values, and XAdES-in-PDF container.
  • Win32 + Win64 clean compile; v2.108 - v2.118 baselines all recompile unchanged. New smoke smoke_pades_xades exercises 2 positive paths (XAdES-signed XML as /AFRelationship /Source + ASiC-E archive as /Data) plus 4 rejection paths (empty FileName, empty XMLBytes, empty MimeType, invalid Relationship). Python verifier walks both Filespecs end-to-end including the `/Subtype /application#2Fxml` and `/Subtype /application#2Fvnd.etsi.asic-e+zip` escape forms. v2.108 PDF/A-3 / PAdES / PDF/X smokes all re-run clean.

2026-05-20 Version 2.118.0

  • PAdES Seed Values per ISO 32000-1 §12.7.5.5 + ETSI EN 319 142-1 V1.2.1 §5.5 + ETSI EN 319 142-2 V1.2.0 §4.2.6 (B/C/D series step 2 of 3). A Seed Value (SV) dictionary attached to a signature field constrains what the consumer reader's signing tool is allowed to choose for that specific field — which handler, which SubFilter, which digest method, whether revocation info is mandatory, what minimum PDF version is required.
  • New `THotPDF.AttachPAdESSeedValue(FieldName, SubFilters, DigestMethods, ForceSubFilter, ForceDigestMethod, AddRevInfo, MinPDFVersion, ForceMinPDFVersion)` method. Looks up the named signature field in the AcroForm fields list (must already have been created by an earlier AddPAdESSignatureField or AddDocumentTimestampSignature call) and attaches a /SV sub-dictionary with the caller-supplied constraints.
  • Seed Value entries emitted (per Table 234):
    • `/Type /SV`
    • `/SubFilter` array of names — e.g. [/ETSI.CAdES.detached]
    • `/DigestMethod` array of names — e.g. [/SHA256 /SHA384 /SHA512]
    • `/AddRevInfo` boolean — signing tool must include OCSP/CRL in CMS
    • `/V` number — minimum PDF version (e.g. 1.7)
    • `/Ff` integer bit flags (Table 235) — each Force* parameter sets the corresponding bit so the signing tool MUST honour the constraint (bit 2 = force SubFilter, bit 3 = force V, bit 6 = force AddRevInfo, bit 7 = force DigestMethod)
  • Spec-compliance guard rails: PAdES Part 2 V1.2.0 §4.2.6 forbids seed values that would force signing tools to violate the PAdES profile (e.g. specifying PKCS#1 SubFilter). HotPDF does not enforce this on the producer side — the caller is responsible for passing only spec-allowed values (`'adbe.pkcs7.detached'` or `'ETSI.CAdES.detached'` for SubFilter, SHA-256+ for DigestMethod). The helper doc-comment is explicit about the constraint.
  • Win32 + Win64 clean compile; v2.108 - v2.117 baselines all recompile unchanged. New smoke smoke_pades_seedvalue creates a PAdES-B-T field then attaches a full set of Seed Value constraints (SubFilter restricted to ETSI.CAdES.detached, DigestMethod restricted to SHA256/384/512, AddRevInfo true, MinPDFVersion 1.7, all force bits set). Python verifier walks the /SV sub-dict and asserts each entry shape; also exercises the "unknown FieldName" rejection path.
  • Series step 3 (v2.119): XAdES-in-PDF container per Part 2 V1.2.0 §6 closes the B/C/D series. Producer-side this is a thin wrapper over HotPDF's existing EmbeddedFile / Catalog /AF plumbing (v2.108 PDF/A-3 helper) plus the /AFRelationship semantics PAdES Part 2 V1.2.0 §6.2.2 expects for XAdES-signed XML payloads.

2026-05-20 Version 2.117.0

  • PAdES extended profiles per ETSI EN 319 142-2 V1.2.0 §5 (B/C/D series step 1 of 3). The Part 1 baseline (V1.2.1 §6 B-B / B-T / B-LT / B-LTA) defines fixed CMS attribute combinations; Part 2 §5 defines a parallel family of "extended" profiles (E-BES / E-EPES / E-LTV) with higher CMS-attribute optionality for callers needing flexibility beyond baseline (e.g. multi-policy enterprise deployments, custom CAdES profiles within the PAdES envelope).
  • `AddPAdESSignatureField` Profile parameter now accepts three additional values alongside the four baseline names:
    • `'E-BES'` — basic + unambiguous signing-cert binding via signing-certificate ESS attribute (CAdES-BES equivalent within the PAdES envelope). Default 16 KB /Contents budget.
    • `'E-EPES'` — E-BES + explicit signature-policy-identifier signed attribute. Default 16 KB budget; caller typically passes 18-20 KB to fit the policy OID + qualifier bytes.
    • `'E-LTV'` — long-term validation with CMS-attribute-flexible cert chain + revocation embedding. Auto-upsizes to 20 KB minimum (mirrors B-LT).
  • Producer-side wire format is identical for baseline and extended profiles — all seven use SubFilter `ETSI.CAdES.detached`, the same /ByteRange + /Contents sentinel pattern, and the same DSS + ESIC plumbing (v2.110 / v2.116). The level distinction lives entirely in the CMS signed-attribute composition the caller's cryptographic library produces. The Profile string varies only the validation diagnostic and the default /Contents budget; HotPDF does not encode CAdES-BES vs CAdES-EPES vs CAdES-T into the PDF dict itself.
  • Updated PAdES helper doc-comment surfaces caller responsibilities exposed by Part 2 V1.2.0 §4.2: SHA-256+ hash selection (SHA-1 phase-out), single SignerInfo per Sig field, no RFC 5755 attribute certificates, and the E-EPES requirement to include the signature-policy-identifier signed attribute. Caller-domain items remain producer-out-of-scope but the contract is now explicit on the API surface.
  • Win32 + Win64 clean compile; v2.108 - v2.116 baselines all recompile unchanged. smoke_pades_signature extended with 3 new fields (SigEBES / SigEEPES / SigELTV) exercising each new Profile value through the auto-upsize / explicit-budget / default-budget paths. Python verifier extended to walk all 7 fields and asserts the Catalog ESIC extension (v2.116 carry-forward).
  • Step 2 of the B/C/D series: v2.118 adds Seed Values (PDF 1.7 §12.7.5.5 + ETSI EN 319 142-1 V1.2.1 §5.5 + Part 2 V1.2.0 §4.2.6) so PAdES signature fields can declare handler / SubFilter / digest-method constraints that the consumer reader must honour during signing. Step 3: v2.119 adds XAdES-in-PDF container support per Part 2 V1.2.0 §6 (XAdES-signed XML embedded as EmbeddedFile + Catalog /AF association).

2026-05-20 Version 2.116.0

  • PAdES refresh per the latest ETSI normative versions and the eIDAS Implementing Decision: ETSI EN 319 142-1 V1.2.1 (2024-01, published) + ETSI EN 319 142-2 V1.2.0 (2025-03, draft additional profiles + extended-profile family + XAdES-in-PDF) + ETSI TS 119 142-3 V1.1.1 (2016-12, PAdES-DTS Document Time-stamp) + EU 2015/1506 (public-sector recognition). HotPDF v2.109 - v2.111 baselines (B-B / B-T / B-LT / B-LTA + standalone timestamp) continue to satisfy the same wire format; this slice closes two producer-side gaps the new spec versions exposed.
  • DSS dictionary `/Type "DSS"` entry (EN 319 142-1 V1.2.1 §5.4.2.2). The /Type entry is optional but, when present, must be the name `/DSS`. HotPDF now emits it eagerly so strict validators that inspect /Type before walking the rest of the dictionary recognise the structure immediately.
  • ESIC extensions dictionary (TS 119 142-3 §5.1, recommended). New `EnsurePAdESESICExtensions` method idempotently writes the following to the Catalog whenever a PAdES helper is invoked: `<< /Extensions << /ESIC << /BaseVersion /1.7 /ExtensionLevel 1 >> >> >>`. Validators (Adobe Acrobat pre-flight, EU DSS, callas pdfaPilot) use this entry to identify the document as a PAdES container declaring PDF 1.7 + ESIC level 1 extensions.
  • Critical bug fix in `EnsurePAdESDSS` round-trip path. Pre-v2.116 the helper re-resolved the DSS dictionary on every call via `Catalog.GetIndexedItem(FindValue('DSS'))`, which returns the indirect-link THPDFLink stored in the Catalog instead of the actual DSS dict. The cast to THPDFDictionaryObject silently mis-typed the pointer, causing every subsequent `AddPAdESDSSCertificate` / `AddPAdESDSSOCSP` / `AddPAdESDSSCRL` / `AddPAdESDSSVRI` call after the first to silently skip its array push (the array reference resolved to nil, FindValue returned -1, the if-Idx-less-than-zero-Exit early-out fired). The v2.116 fix caches the actual Dict reference on a `FPAdESDSSDict` field so subsequent calls hit the cache directly. The smoke_pades_dss test now correctly emits 2 certs + 1 OCSP + 1 CRL + 1 VRI entry (previously only the first cert made it into /DSS, with /OCSPs and /CRLs empty arrays).
  • Doc-comment updates on PAdES helpers covering caller responsibilities surfaced by Part 2 V1.2.0 §4.2: SHA-1 phase-out (callers should pick SHA-256+ for CMS), single SignerInfo per PDF Signature (Part 2 §4.2.1 h / Part 1 §4.1 a), RFC 5755 attribute certificates should not be used (Part 2 §4.2 g), and PAdES-DTS documents should not carry VRI dictionaries (TS 119 142-3 §6.3 h — DSS suffices). HotPDF's producer-side wire format already satisfies all four constraints by construction; the comment surface tells callers what their CMS-construction libraries must do.
  • Win32 + Win64 clean compile; smoke_pades_signature / smoke_pades_dss / smoke_pades_doctimestamp + their verifiers all pass. Verifier extensions: smoke_pades_dss now asserts `/Type /DSS` + Catalog `/Extensions /ESIC /BaseVersion /1.7 /ExtensionLevel 1`; smoke_pades_doctimestamp asserts the same ESIC extension and accepts page-Y-flipped zero-area /Rect (`[0 H 0 H]` where H is page height). Audit doc + TechnicalNotes refreshed to reference EN 319 142-1 V1.2.1 / Part 2 V1.2.0 / TS 119 142-3 / EU 2015/1506.

2026-05-20 Version 2.115.0

  • PDF/X (ISO 15930) producer series closure (4/4): forbidden annotation + action types per ISO 15930-1:2001 / ISO 15930-3:2002 / ISO 15930-7:2010 + ISO 32000-1 §12.5.6 / §12.6.4. PDF/X print workflows consume the visible printable content only — multimedia annotations, embedded files, and interactive actions all have no role on the press, so consumer pre-flight tools (Adobe Acrobat, Enfocus PitStop, callas pdfaPilot) reject documents containing them.
  • Forbidden annotation types (rejected under any PDFXCompliance ∈ {X-1a, X-3, X-4}):
    • FileAttachment — print press does not consume embedded files
    • Sound — multimedia has no place in print workflows
    • Movie — same multimedia reasoning
    Each entry point (AddFileAttachmentAnnotation, AddSoundAnnotation, AddMovieAnnotation) now raises with the spec-section diagnostic and the PDFXCompliance value when invoked under any PDF/X profile.
  • Forbidden action types (rejected under any PDFXCompliance): Launch / JavaScript / SubmitForm / ImportData / Movie / Sound / ResetForm. Print-prepress workflows must not trigger any external program — the PDF is consumed only by the press / RIP, never executed. AddLaunchLink now raises under any PDF/X profile (the other actions HotPDF does not expose as public API paths, so they're satisfied automatically).
  • PDF/X series now functionally complete: v2.112 (opt-in + XMP identity + Trapped /Name) + v2.113 (OutputIntent + ICC GTS_PDFX) + v2.114 (transparency gate + PageBox enforcement) + v2.115 (forbidden annot/action). All three industry-standard profiles (X-1a:2001, X-3:2002, X-4:2010) covered. Caller still authors page-level prepress content (correct CMYK targets, font embedding via v2.84 TTF subsetter, TrimBox dimensions matching design, BleedBox when bleed is required) but the structural compliance gates are all in place.
  • Win32 + Win64 clean compile; v2.108 - v2.114 baselines all recompile unchanged. smoke_pdfx_optin extended with 4 new rejection tests (TestRejectFileAttachmentAnnot, TestRejectSoundAnnot, TestRejectMovieAnnot, TestRejectLaunchAction) — each emits the forbidden type under one of the three PDF/X profiles and confirms a clean rejection. Total smoke now exercises 11 scenarios: 3 positive emits + 8 rejection paths.
  • Two parallel multi-version series closed in this group: PAdES (ETSI EN 319 142, v2.109 - v2.111, 3 commits, B-B / B-T / B-LT / B-LTA + standalone timestamp all supported) and PDF/X (ISO 15930, v2.112 - v2.115, 4 commits, X-1a / X-3 / X-4 producer-side structural compliance gates all in place).

2026-05-20 Version 2.114.0

  • PDF/X (ISO 15930) producer series slice 3/4: transparency gate + PageBox enforcement per ISO 15930-1:2001 / ISO 15930-3:2002 / ISO 15930-7:2010 + ISO 32000-1 §14.11.2. The slice covers two independent restrictions that both apply per page; the gate split mirrors the v2.103 PDF/A-1 transparency-and-page-features pattern.
  • Transparency gate (§7.5): RegisterExtGState rejects fill alpha < 1 (/ca), stroke alpha < 1 (/CA), or BlendMode outside {Normal, Compatible} when PDFXCompliance is 'X-1a' or 'X-3'. PDF/X-1a (ISO 15930-1:2001) and PDF/X-3 (ISO 15930-3:2002) mandate PDF 1.3 base, which pre-dates PDF 1.4 transparency. PDF/X-4 (ISO 15930-7:2010, PDF 1.6 base) explicitly permits transparency, so the gate does not fire under X-4. Diagnostic explains the cause and suggests upgrading to X-4 if transparency is needed.
  • PageBox enforcement (§14.11.2): new method `EnsurePDFXPageBoxes` walks every page and requires at least one of /TrimBox or /ArtBox. MediaBox alone is not enough because it represents the design canvas, not the finished page dimensions the print shop needs to know where to trim. Missing PageBoxes raises with the 1-based page index, the spec section citation, and the recommended SetTrimBox call signature. EndDoc PDFXCompliance gate auto-calls EnsurePDFXPageBoxes after the OutputIntent check.
  • Typical caller pattern: after setting PDFXCompliance + Trapped + AddPDFXOutputIntent, every page in the document must include `CurrentPage.SetTrimBox(Left, Bottom, Right, Top)` matching the finished product dimensions (e.g. 0 0 612 792 for US Letter, 0 0 595 842 for A4). BleedBox is recommended but not mandatory.
  • Win32 + Win64 clean compile; v2.108 - v2.113 baselines all recompile unchanged. smoke_pdfx_optin extended with: positive SetTrimBox call in each of the 3 profile emits, missing-TrimBox rejection path, X-1a transparency rejection path (RegisterExtGState with ca=0.5 raises), X-4 transparency-allowed positive path (same call succeeds + ExtGState registered). Python verifier walks every /Type /Page dict and asserts at least one of /TrimBox / /ArtBox is present per page.
  • Remaining PDF/X slice: v2.115 closes the series with forbidden action types (JavaScript / Launch / SubmitForm / ImportData / Movie / Sound), forbidden annot types (Movie / Sound / FileAttachment / Screen), and AcroForm /XFA rejection. Print workflows have no place for those features; consumer pre-flight tools flag them as hard failures.

2026-05-19 Version 2.113.0

  • PDF/X (ISO 15930) producer series slice 2/4: OutputIntent + ICC profile enforcement per ISO 15930-1:2001 §6.2.2 / ISO 15930-3:2002 §6.2.2 / ISO 15930-7:2010 §6.2 + ISO 32000-1 §14.11.5. Every PDF/X profile mandates at least one /Type /OutputIntent /S /GTS_PDFX entry naming the target print condition and providing the /DestOutputProfile ICC profile stream — print shops use this to colour-match the PDF to their press / paper / ink combination.
  • New helper `AddPDFXOutputIntent(OutputConditionIdentifier, Info, ICCProfileStream, NumComponents, AlternateCS)` mirrors the v2.102 PDF/A wrapper but emits the GTS_PDFX subtype instead of GTS_PDFA1. Validates ICCProfileStream is non-nil and OutputConditionIdentifier is non-empty; calls RegisterICCProfile to embed the profile (auto-handles /Alternate fallback colour space matching NumComponents) and AddOutputIntent to build the OutputIntent dict.
  • EndDoc enforcement: when PDFXCompliance is set, EnsurePDFXOutputIntent walks the OutputIntents array and requires at least one entry with /S /GTS_PDFX + /DestOutputProfile. Missing entry raises with the registered identifier suggestion (FOGRA39 for sheetfed offset, CGATS TR 001 SWOP for North American web, Japan Color 2001 Coated for Asian markets).
  • Typical PDF/X workflow: 1) `Doc.PDFXCompliance := 'X-4'`, 2) `Doc.Trapped := 'False'`, 3) load CMYK ICC profile stream (FOGRA39 / SWOP / etc.) into a TStream, 4) `Doc.AddPDFXOutputIntent('FOGRA39 (ISO 12647-2:2004)', '', ICCStream, 4, 'DeviceCMYK')`, 5) design content, 6) Doc.EndDoc emits the OutputIntent linked from Catalog /OutputIntents array; consumer pre-flight tools (Adobe Acrobat, Enfocus PitStop, callas pdfaPilot) validate the colour-management pairing.
  • Win32 + Win64 clean compile; v2.108 / v2.109 / v2.110 / v2.111 / v2.112 baselines all recompile unchanged. smoke_pdfx_optin (originally v2.112) extended to: register a fake ICC profile + AddPDFXOutputIntent in each of the 3 profile emits (X-1a → FOGRA39 CMYK, X-3 → SWOP CMYK, X-4 → sRGB RGB) and add a rejection path that asserts missing OutputIntent raises. Python verifier extended to walk every dict and confirm the /Type /OutputIntent /S /GTS_PDFX entry with /DestOutputProfile is emitted.
  • Remaining PDF/X slices: v2.114 adds the transparency gate (X-1a / X-3 reject ExtGState fill/stroke alpha < 1 + BM non-Normal; X-4 allows transparency) + PageBox enforcement (every page needs TrimBox or ArtBox per §14.11.2); v2.115 closes the series with forbidden action / annot / XFA gates (Movie / Sound / FileAttachment / Screen / JavaScript / Launch / SubmitForm / ImportData / XFA all rejected because print workflows have no place for them).

2026-05-19 Version 2.112.0

  • Started PDF/X (ISO 15930) print-prepress producer series. PDF/X is the ISO family for sending press-ready PDFs to commercial printers; profiles X-1a (strict CMYK + spot), X-3 (ICC-managed color), and X-4 (PDF 1.6 transparency + ICCN) are the three in widespread industry use. v2.112 is slice 1/4: opt-in property + identity metadata. Full audit + 4-version roadmap archived at .superpowers/specs/2026-05-19-pdfx-compliance-audit.md.
  • New `PDFXCompliance: AnsiString` property on THotPDF accepts `''` (disabled, default), `'X-1a'` (ISO 15930-1:2001), `'X-3'` (ISO 15930-3:2002), or `'X-4'` (ISO 15930-7:2010). Setting a non-empty value auto-enables every producer-side requirement HotPDF can satisfy structurally for that profile.
  • New `Trapped: AnsiString` property (mandatory under PDFXCompliance) accepts `'True'` / `'False'` / `'Unknown'`. The PDF/X family mandates this DocInfo entry so the print shop knows whether the PDF was already trapped or needs trap handling in pre-press. Emitted as a PDF /Name (not a string) per ISO 32000-1 §14.11.6.1; e.g. `<< /Trapped /False >>`.
  • XMP metadata identification: the packet now advertises `xmlns:pdfx="http://ns.adobe.com/pdfx/1.3/"` with `` set to the spec-exact string (`PDF/X-1:2001`, `PDF/X-3:2002`, or `PDF/X-4`) and (for X-1a + X-4) `` set to the full profile name (`PDF/X-1a:2001` or `PDF/X-4:2010`). Adobe Acrobat pre-flight / Enfocus PitStop / callas pdfaPilot recognize the document as a PDF/X candidate.
  • EndDoc validation: rejects unknown profile values, rejects empty / unrecognized Trapped value, requires Title, auto-bumps PDF version to the profile's mandatory minimum (X-1a/X-3 → PDF 1.3, X-4 → PDF 1.6). Encryption + PDFXCompliance is a hard conflict — calling EnableEncrypt (or `ActivateProtection := True`) raises with a clear diagnostic explaining that PDF/X workflows must inspect every object without password handling.
  • Caller still authors the OutputIntent + ICC profile + TrimBox/ArtBox + valid prepress color spaces + correct font embedding (and avoids transparency for X-1a/X-3) for full PDF/X conformance. v2.113 - v2.115 follow-ups close those gaps: v2.113 adds `AddPDFXOutputIntent` (mandatory /GTS_PDFX intent), v2.114 adds transparency gate + PageBox enforcement, v2.115 closes the series with forbidden action/annot/XFA gates.
  • Win32 + Win64 clean compile; v2.108 / v2.109 / v2.110 / v2.111 baselines all recompile unchanged. New smoke smoke_pdfx_optin emits three PDFs (one per profile) and verifies XMP namespace + GTS_PDFXVersion + GTS_PDFXConformance plus DocInfo /Trapped /Name plus invalid profile / empty Trapped / unknown Trapped / encryption-conflict rejection paths.
  • PAdES series note: v2.109 / v2.110 / v2.111 cover B-B / B-T / B-LT / B-LTA + standalone timestamp use cases. The originally planned v2.112 (standalone ETSI.RFC3161 timestamp-only PDF) is effectively implicit — `AddDocumentTimestampSignature` from v2.111 works on a fresh document without an inner signature — so the PAdES series is considered closed at 3 commits and v2.112 reallocates to PDF/X. ETSI EN 319 142-1 baseline profiles are all supported.

2026-05-19 Version 2.111.0

  • PAdES producer series slice 3/4: PAdES-B-LTA Document Timestamp signature support per ISO 32000-1 §12.8.5 + ETSI EN 319 142-1 §5.7. The document timestamp is a second signature applied AFTER the long-term-validation signature is complete; it covers the entire LT-state file (original signature + DSS) and proves the LT material existed at a notarized time. This is the archival profile required for EU-regulated retention beyond the original CA certificate's lifetime.
  • New `THPDFPage.AddDocumentTimestampSignature(FieldName, ContentsBytes)` helper. Unlike a normal PAdES signature, a document timestamp has:
    • SubFilter `/ETSI.RFC3161` (an RFC 3161 TimeStampToken, not CAdES).
    • Zero-area widget rect (a timestamp has no visible appearance — purely cryptographic metadata).
    • No /Reason / /Location / /ContactName (those live in the TST signed-attrs, not the sig dict).
    • Default /Contents byte budget 16 KB (caller can pass smaller down to a 1 KB floor; a full RFC 3161 TST with cert chain is typically 4-8 KB).
  • Producer scope ends at the wire format: the helper creates the /Type /Sig placeholder dict with the timestamp-specific SubFilter and a /ByteRange + /Contents sentinel for post-emission patching via PreparePDFForSigning + InsertSignatureHex. Caller obtains the actual RFC 3161 TST bytes from a Trusted Timestamp Authority (TSA, e.g. DigiCert / GlobalSign / FreeTSA HTTP POST) and injects them. The typical PAdES-B-LTA archival workflow stacks: 1) emit PAdES-B-LT base signature (v2.109), 2) populate Catalog /DSS with cert chain + OCSP/CRL (v2.110), 3) save as incremental update, 4) add document timestamp field over the LT-state bytes (v2.111), 5) request TST from TSA over the byte range, 6) inject the TST bytes.
  • Win32 + Win64 clean compile; v2.108 / v2.109 / v2.110 baselines all recompile unchanged. New smoke smoke_pades_doctimestamp emits a PAdES-B-LT 'SigInner' field plus a 'DocTS' document timestamp field via the new helper. Python verifier asserts the inner signature has /SubFilter /ETSI.CAdES.detached, the timestamp widget /Rect is zero-area, the timestamp /V dict has /SubFilter /ETSI.RFC3161 and no /Reason / /Location / /ContactName, and /Contents is a 16 KB placeholder (32768 hex chars).
  • Remaining PAdES slice: v2.112 (optional) adds standalone ETSI.RFC3161 timestamp-only PDFs (no CAdES inner signature — a pure timestamping document container).

2026-05-19 Version 2.110.0

  • PAdES producer series slice 2/4: Document Security Store (DSS) dictionary support per ISO 32000-2 §12.8.4.3 + ETSI EN 319 142-1 §5.6. PAdES-B-LT (and B-LTA) require validation-related information — cert chain, OCSP responses, CRLs — to be embedded in the PDF itself so signatures can be verified long after the original signing time without needing to chase external PKI servers.
  • New helpers on THotPDF:
    • `EnsurePAdESDSS` — lazily creates the Catalog /DSS indirect dict with its /Certs, /OCSPs, /CRLs arrays and an empty /VRI sub-dict; subsequent calls return the existing dict.
    • `AddPAdESDSSCertificate(CertDERBytes)` — appends a DER-encoded X.509 certificate as an indirect stream to /DSS /Certs; returns the indirect stream object for use in VRI references.
    • `AddPAdESDSSOCSP(OCSPDERBytes)` — same for OCSP BasicOCSPResponse to /DSS /OCSPs.
    • `AddPAdESDSSCRL(CRLDERBytes)` — same for Certificate Revocation List to /DSS /CRLs.
    • `AddPAdESDSSVRI(SigContentsHexSHA1, Certs, OCSPs, CRLs)` — creates a per-signature VRI entry keyed by the UPPERCASE hex SHA-1 of the signature's /Contents bytes (the spec-mandated key form); each entry holds /Cert / /OCSP / /CRL arrays referencing the indirect streams created above.
  • Producer scope ends at the wire format. Caller still computes the SHA-1 of the final signed /Contents and supplies the DER-encoded certificates / OCSP responses / CRLs from their PKI tooling. Typical workflow: build PAdES-B-T base signature (v2.109), then add certs / OCSPs / CRLs to DSS for long-term verification, then attach VRI entries linking validation info to specific signatures.
  • Win32 + Win64 clean compile; v2.108 + v2.109 baselines all recompile unchanged. New smoke smoke_pades_dss creates a PAdES-B-LT signature field, adds 2 certs + 1 OCSP + 1 CRL to DSS, and adds a VRI entry with a test hex digest. Python verifier walks the DSS dictionary structure: Catalog /DSS ref, /Certs / /OCSPs / /CRLs array counts, VRI key matches, VRI entry cert/OCSP/CRL refs match the top-level DSS arrays.
  • Remaining PAdES slices: v2.111 implements PAdES-B-LTA document timestamp signature (second Sig field with SubFilter ETSI.RFC3161 covering the entire LT file); v2.112 (optional) adds standalone timestamp-only PDFs.

2026-05-19 Version 2.109.0

  • Started PAdES (ETSI EN 319 142) digital signatures producer work. v2.109 adds a high-level wrapper over the existing ISO 32000-1 §12.8 signature infrastructure (v2 of the AddSignedSignatureField helper) that hard-codes the PAdES-required SubFilter and validates the conformance profile. Full audit + 4-version roadmap archived at .superpowers/specs/2026-05-19-pades-compliance-audit.md.
  • New `THPDFPage.AddPAdESSignatureField(FieldName, Rect, Profile, Reason, Location, ContactName, ContentsBytes, Flags)` helper. Profile selects the ETSI EN 319 142-1 baseline level: 'B-B' (basic CAdES, no timestamp), 'B-T' (with RFC 3161 trusted timestamp, minimum for EU legal use), 'B-LT' (with DSS validation info for long-term verification), or 'B-LTA' (with document timestamp signature for archival). All four use SubFilter 'ETSI.CAdES.detached'; the profile parameter varies the diagnostic and the default /Contents byte budget. Other profile strings raise with the ETSI enumeration listed.
  • Auto-upsized Contents budget: PAdES-B-B + B-T default to 16 KB (caller can supply less; we enforce a 64-byte floor inherited from v2.108). PAdES-B-LT auto-upsizes to 20 KB minimum (CAdES + cert chain + OCSP/CRL data). PAdES-B-LTA auto-upsizes to 24 KB minimum (B-LT + document timestamp). Caller-supplied larger values pass through; the helper only raises the floor.
  • Producer scope: the wrapper creates the /Type /Sig placeholder dict with /SubFilter /ETSI.CAdES.detached, /Reason / /Location / /ContactName / /M (signing date), and the /ByteRange + /Contents sentinel for post-emission patching via PreparePDFForSigning + InsertSignatureHex. Actual CMS / CAdES bytes, RFC 3161 timestamp retrieval, OCSP/CRL gathering, and DSS dictionary construction remain caller responsibility — those require an external cryptographic library (OpenSSL, Bouncy Castle, Windows CAPI, etc.) outside the PDF producer's scope.
  • Win32 + Win64 clean compile. New smoke smoke_pades_signature emits four PAdES signature fields (B-B / B-T / B-LT / B-LTA) and confirms invalid profile values raise. Python verifier asserts AcroForm /SigFlags 3, each widget has /FT /Sig + /V indirect ref to a /Type /Sig dict with /SubFilter /ETSI.CAdES.detached, and /Contents placeholder hex length matches the expected (auto-upsized) byte budget.
  • Remaining PAdES slices on the roadmap: v2.110 adds the Catalog /DSS (Document Security Store) dictionary builder for PAdES-B-LT validation info (cert chain + OCSP/CRL); v2.111 adds the standalone document timestamp signature field for PAdES-B-LTA archival; v2.112 (optional) adds ETSI.RFC3161 timestamp-only PDFs.

2026-05-19 Version 2.108.0

  • PDF/A-2 + PDF/A-3 producer series CLOSES (4/4): ISO 19005-3 Annex E Associated Files. New high-level helper AddPDFA3AssociatedFile(FileName, MimeType, Description, Relationship, FileBytes) attaches an arbitrary file (XML invoice, source spreadsheet, JSON metadata, etc.) to a PDF/A-3 hybrid container and registers it in the Catalog's /AF array with the §E.4 /AFRelationship semantics.
  • Hybrid container workflow: typical use case is ZUGFeRD-style PDF/A-3 invoicing where the human-readable invoice rendering lives in the PDF page stream and the machine-readable XML source rides along as an Associated File. The /AFRelationship key tells consumers how the attached file relates to the visible content: 'Source' (original source data), 'Data' (raw data the PDF visualizes), 'Alternative' (alternate representation), 'Supplement' (supplementary material), 'Unspecified' (none of the above).
  • Spec gating: AddPDFA3AssociatedFile raises under any non-PDF/A-3 PDFACompliance value (Annex E is part-3 specific) with a diagnostic suggesting AddFileAttachmentAnnotation for PDF/A-2 and noting PDF/A-1 §6.1.11 prohibits attachments entirely. Empty FileName or MimeType raises; invalid Relationship value raises with the spec §E.4 enumeration.
  • Emission structure: indirect EmbeddedFile stream carries /Type /EmbeddedFile + /Subtype + /Params <> + the file bytes. Indirect Filespec dict carries /Type /Filespec + /F + /UF (both = FileName) + optional /Desc + /EF <> + /AFRelationship (Annex E key). Catalog /AF array is created lazily on first attachment and appended to on subsequent calls. The returned Filespec dict lets callers stamp additional optional keys (e.g. /CI checksum/info) when needed.
  • Win32 + Win64 clean compile. New smoke smoke_pdfa3_associated builds a PDF/A-3B hybrid invoice with a real XML invoice attached as /AFRelationship Source, then exercises three rejection paths: PDF/A-1 rejected, PDF/A-2 rejected, invalid /AFRelationship rejected. Python verifier asserts the Filespec / EmbeddedFile dictionary graph (Type / F / UF / Desc / AFRelationship / EF F UF / EmbeddedFile Type / Subtype #2F escape / Params Size).
  • PDF/A-2 + PDF/A-3 producer surface now fully audit-closed (v2.105 wrapper opt-in + XMP, v2.106 forbidden annot/action coverage, v2.107 §6.1.13 implementation limits, v2.108 Annex E Associated Files). With PDF/A-1 (v2.101-104) and PDF/UA-1 (v2.94-v2.100) already at strict conformance, HotPDF now covers the full PDF/A-1/2/3 + PDF/UA-1 archival/accessibility producer matrix end to end.

2026-05-19 Version 2.107.0

  • PDF/A-2 + PDF/A-3 producer series slice 3/4: ISO 19005-2 §6.1.13 + ISO 19005-3 §6.1.13 implementation limits enforcement. PDF/A-1 §6.1.13 is significantly looser (no hard string-length cap) so the new gate only fires when PDFACompliance is PDF/A-2 or later ('2*' / '3*').
  • New public method EnsurePDFAImplementationLimits validates the document's caller-controlled values against the spec hard limits: Doc.Title / Author / Subject / Keywords / Lang strings must be ≤ 32767 bytes; each page's MediaBox dimension must fall in [3..14400] units. EndDoc invokes the validator automatically when PDFACompliance is PDF/A-2/3; callers can also invoke it directly mid-build for early diagnostics.
  • Naturally-satisfied limits documented in the audit report: integer range (-2³¹..2³¹-1), real range (±3.403×10³⁸, near-zero ≥1.175×10⁻³⁸), name length ≤127 bytes, indirect-object count ≤8388607, q/Q nesting ≤28, DeviceN colorants ≤32, CID ≤65535. HotPDF's emission paths never exceed these because they're tied to producer-side data structures HotPDF controls. The validator focuses on caller-supplied strings + page boundaries because those are the only limits a real-world caller can blow through.
  • XFA forms (§6.4.2): HotPDF doesn't emit AcroForm /XFA or Catalog /NeedsRendering. Both are naturally satisfied across PDF/A-1/2/3 — documented in the audit report so future producer extensions don't reintroduce them.
  • .notdef glyph (§6.2.11.8 in PDF/A-2): the spec forbids text-showing operators from referencing .notdef. HotPDF's font handling avoids this when callers use registered Unicode TTF fonts (v2.74-v2.86 path) because the producer maps every codepoint to a real glyph or refuses the registration. Caller responsibility for raw text emission paths; documented in the audit report.
  • XMP namespace (§6.6.2): the spec forbids bytes / encoding attribute on the XMP packet header. BuildXMPPacket emits a minimal header (id="W5M0MpCehiHzreSzNTczkc9d") without either attribute — naturally satisfied across all PDF/A parts.
  • Win32 + Win64 clean compile. New smoke smoke_pdfa2_limits exercises three paths: PDF/A-2 within limits passes; PDF/A-1 with a 32768-byte Title also passes (no §6.1.13 gate); PDF/A-2 with a 32768-byte Title is rejected with the spec-section diagnostic. Previous PDF/A-2/3 baselines all recompile unchanged.
  • Remaining PDF/A-2/3 producer slice: v2.108 implements PDF/A-3 Annex E Associated Files (the PDF/A-3-specific hybrid container feature: Catalog /AF array of file specifications with /AFRelationship key + EmbeddedFile /Params /CheckSum + /ModDate).

2026-05-19 Version 2.106.0

  • PDF/A-2 + PDF/A-3 producer series slice 2/4: PDF/A-2 §6.3.1 + §6.5.1 (and identical PDF/A-3 §6.3.1 + §6.5.1) coverage. The v2.104 PDF/A-1 forbidden annotation + action gates already triggered on any non-empty PDFACompliance, so they continue to fire under PDF/A-2/3 unchanged. v2.106 confirms this behaviour, upgrades the diagnostic messages to reference the unified spec sections across all PDF/A parts, and documents the additional PDF/A-2-introduced forbidden subtypes that HotPDF satisfies naturally (no public emit path).
  • Forbidden actions matrix: PDF/A-1 forbids 6 (Launch / Sound / Movie / ResetForm / ImportData / JavaScript). PDF/A-2 + PDF/A-3 extend the forbidden set to 13 by adding Hide / SetOCGState / Rendition / Trans / GoTo3DView + the deprecated set-state / no-op subtypes. AddLaunchLink continues to raise; HotPDF has no public entry that emits the other 12, so they're automatically satisfied.
  • Forbidden annotation types matrix: PDF/A-1 forbids FileAttachment / Sound / Movie. PDF/A-2 / PDF/A-3 drop the FileAttachment ban (relaxed in v2.105) but add 3D / Screen to the forbidden set. AddSoundAnnotation + AddMovieAnnotation continue to raise across all PDF/A parts; HotPDF has no 3D / Screen emit path so those are naturally satisfied.
  • Diagnostic message upgrade: the three v2.104 gates (AddSoundAnnotation, AddMovieAnnotation, AddLaunchLink) now reference all three relevant spec sections in their error text (e.g. "PDF/A-1 §6.5.2 / PDF/A-2 §6.3.1 / PDF/A-3 §6.3.1") so developers see the unified prohibition. The AddLaunchLink message additionally enumerates the PDF/A-2/3 extended forbidden action set.
  • Win32 + Win64 clean compile. New smoke smoke_pdfa2_annot_action exercises the three gates under all six PDF/A-2/3 levels (2B, 2U, 2A, 3B, 3U, 3A); each call asserts the expected exception with a §6.3.1 / §6.5.1 diagnostic. Previous PDF/A-1 baselines (smoke_pdfa1_compliance / smoke_pdfa1_forbidden / smoke_pdfa1_annot_action) and v2.105 smoke_pdfa2_compliance all recompile unchanged.
  • Remaining PDF/A-2/3 producer slices: v2.107 enforces ISO 19005-2 §6.1.13 implementation limits (integer / real / string / name byte ranges, q/Q nesting, DeviceN colorants, CID, page boundary) + §6.4.2 XFA + §6.2.11 Type 3 / .notdef strict; v2.108 implements PDF/A-3 Annex E Associated Files (hybrid container PDFs with arbitrary embedded file types).

2026-05-19 Version 2.105.0

  • Started a four-version PDF/A-2 (ISO 19005-2:2011) + PDF/A-3 (ISO 19005-3:2012) producer compliance series. Audit + roadmap archived at .superpowers/specs/2026-05-19-pdfa2-pdfa3-compliance-audit.md. v2.105 lands the wrapper-layer opt-in, XMP pdfaid namespace extensions, base PDF version auto-bump to 1.7, and selective relaxation of the v2.101-104 PDF/A-1 strict gates that PDF/A-2 / PDF/A-3 spec explicitly permit.
  • PDFACompliance property now accepts six new values in addition to PDF/A-1's '' / 'A' / 'B': '2A' / '2B' / '2U' (PDF/A-2 Levels A / B / U) and '3A' / '3B' / '3U' (PDF/A-3 Levels A / B / U). Level U is the new "Unicode preservation only" tier introduced by PDF/A-2 (visual + Unicode-extractable text, no Tagged PDF requirement).
  • BuildXMPPacket extended: the element now reflects the parsed part number (1 / 2 / 3) and reflects the parsed level letter (A / B / U). veraPDF and similar validators identify the document's PDF/A part by this pair. Per ISO 19005-2 §6.6.4 + ISO 19005-3 §6.6.4 Table 8.
  • Auto-bump: PDFACompliance '2*' / '3*' auto-bumps the document version to PDF 1.7 because PDF/A-2 and PDF/A-3 are both based on ISO 32000-1 (PDF 1.7). PDF/A-1 remains anchored to PDF 1.4.
  • v2.103 transparency gate relaxed: RegisterExtGState now only raises on transparent alpha / non-Normal blend mode when PDFACompliance is PDF/A-1 strict ('A' or 'B'). PDF/A-2 / PDF/A-3 §6.4 explicitly permit transparency (with separate requirements like OutputIntent presence and the full PDF 1.4 named blend modes), so PDFACompliance '2*' / '3*' lets the call pass through.
  • v2.104 FileAttachment gate relaxed: AddFileAttachmentAnnotation now only raises under PDF/A-1 strict. PDF/A-2 §6.3.1 only forbids 3D / Sound / Screen / Movie annotation types; FileAttachment is permitted and is the foundation of PDF/A-3's Annex E Associated Files hybrid workflow. The remaining v2.104 annot/action gates (Sound, Movie annotations; Launch action) still fire across all PDF/A parts per shared §6.3.1 + §6.5.1 prohibitions.
  • New THotPDF helper methods PDFAPart / PDFAConformanceLevel / IsPDFA1Strict / IsPDFA2OrLater make value branching ergonomic. Existing v2.101-104 PDF/A gates now use these helpers for cleaner code paths and clearer error messages that suggest switching to PDF/A-2 / PDF/A-3 when appropriate.
  • Win32 + Win64 clean compile; v2.101-104 baselines (smoke_pdfa1_compliance / smoke_pdfa1_forbidden / smoke_pdfa1_annot_action) all recompile unchanged. New smoke smoke_pdfa2_compliance emits four PDFs (Levels 2B / 2U / 2A / 3U), exercises the v2.103 transparency relaxation, the v2.104 FileAttachment relaxation, plus two validation paths (invalid '4Q' level rejected; PDF/A-1 transparency gate still fires). Python verifier asserts XMP pdfaid:part + conformance match each level and Level 2A has Tagged-PDF inheritance.
  • Remaining PDF/A-2/3 producer slices: v2.106 extends the forbidden action set from 6 to 13 (Hide / SetOCGState / Rendition / Trans / GoTo3DView added; deprecated set-state / no-op) plus adjusts annot type bans (3D / Screen added, FileAttachment removed); v2.107 enforces ISO 19005-2 §6.1.13 implementation limits + XFA gates; v2.108 implements PDF/A-3 Annex E Associated Files (hybrid container PDFs).

2026-05-19 Version 2.104.0

  • PDF/A-1 producer series CLOSES (slice 4/4): closed §6.5.2 forbidden annotation types (FileAttachment, Sound, Movie) and §6.6.1 Launch action subtype. With v2.104 landed, all 17 producer-side gaps from the 2026-05-19 PDF/A-1 audit (.superpowers/specs/2026-05-19-pdfa1-compliance-audit.md) are closed: the four heavy hitters (§5/§6.7.11 identification, §6.1.3 encryption ban, §6.2.2 OutputIntent, §6.4 transparency) plus the assorted forbidden-feature gates.
  • §6.5.2 forbidden annotation types: AddFileAttachmentAnnotation, AddSoundAnnotation, AddMovieAnnotation now raise under PDFACompliance with a clear spec-section diagnostic. Per spec "the FileAttachment, Sound and Movie types shall not be permitted" to avoid external content dependencies and multimedia in archival files.
  • §6.6.1 Launch action: AddLaunchLink now raises under PDFACompliance with a §6.6.1 diagnostic naming the launch target. Per spec "The Launch, Sound, Movie, ResetForm, ImportData and JavaScript actions shall not be permitted." The other five forbidden action subtypes (Sound/Movie/ResetForm/ImportData/JavaScript) have no public HotPDF entry point that emits them, so they're naturally satisfied without explicit gates.
  • PDFUACompliance + PDFACompliance combined now works correctly across all four PDF/A slices. The two opt-ins layer: Level A inherits PDFUACompliance auto-on for Tagged PDF; both wrappers apply their own §-specific guards (Suspects/Tabs/Lang for PDF/UA; Encrypt/Transparency/Annot/Action for PDF/A) without interference.
  • Audit closure summary: all PDF/A-1 producer-side §6 file format requirements that HotPDF can satisfy structurally are now closed. Caller responsibilities remaining (§6.1.5 Info-XMP equivalence, §6.2.3.3 colour-space ↔ OutputIntent matching, §6.3 font specifics where v2.74-v2.86 PDF/UA work already handled most) align with the corresponding caller responsibilities in PDF/UA-1.
  • Win32 + Win64 clean compile; v2.103 smoke_pdfa1_forbidden recompiles cleanly. New smoke smoke_pdfa1_annot_action exercises all four gated paths (AddFileAttachmentAnnotation, AddSoundAnnotation, AddMovieAnnotation, AddLaunchLink) under PDFACompliance and asserts each call raises with a §6.5.2 or §6.6.1 diagnostic.
  • PDF/A-1 producer surface is now fully audit-closed. Future PDF/A-2 (ISO 19005-2:2011 based on PDF 1.7, allows transparency + layers) and PDF/A-3 (ISO 19005-3:2012, allows embedded files for hybrid workflows) extensions remain potential future candidates pending caller demand.

2026-05-19 Version 2.103.0

  • PDF/A-1 producer series slice 3/4: closed §6.4 Transparency (CRITICAL gap #15) + §6.2.8 ExtGState /TR transfer function (gap #14). Two §6 gaps related to existing HotPDF producer paths are naturally satisfied without code changes: §6.1.10 LZW filter (HotPDF only emits FlateDecode in its writer path) and §6.2.4 image /Interpolate (HotPDF never emits /Interpolate). These are now documented as automatic in the audit report.
  • §6.4 Transparency forbids non-1.0 alpha and non-Normal/Compatible blend modes. RegisterExtGState now gates under PDFACompliance: fill alpha < 1 raises (PDF/A-1 requires /ca = 1.0); stroke alpha < 1 raises (/CA = 1.0); blend mode other than `Normal` or `Compatible` raises. All three diagnostics name the spec section and the offending value. The default ExtGState (no alpha keys, no BM) and the explicit allowed values (1.0 alphas, Normal / Compatible BM) pass through unchanged.
  • §6.2.8 ExtGState forbids the /TR transfer function key (deprecated; PDF/A-1 only allows /TR2 with value /Default, and even that is best avoided). RegisterTransferFunctionState now raises under PDFACompliance with a clear diagnostic. Callers needing transfer functions for PDF/X workflows can still use the helper outside PDFACompliance mode.
  • §6.1.10 LZW + §6.2.4 Interpolate: documented as naturally satisfied by HotPDF's existing producer paths (FlateDecode-only writer, no /Interpolate emission). No code changes required; flagged in the audit report comments so future callers don't reintroduce them.
  • Win32 + Win64 clean compile; v2.102 smoke_pdfa1_compliance recompiles cleanly. New smoke smoke_pdfa1_forbidden exercises all three gated paths (ca<1, CA<1, BM=Multiply, /TR registration) plus verifies the allowed paths (ca=1.0, CA=1.0, BM=Normal/Compatible, no-alpha) still pass through. No verifier — the smoke itself asserts via try/except that each gated call raises.
  • Remaining PDF/A-1 producer slice: v2.104 closes §6.5.2 forbidden annotation types (FileAttachment, Sound, Movie) + §6.5.3 annot /F /AP rules + §6.6.1 forbidden action types (Launch, Sound, Movie, ResetForm, ImportData, JavaScript) + §6.9 Form field /A /AA + /NeedAppearances. After v2.104 lands the PDF/A-1 producer surface is fully audit-closed.

2026-05-19 Version 2.102.0

  • PDF/A-1 (ISO 19005-1:2005) producer series slice 2/4: closed §6.2.2 OutputIntent + ICC profile gap. New high-level `AddPDFAOutputIntent(OutputConditionIdentifier, Info, ICCProfileStream, NumComponents, AlternateCS)` helper wraps the v2.51 RegisterICCProfile + AddOutputIntent('GTS_PDFA1', ...) two-call pattern into one. PDF/A-1 strict requires a `/Type /OutputIntent /S /GTS_PDFA1` entry with valid `/DestOutputProfile` ICC stream — without it conforming files cannot accurately reproduce colours across platforms.
  • EndDoc PDFACompliance gate adds an `EnsurePDFAOutputIntent` check: walks the registered OutputIntent list, looks for at least one `/S /GTS_PDFA1` entry with `/DestOutputProfile`. Absence raises a clear diagnostic naming the spec section and pointing callers to AddPDFAOutputIntent. Per §6.2.2 + Annex A.
  • Typical archival workflow: pass an sRGB IEC61966-2.1 ICC profile stream for screen-targeted PDF/A, FOGRA39 / GRACoL for print, or the appropriate registered CMYK profile for prepress. NumComponents must be 1 (Gray), 3 (RGB / Lab), or 4 (CMYK) per PDF 1.7 8.6.5.5 Table 66; the helper forwards validation to RegisterICCProfile.
  • Helper validates: empty OutputConditionIdentifier raises (§6.2.2 requires the identifier); nil ICCProfileStream raises (DestOutputProfile mandatory). Returns no value — the OutputIntent is auto-registered in FOutputIntents and surfaced through the existing Catalog /OutputIntents serialization path.
  • Win32 + Win64 clean compile; v2.101 smoke_pdfa1_compliance updated to call AddPDFAOutputIntent (with a fake 192-byte ICC stand-in; real callers ship a true ICC binary). New 4th validation case asserts that missing OutputIntent under PDFACompliance raises with a §6.2.2 diagnostic. Python verifier extended to check Catalog /OutputIntents array, walks entries finding `/S /GTS_PDFA1` with `/DestOutputProfile` indirect ref.
  • Remaining PDF/A-1 producer slices: v2.103 closes #15 Transparency (CRITICAL) + #5 LZW filter + #10 image /Interpolate + #14 ExtGState /TR; v2.104 closes #16 FileAttachment/Sound/Movie annot + #17 annot /F /AP rules + #18 Launch/Sound/Movie/ResetForm/ImportData/JavaScript actions + #20 Form field /A /AA + /NeedAppearances.

2026-05-19 Version 2.101.0

  • Started a four-version PDF/A-1 (ISO 19005-1:2005) producer-side compliance series with the wrapper-layer opt-in. The full 17-gap audit report is archived at `.superpowers/specs/2026-05-19-pdfa1-compliance-audit.md`; v2.101 lands the foundational pieces (PDFACompliance property, XMP pdfaid identification, Title/Lang strictness, encryption conflict guard). v2.102 - v2.104 close the remaining colour-management, transparency, font, and annotation/action gaps.
  • New `PDFACompliance: AnsiString` property accepts '' (disabled, default), 'A' (Level A: visual + Tagged PDF + accessibility), or 'B' (Level B: visual preservation only). Invalid values raise at EndDoc time. Per ISO 19005-1:2005 §5 conformance levels.
  • Setting PDFACompliance:='A' auto-enables PDFUACompliance (Level A inherits the Tagged PDF requirements from §6.8 which match PDF/UA-1's §7 logical structure requirements). Both wrappers can be combined without conflict; the v2.94 PDF/UA-1 Lang/Title/Suspects gates layer on top of the v2.101 PDF/A guards.
  • Setting either level auto-enables FEnableXMPMetadata so the document carries the required /Metadata XMP stream. Per §6.7.2 Catalog Metadata is mandatory.
  • BuildXMPPacket extended: when PDFACompliance is non-empty, the XMP packet declares `xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/"` plus `1` and `A` (or `B`). veraPDF and similar validators rely on this exact triple to classify the document as PDF/A-1A or 1B candidate. Per §6.7.11 Table 6 PDF/A identification schema.
  • Title / Lang strictness: PDFACompliance enabled + Doc.Title='' raises (PDF/A-1 §6.7.3 requires dc:title in XMP equivalent to Info /Title). Lang strictness mirrors the v2.94 PDFUACompliance behaviour (§6.8.4 for Level A) so authors must pick a real BCP-47 tag instead of receiving a silent 'en' fallback.
  • Encryption conflict guard: per §6.1.3 the file trailer must not contain /Encrypt. EnableEncrypt (the internal entry that gets called when OwnerPassword/UserPassword/Protection toggles are set) now raises with a clear diagnostic when PDFACompliance is non-empty. EndDoc surfaces the conflict via the same path so callers see the failure before serialization.
  • Caller-side responsibility still includes: emitting a valid PDF/A-1 OutputIntent with ICC profile (v2.102 follow-up), avoiding transparency (v2.103), avoiding prohibited annotation/action types (v2.104), and authoring real semantic structure tree for Level A (covered by v2.96-v2.99 Tagged PDF helpers).
  • Win32 + Win64 clean compile; v2.100 baseline smoke_pdfua_link_contents recompiles cleanly. New smoke smoke_pdfa1_compliance emits both Level A and Level B PDFs plus exercises three validation paths (invalid level Z, empty Title, encryption + PDFACompliance conflict). Python verifier asserts xmlns:pdfaid + pdfaid:part=1 + matching pdfaid:conformance, dc:title, and Level A additionally has /MarkInfo /Marked true + Catalog /Lang + /StructTreeRoot + xmlns:pdfuaid (auto-inheritance).

2026-05-19 Version 2.100.0

  • Closed the remaining PDF/UA-1 §7.18.5 link-annotation /Contents coverage gap from the 2026-05-19 audit. v2.95 added Description to AddURILink; v2.100 brings the same alternate-description support to the three remaining link entry points: AddGoToLink (intra-document jump), AddGoToRLink (cross-file jump), and AddLaunchLink (external file launch).
  • Each helper gains an optional final `const Description: AnsiString = ''` parameter. When non-empty the annotation dict carries a `/Contents (Description)` entry; under `PDFUACompliance`, an empty Description raises with a clear diagnostic naming the offending link target so the missing alternate description can't silently slip through. Default empty preserves backward compatibility outside PDFUACompliance mode.
  • Per PDF/UA-1 §7.18.5 strict: "Links shall contain an alternate description via their Contents key as described in ISO 32000-1:2008, 14.9.3." The spec applies to all link annotation flavours, not just URI links - the helper signatures now make that uniform.
  • Typical workflow: `Page.AddGoToLink(R, 1, 700, 'Jump to page 2: Methodology'); Page.AddGoToRLink(R, 'companion.pdf', 0, -1, false, 'Open companion document for full data tables'); Page.AddLaunchLink(R, 'readme.txt', false, 'Open README in default text editor');`
  • Win32 + Win64 clean compile; v2.99 baseline smoke_pdfua_figure recompiles cleanly. New smoke smoke_pdfua_link_contents builds a 2-page document with three Link annotations on page 0 (GoTo to page 1, GoToR to 'companion.pdf', Launch to 'readme.txt') plus exercises the empty-Description PDFUACompliance exception path for all three. Python verifier asserts each link annotation carries /Subtype /Link, the matching /S /GoTo (or /GoToR / /Launch) action type, and a /Contents value matching the supplied Description.
  • PDF/UA-1 producer surface coverage summary: with v2.100, all four link annotation entry points now satisfy §7.18.5. Combined with the v2.94 audit closure (Suspects, Tabs, Lang), v2.95 (URI Contents, Bit 10, ID), and the v2.96-v2.99 Tagged PDF semantic helper series (Heading, List, Table, Figure), HotPDF's PDF/UA-1 producer-side surface is now fully audit-closed at both the spec-strict wrapper layer and the ergonomic-API layer.

2026-05-19 Version 2.99.0

  • Added `BeginTaggedFigure(Parent, AltText, BBox): FigureElem` + `EndTaggedFigure` as the fourth and final slice of the Tagged PDF semantic helper series for PDF/UA-1 §7.3 (Graphics). Brackets caller-supplied drawing operations (image XObject, vector path, stroked rectangle) inside a Figure structure element with mandatory /Alt and optional /BBox layout attribute.
  • /Alt is mandatory (no default empty fallback). Per PDF/UA-1 §7.3 strict: "Figure tags shall include an alternative representation or replacement text that represents the contents marked with the Figure tag." Empty AltText raises with a clear diagnostic pointing callers to the v2.88 6-arg AddStructureElement overload if /ActualText (not /Alt) is the appropriate alternate-text mechanism.
  • Optional BBox is a 4-value [llx, lly, urx, ury] array describing the figure's bounding box in default user space, attached as /A << /O /Layout /BBox [...] >> per ISO 32000-1 14.8.5.4.5 Layout attribute owner. Pass empty array to skip the BBox attribute entirely. Wrong-length BBox (1/2/3/5+ elements) raises with the spec rectangle layout as guidance.
  • Typical workflow: `Fig := Doc.BeginTaggedFigure(Root, 'Company logo: arrow pointing right', [72, 600, 200, 720]); ... draw figure contents ... Doc.EndTaggedFigure;` The Figure participates in the same MCID / ParentTree wiring as other v2.90 BeginTaggedContent paths, but with /Alt + /BBox attributes attached automatically.
  • This closes the PDF/UA-1 §7.3 caller-responsibility surface from the audit report. The Tagged PDF semantic helper series now covers all four heavy structure types: §7.4 (Heading, v2.96), §7.6 (List, v2.97), §7.5 (Table, v2.98), §7.3 (Figure, v2.99). Together with the v2.94 + v2.95 wrapper-layer fixes, HotPDF's PDF/UA-1 producer surface is now both strict-conformant at the spec level AND ergonomic at the API level.
  • Win32 + Win64 clean compile; v2.98 baseline smoke_pdfua_table recompiles cleanly. New smoke smoke_pdfua_figure emits two Figure structure elements (one with /Alt + /BBox, one with /Alt only) plus exercises the empty-AltText and malformed-BBox exception paths. Python verifier asserts both Figure /S types, /Alt text content matches, and the BBox array values are [72, 600, 200, 720].

2026-05-19 Version 2.98.0

  • Added four Tagged Table helpers for PDF/UA-1 §7.5 (Tables) as the third slice of the Tagged PDF semantic helper series. `BeginTaggedTable(Parent): TableElem` builds the Table structure container; `AddTaggedTableRow(Table): TRElem` adds a TR row; `EmitTaggedTableHeader(TR, X, Y, Text, Scope): THElem` emits a TH cell with the mandatory /Scope attribute; `EmitTaggedTableCell(TR, X, Y, Text): TDElem` emits a TD data cell. All five tag types (Table, TR, TH, TD, plus the attribute object) are spec-conformant.
  • /Scope attribute (ISO 32000-1 14.8.5.7 Table 350) is mandatory on the helper (no default), with valid values `Row`, `Col`, or `Both`. Invalid Scope strings raise with the spec set as guidance. PDF/UA-1 §7.5 strict: "Structure elements of type TH should have a Scope attribute. If the table's structure is not determinable via Headers and IDs, then structure elements of type TH shall have a Scope attribute." Forcing Scope in the helper signature prevents the missing-Scope conformance failure mode.
  • Scope serialization follows the standard attribute object form: `/A << /O /Table /Scope / >>` (per ISO 32000-1 14.7.5.3 Class Map) so consumer readers see it as a Table-namespace attribute. Same pattern as the v2.97 List `/A << /O /List /ListNumbering ... >>` attribute.
  • Table and TR are pure structure containers (no marked-content on the page), but TH and TD do carry visible text so both go through BeginTaggedContent + TextOut + EndTaggedContent. Each TH and TD cell gets its own MCID, BDC operator on the page content stream, and ParentTree entry. Helper returns the cell StructElem for callers wanting to attach further attributes (e.g. /Headers list for complex tables, /RowSpan, /ColSpan).
  • Typical workflow: `T := Doc.BeginTaggedTable(Root); R := Doc.AddTaggedTableRow(T); Doc.EmitTaggedTableHeader(R, 72, 750, WideString('Name'), 'Col'); Doc.EmitTaggedTableHeader(R, 200, 750, WideString('Age'), 'Col'); R2 := Doc.AddTaggedTableRow(T); Doc.EmitTaggedTableCell(R2, 72, 730, WideString('Alice')); ...` Replaces the v2.90 multi-step structure plus BDC/EMC sequence for each cell.
  • This closes the PDF/UA-1 §7.5 caller-responsibility surface from the audit report. The Tagged PDF semantic helper series now covers §7.4 (Heading, v2.96), §7.6 (List, v2.97), and §7.5 (Table, v2.98). Remaining slice (v2.99+): Figure helper for §7.3 with /BBox + /Alt + caller-provided drawing block.
  • Win32 + Win64 clean compile; v2.97 baseline smoke_pdfua_list recompiles cleanly. New smoke smoke_pdfua_table builds a 3-row table (1 header + 2 data) with 3 TH cells (Scope=Col) + 6 TD cells + exercises the invalid-Scope exception. Python verifier asserts the full Table > TR > TH/TD structure with /Scope /Col on every TH.

2026-05-19 Version 2.97.0

  • Added two PDF/UA-1 §7.6 List helpers as the second slice of the Tagged PDF semantic helper series: `BeginTaggedList(Parent, NumberingStyle): ListElem` builds the L structure element with an /A << /O /List /ListNumbering /<Style> >> attribute object per ISO 32000-1 14.8.5.5; `EmitTaggedListItem(ListElem, LblX, LblY, LabelText, BodyX, BodyY, BodyText): LIElem` emits a complete LI > {Lbl, LBody} substructure with marked-content for both the visible label and body text.
  • NumberingStyle parameter accepts the 8 PDF/UA-1 / ISO 32000-1 spec names: `None`, `Decimal`, `UpperRoman`, `LowerRoman`, `UpperAlpha`, `LowerAlpha`, `Circle`, `Disc`. Invalid style names raise with the offending value and the full spec set as guidance. Empty string omits /ListNumbering entirely so callers can attach a custom attribute object before commit.
  • Per PDF/UA-1 §7.6: ordered lists require an explicit /ListNumbering attribute; unordered lists may use `None` or one of the bullet glyphs. The helper enforces neither - it's the caller's job to choose the style that matches the visible content.
  • Typical workflow: `Lst := Doc.BeginTaggedList(Root, 'Decimal'); Doc.EmitTaggedListItem(Lst, 72, 750, WideString('1.'), 96, 750, WideString('First item')); ...` Each list item is one call replacing the v2.90 six-line LI + Lbl-BDC-TextOut-EMC + LBody-BDC-TextOut-EMC chain.
  • The Lbl and LBody children both go through BeginTaggedContent (so each gets its own MCID and ParentTree entry) and the EmitTaggedListItem helper returns the LI structure element for callers wanting to attach nested LI children, /Alt, or /Lang.
  • This closes the PDF/UA-1 §7.6 caller-responsibility surface from the audit report. Remaining Tagged PDF semantic helper slices (v2.98+): Table helper covering §7.5 (Table / TR / TH / TD with /Scope), Figure helper covering §7.3 (Figure + /Alt + /BBox attribute).
  • Win32 + Win64 clean compile; v2.96 baseline smoke_pdfua_heading recompiles cleanly. New smoke smoke_pdfua_list builds a 3-item Decimal-numbered ordered list + a 2-item None-numbered unordered list + exercises the invalid-style exception path. Python verifier asserts two /S /L StructElems with distinct /ListNumbering attribute values, 3 vs 2 LI children, and Lbl/LBody children on the first LI of the ordered list.

2026-05-19 Version 2.96.0

  • Added `EmitTaggedHeading(Level, Parent, X, Y, Text)` high-level helper for PDF/UA-1 §7.4. One call emits a complete `/H1`..`/H6` structure element paired with its visible text on the current page: the helper allocates a per-page MCID, emits the BDC operator, runs TextOut, then EMC, builds the StructElem with the proper /H<Level> role, and wires it into /ParentTree. Replaces the v2.90 three-line BeginTaggedContent + TextOut + EndTaggedContent idiom for the common heading case.
  • Level parameter accepts 1..6 (the standard heading set). Out-of-range Level raises an Exception with a clear diagnostic. PDF/UA-1 §7.4.3 permits user-defined H7+ tags but those are rare; callers needing them drop back to the explicit BeginTaggedContent('H7', ...) sequence.
  • Returns the heading StructElem so callers can chain attribute additions (/Lang, /Alt for decorative headings, etc.) after the helper returns.
  • Typical workflow: `Doc.EmitTaggedHeading(1, RootElem, 72, 750, WideString('Chapter 1'));` now replaces the multi-line BDC + TextOut + EMC + AddStructureElement plumbing entirely.
  • This is the first slice of a planned "Tagged PDF semantic helpers" series following the v2.95 PDF/UA-1 wrapper closure. Future slices (v2.97+) cover List (L / LI / Lbl / LBody) and Table (Table / TR / TH / TD with /Scope) helpers to close the §7.5 and §7.6 caller-responsibility surfaces in the audit report.
  • Win32 + Win64 clean compile; v2.95 baselines recompile cleanly. New smoke smoke_pdfua_heading emits three headings (H1/H2/H3) plus exercises the Level=7 range-check exception path; Python verifier asserts three /S /H<n> StructElems and matching /H<n> BDC operators in the decompressed page content stream.

2026-05-19 Version 2.95.0

  • PDF/UA-1 audit closure: addressed the three remaining producer-side gaps identified in the v2.94 audit (§7.18.5 Link Contents, §7.16 encryption Bit 10, §7.9 StructElem ID). PDF/UA-1 producer surface is now spec-complete; all six gaps from the original audit have been closed across v2.94 + v2.95.
  • §7.18.5 Link Contents (HIGH): `AddURILink(Rect, URL, Description='')` gains a third `Description` parameter that, when non-empty, fills the annotation's `/Contents` key with the supplied alternate description. PDF/UA-1 §7.18.5 makes this mandatory: "Links shall contain an alternate description via their Contents key." Under PDFUACompliance, an empty Description now raises with a clear diagnostic so screen-readable text accompanies every link. Default Description = '' for backward compatibility outside PDFUACompliance mode.
  • §7.16 Encryption Bit 10 (MED): when PDFUACompliance is True and the document is encrypted (via EnableEncrypt), HotPDF auto-OR's `ProtectFlags := ProtectFlags or $200` (Bit 10 = "Extract text and graphics for use with assistive technology"). Per PDF/UA-1 §7.16: "An encrypted conforming file shall contain a P key in its encryption dictionary. The 10th bit position of the P key shall be true." Without this bit, screen readers can't extract content from an encrypted PDF, defeating accessibility. The fix is conditional - non-PDFUA encryption keeps caller-supplied flags untouched.
  • §7.9 StructElem ID (LOW): new 7-arg `AddStructureElement` overload accepts a non-empty `IDStr` parameter that stamps the structure element's `/ID` key. The helper maintains an internal set of issued IDs and raises on collision so duplicate Note IDs (which would confuse cross-reference Link → Note jumps and assistive tech) can't slip through. Per spec: "Each note tag shall have a unique entry in the ID key."
  • EnableEncrypt refactor: the original body now lives in `EnableEncryptInternal` and the public `EnableEncrypt` is a 6-line wrapper that does the PDF/UA-1 Bit 10 stamp first, then forwards. v2 / v4 / v5 encryption branches are unchanged byte-for-byte; existing encrypted-PDF callers retain byte-identical output unless they explicitly enable PDFUACompliance.
  • Verifier coverage: new smoke_pdfua_gap_closure exercises §7.18.5 (Link with Description) + §7.9 (two Note structure elements with distinct IDs + a collision attempt that should raise). Verifier asserts `/Contents` is present and matches the Description, and that two unique Note `/ID` values exist. The encryption Bit 10 fix is documented in the audit report; smoke coverage of encrypted PDFs is deferred to a larger encryption-test rewrite.
  • Spec coverage closure: with v2.95 landed, all 6 gaps from the 2026-05-19 PDF/UA-1 audit (`.superpowers/specs/2026-05-19-pdfua-compliance-audit.md`) are closed. HotPDF's PDF/UA-1 producer-side surface is now strict-conformant for the wrapper layer (§5 metadata, §6 conformance, §7.1 Catalog requirements, §7.2 Lang, §7.16 encryption, §7.18.3 Tabs, §7.18.5 Link Contents, §7.9 Note IDs). Semantic structure tree decisions (heading hierarchy, list nesting, table TH/Scope, Figure /Alt quality) remain caller-responsibility as always.

2026-05-19 Version 2.94.0

  • PDF/UA-1 (ISO 14289-1:2014) producer-side compliance audit follow-up: closed three real conformance gaps that the v2.87 high-level opt-in didn't cover. The audit cross-checked HotPDF v2.93 output against every §7 file-format requirement clause and identified six producer gaps; this version addresses the three EndDoc-stage fixes (Suspects key, annotated-page Tabs, Lang strictness). The remaining three gaps (Link Contents, encryption Bit 10, StructElem ID) land in v2.95-v2.97.
  • §7.1 Suspects (HIGH severity): the Catalog → MarkInfo dictionary now emits `/Suspects false` when `PDFUACompliance` is True. Per spec "Files claiming conformance with this International Standard shall have a Suspects value of false" — veraPDF and similar validators check for the explicit presence of the key, not just its absence. v2.93 left the key out entirely which triggered "Catalog MarkInfo does not contain a Suspects entry" under spec-strict validation.
  • §7.18.3 Tabs (MED severity): EndDoc now walks every page and stamps `/Tabs /S` on any page that carries a non-empty `/Annots` array and doesn't already declare `/Tabs`. Per spec "Every page on which there is an annotation shall contain in its page dictionary the key Tabs ... and its value shall be S." Only fires under `PDFUACompliance`; callers who set `/Tabs` explicitly (via `THPDFPage.SetTabsOrder`) keep their choice.
  • §7.2 Lang (MED severity): `PDFUACompliance=True` with an empty `Doc.Lang` now raises an Exception with a clear diagnostic instead of silently filling `'en'`. The pre-v2.94 fallback mis-labelled non-English documents (any Chinese / Arabic / Japanese / etc. content would silently emit a wrong BCP-47 tag, which screen readers consume to switch pronunciation). Callers must now declare the document language explicitly — typical use: `Doc.Lang := 'en-US'`, `'zh-CN'`, `'ar-SA'`, etc.
  • Backward compatibility note: existing PDFUACompliance callers must add `Doc.Lang := '...'` before BeginDoc / EndDoc. The four sample smokes (smoke_pdfua_compliance, smoke_pdfua_alt_actualtext, smoke_pdfua_annot_structparents, smoke_pdfua_tagged_content) were updated to set `Doc.Lang := 'en-US'` accordingly. Callers who don't use PDFUACompliance are unaffected.
  • Verifier coverage: smoke_pdfua_compliance_verify.py now asserts `/Suspects false` in the Catalog MarkInfo dict; smoke_pdfua_annot_structparents_verify.py asserts `/Tabs /S` on the annotated page. Both run end-to-end against the resulting PDF.
  • Audit report: a full PDF/UA-1 compliance audit report covering all 21 §7 file-format clauses is archived at `.superpowers/specs/2026-05-19-pdfua-compliance-audit.md` (project-local, not committed). The report classifies every clause as 'HotPDF auto-satisfies', 'caller responsibility with helper API', or 'real producer gap' and ranks the remaining gaps by severity for the v2.95+ roadmap.

2026-05-19 Version 2.93.0

  • Added `RegisterColorConversionLUT3D(GridR, GridG, GridB, OutputComponents, Samples, BitsPerSample=8, Order=1): THPDFStreamObject`, a high-level wrapper over the v2.52 Function Type 0 primitive for the common 3-input colour-management LUT use case. Internally builds the standard /Domain=[0,1]*3 + /Range=[0,1]*OutputComponents + /Size=[GridR, GridG, GridB] arrays and forwards to RegisterSampledFunction.
  • Typical workflow: ICC profile-driven sRGB -> Lab, sRGB -> CMYK, or RGB -> RGB device-link conversion expressed as a 3D LUT. The wrapper validates the per-axis grid sizes and OutputComponents range; the underlying RegisterSampledFunction validates BitsPerSample / Order / total byte count against the expected Sample payload size. Mismatched buffers raise from the primitive with a clear diagnostic.
  • Sample byte layout: row-major over the 3D grid with R fastest, then G, then B; OutputComponents bytes per cell at BitsPerSample=8. Total = GridR * GridG * GridB * OutputComponents bytes. Higher bit depths (12, 16) require caller-packed payloads matching the spec's MSB-first byte packing rules.
  • Visible user impact: a colour-management workflow can now express a 17x17x17 RGB -> CMYK ICC simulation in one line with a precomputed sample array rather than building /Domain / /Range / /Size manually. The returned indirect THPDFStreamObject plugs into any Function-consuming API: Pattern Type 2 axial / radial shading /Func slots, Separation tint transforms via RegisterSeparationLUT, /TR transfer-function ExtGState, /BG black-generation ExtGState.
  • Backward compatibility: the existing RegisterSampledFunction primitive is unchanged; the new wrapper is a thin add. Callers needing non-default /Domain (e.g. Lab L* in [0..100], a* / b* in [-128..127]) or non-rectangular Range continue to use RegisterSampledFunction directly.
  • Candidate #4 status: this is the only slice of the matrix candidate "3D Function Type 0". The high-level wrapper makes the existing N=3 primitive ergonomic for the typical colour-management use case; candidate #4 closes.

2026-05-19 Version 2.92.0

  • Added `RegisterHalftoneType5(ColorantNames, Halftones, DefaultHalftone, HalftoneName)` to close out the ExtGState `/HT` halftone family. Type 5 is a multi-component dict keyed by colorant name (e.g. /Cyan /Magenta /Yellow /Black /Red /Green /Blue /Gray plus any registered Separation/DeviceN spot color) whose values are indirect halftone refs to Type 1/6/10/16 sub-halftones. CMYK prepress workflows can now give each ink its own screen frequency / angle / dot shape in a single ExtGState entry.
  • Parallel-array signature: `ColorantNames` and `Halftones` are matched in pairs, validated to be the same length. Empty colorant names or nil halftones throw with the offending index. The optional `DefaultHalftone` becomes the dict's /Default fallback that consumer readers use for any colorant not in the explicit list (typical for documents that include unexpected spot colors).
  • Output dict structure (PDF 1.7 ISO 32000-1 10.5.5.2): /Type /Halftone + /HalftoneType 5 + optional /HalftoneName + one /ColorantName entry per pair + optional /Default. Returns the indirect THPDFDictionaryObject so the caller can pass it to v2.64 `RegisterHalftoneState` like any other halftone object.
  • Validation: at least one component or a DefaultHalftone is required (an empty Type 5 with no /Default is semantically meaningless). The helper does NOT validate that the supplied sub-halftones are non-Type-5 (spec forbids Type 5 nesting), since the caller would have to actively construct a self-referencing graph and real-world prepress workflows don't trip this.
  • Visible user impact: a prepress engineer publishing a four-color brochure can now set up four independent screens (Cyan at 105° / 75 lpi, Magenta at 75° / 75 lpi, Yellow at 90° / 75 lpi, Black at 45° / 75 lpi via Type 1 spot functions, or substitute any with Type 6 threshold rasters for FM screening) and wrap them in one ExtGState. The same ExtGState is then activated by any drawing call that needs the per-channel screens.
  • Candidate #3 status: this is the second and final slice of the ExtGState `/HT` halftone family. The full feature set (Type 1 spot function in v2.64, Type 5 multi-component in v2.92, Type 6/10/16 threshold-stream in v2.91) is now PDF 1.7 spec-complete. Candidate #3 closes.

2026-05-19 Version 2.91.0

  • Added three threshold-stream halftone builders to round out the ExtGState `/HT` halftone family started in v2.64: `RegisterHalftoneType6` (8-bit threshold raster, Width/Height-keyed), `RegisterHalftoneType10` (8-bit threshold raster with parallelogram cells via Xsquare/Ysquare for angled screens), and `RegisterHalftoneType16` (16-bit threshold raster, 65536-level precision for high-end prepress).
  • Each builder returns an indirect /Type /Halftone stream object that callers can pass to the v2.64 `RegisterHalftoneState` to wrap in an ExtGState /HT entry, exactly like Type 1 spot-function halftones. Optional /HalftoneName, /TransferFunction or /TransferFunction /Identity entries match the Type 1 pattern.
  • Size validation: Type 6 requires len(ThresholdBytes) = Width*Height; Type 10 requires Xsquare^2 + Ysquare^2 (PDF 1.7 10.5.5.4 stacked-squares layout); Type 16 requires Width*Height*2 (big-endian uint16 pairs, caller-supplied). Mismatched buffers raise an Exception with both the actual and expected byte count.
  • Shared `_HotPDFCreateHalftoneStream` internal helper deduplicates the per-type construction: indirect stream allocation, /Type /Halftone + /HalftoneType N header, optional name + transfer function entries, payload write + /Length stamp. Each public function only validates type-specific size keys and forwards.
  • Visible user impact: prepress workflows that need user-defined screen patterns (custom dot shapes, frequency-modulated screens, angled cells) now have a HotPDF entry point. Caller supplies the threshold raster (which they typically build offline from a screen-pattern generator), HotPDF emits the spec-conformant /Type /Halftone stream object and wires it into the ExtGState resource chain.
  • Spec coverage: PDF 1.7 ISO 32000-1 10.5.5.3 (Type 6) + 10.5.5.4 (Type 10) + 10.5.5.5 (Type 16). Auto-bumps to PDF 1.2 (matches the Type 1 floor since halftones landed together).
  • Candidate #3 status: this is the first slice of the "ExtGState /HT halftone remaining types" matrix candidate. Type 5 multi-component halftone (dict keyed by colour-component name, delegating to Type 1/6/10/16 sub-dicts for per-channel screens, CMYK prepress) is the second and final slice and lands in v2.92.

2026-05-19 Version 2.90.0

  • Added a high-level marked-content sequence helper pair: `THotPDF.BeginTaggedContent(Role, Parent): THPDFDictionaryObject` + `THotPDF.EndTaggedContent`. The helper auto-allocates a per-page MCID, emits the BDC operator on the current page's content stream, builds the StructElem under Parent, and wires it into /ParentTree in one call. End-result: PDF/UA-1 callers can tag visible content paragraphs without plumbing MCID counters manually.
  • Typical usage: `Para := Doc.BeginTaggedContent('P', Root); Doc.CurrentPage.TextOut(...); Doc.EndTaggedContent;` The returned StructElem can be chained into AddStructureElement('Span', Para, ...) for sub-structure or carry /Alt / /ActualText via the v2.88 attribute path.
  • New THPDFPage.FNextMCID field tracks per-page MCID allocation; resets per page (lazily initialised to 0 in BeginDoc init). The low-level BeginMarkedContentMCID / EndMarkedContent operators (in HotPDF since v2.11) remain available for callers that want explicit MCID control.
  • Pipeline integration: BeginTaggedContent emits "/Role <> BDC" on the current page content stream, then calls AddStructureElement(Role, Parent, PageIdx, MCID) which builds the StructElem and runs RegisterMCIDForPage (the v2.11 path that lazily stamps /StructParents on the page dict and grows the per-page array in /ParentTree). EndTaggedContent emits "EMC" on the current page. Nesting works because BDC/EMC pairs nest implicitly via page content stream linear order.
  • Visible user impact: a PDF/UA-1 candidate document can now tag every visible paragraph, heading, span, or figure caption with semantic structure in two lines flanking the drawing call. veraPDF accepts the resulting page-content marked-content sequences + StructTree linkage; assistive tech follows the document outline into the marked content and announces the role + text.
  • Candidate #1 status: fourth and final slice. PDF/UA-1 producer-side surface now covers wrapper opt-in (v2.87) + /Alt / /ActualText attributes (v2.88) + annot /StructParent + OBJR reverse-wiring (v2.89) + page-content BDC/EMC auto-wrap (v2.90). Callers still own structure-tree topology decisions (heading hierarchy, list nesting, table headers), but the mechanical wiring is fully automated. Candidate #1 closes.

2026-05-19 Version 2.89.0

  • Added annotation-to-structure wiring per PDF 1.7 14.7.4.4. New `THotPDF.RegisterAnnotForStructure(AnnotDict, StructElem)` helper allocates a fresh /ParentTree key, stamps /StructParent on the annotation, stores the StructElem at /ParentTree[Key] (single-ref form for annotations, distinct from the array-of-refs form used by page MCID entries), and appends <</Type /OBJR /Obj <annotRef>>> to the StructElem's /K array. Screen readers can now navigate from any tagged annotation up to its parent structure element and the reverse.
  • Added `THPDFPage.AddURILink(Rect, URL): THPDFDictionaryObject` so PDF/UA-1 callers have a return-value-bearing URI annotation entry point. The existing AddGoToLink / AddGoToRLink / AddLaunchLink methods don't return their annot dicts (an API limitation from earlier); the new AddURILink returns the indirect annotation dict so it can be passed directly to RegisterAnnotForStructure.
  • Visible user impact: a PDF/UA-1 candidate document can now tag a hyperlink with its semantic structure in three lines: `LinkAnnot := Doc.CurrentPage.AddURILink(R, 'https://example.org/'); LinkElem := Doc.AddStructureElement('Link', RootElem, -1, -1); Doc.RegisterAnnotForStructure(LinkAnnot, LinkElem);` Assistive technology following the document outline can now reach the link, announce its purpose, and present it as activatable.
  • Implementation note: the v2.11 EmitTaggedPDFRoot loop that emits /ParentTree /Nums entries used a hard cast `THPDFArrayObject(FParentTree.Items[I])`. v2.89 widens the cast to THPDFObject because annotation entries store a single dict rather than an array; THPDFArrayObject.AddObject already dispatches by runtime IsIndirect probe, so the cast was always nominal. v2.11+ smokes that store arrays in FParentTree remain byte-stable.
  • Spec note: PDF 1.7 14.7.4 allows /ParentTree values to be either an array (for marked-content sequences indexed by MCID) or a direct StructElem ref (for annotations, beads, and other non-MCID structural elements). v2.89 is the first slice that exercises the single-ref form; the array form continues to work via the v2.11 AddStructureElement-with-MCID path.
  • Candidate #1 status: third slice of PDF/UA producer-side fleshing-out. One slice remains: page-content BDC/EMC auto-wrap so AddTextField + CurrentPage.TextOut routes can emit marked-content sequences without manual operator calls. After that the PDF/UA producer surface lands at a satisfying baseline.

2026-05-19 Version 2.88.0

  • Added an overloaded `AddStructureElement` signature accepting optional `/Alt` and `/ActualText` attribute strings. PDF/UA-1 (ISO 14289-1) requires non-text content (figures, formulas, decorative glyphs) to provide screen-reader-friendly alternatives; the new overload makes them straightforward to attach to a StructElem in a single call.
  • `/Alt` (alternative description) is what assistive technology reads aloud for non-text content - typical use: Figure structure elements wrapping an image or chart. `/ActualText` is what copy-paste returns when the visible glyphs differ from the intended Unicode - typical use: decorative ligatures, drop caps, hard-to-extract calligraphy.
  • Both attributes are PDF text strings emitted through the standard byte-safe SaveStringObject escape path; empty strings skip the corresponding entry so existing four-arg callers retain byte-identical output. The original four-arg `AddStructureElement(Role, Parent, PageIndex, MCID)` signature is preserved via Pascal `overload` directive.
  • Visible impact: a PDF/UA-1 candidate document can now mark a logo figure with `AddStructureElement('Figure', Root, 0, 0, 'Company logo', '')` and a ligature span with `AddStructureElement('Span', Root, 0, 1, '', 'A bus stop')`; veraPDF and assistive technology consume the resulting /Alt and /ActualText entries without further setup.
  • Pipeline placement: the v2.88 overload forwards to the v2.11 base implementation (so StructTreeRoot wiring + /K array append + /Pg back-link + MCID/ParentTree registration all run unchanged) and only adds the two attribute entries afterwards. No mutation of existing call sites; v2.74-v2.87 byte stability preserved.
  • Candidate #1 status: this is the second slice of the PDF/UA producer-side fleshing-out. Follow-ups: page-content BDC/EMC auto-wrap (so AddTextField + CurrentPage.TextOut routes can emit marked-content sequences without manual operator calls); /StructParents wiring for annotations (so screen reader navigation reaches link / form widgets too).

2026-05-19 Version 2.87.0

  • Added PDF/UA-1 (ISO 14289-1) high-level opt-in via the new `PDFUACompliance` property. Setting True before BeginDoc / EndDoc auto-enables every PDF/UA-1 producer-side wrapper requirement HotPDF can satisfy without caller cooperation: /MarkInfo /Marked = true, /StructTreeRoot stub, /Lang (default 'en' when empty), /ViewerPreferences /DisplayDocTitle = true, /Metadata XMP stream, and the AIIM-registered pdfuaid:part = 1 identifier in the XMP packet.
  • One property instead of six toggles: previously a caller seeking PDF/UA-1 conformance had to set EnableTaggedPDF + EnableXMPMetadata + Lang + ViewerPreferences := [vpDisplayDocTitle] + Title individually and then craft a custom XMP packet to add the pdfuaid namespace. v2.87 collapses all of that to PDFUACompliance:=True; the individual flags remain available and are honoured if the caller sets them explicitly.
  • XMP packet update: BuildXMPPacket now emits xmlns:pdfuaid="http://www.aiim.org/pdfua/ns/id/" plus <pdfuaid:part>1</pdfuaid:part> whenever PDFUACompliance is True. veraPDF and other PDF/UA-1 validators detect documents as PDF/UA-1 candidates by this exact namespace + identifier triple.
  • Visible user impact: a PDF written with `Doc.Title := '...'; Doc.PDFUACompliance := True; Doc.BeginDoc; ...; Doc.EndDoc;` validates as PDF/UA-1 candidate in veraPDF without further caller setup, provided the document body authors a real structure tree via AddStructureElement + marked-content sequences. Government / education / accessibility-mandated publishing workflows now have a single switch.
  • Caller responsibility: PDF/UA-1 also requires structure tree integrity (every glyph belongs to a tagged structure element), /Alt or /ActualText on figure / formula content, sane tab order, valid heading hierarchy, etc. The opt-in covers the Catalog / metadata wrapper that machine-checkable validators inspect first; full semantic compliance still depends on the caller building the structure tree honestly via AddStructureElement and the marked-content BDC / EMC operators.
  • Backward compatibility: PDFUACompliance defaults False so v2.74-v2.86 callers retain byte-identical output. Existing EnableTaggedPDF / EnableXMPMetadata flags continue to work in isolation when the new opt-in is False.
  • This is the first slice of matrix candidate #1 (Tagged PDF / PDF/UA producer-side fleshing-out). Follow-ups (v2.88+): /Alt and /ActualText support on AddStructureElement, page content BDC / EMC helpers for AddTextField & CurrentPage.TextOut routes, and /StructParents wiring for annotations.

2026-05-19 Version 2.86.0

  • Extended RegisterUnicodeTTF's cmap parser to handle OpenType format 12 (segmented full-Unicode coverage). v2.75-v2.85 only parsed format 4 (legacy BMP segment mapping); modern fonts that include format 12 alongside format 4 now use the richer table. Subtable selection prefers format 12 over format 4 because format 12 is authoritative for the full codepoint range.
  • New `_TTFParseCmapFormat12` walker: reads the 16-byte header (format / reserved / length / language / numGroups) then iterates numGroups 12-byte groups (startCharCode / endCharCode / startGlyphID). For each codepoint C in [startCharCode..endCharCode], the glyph ID is startGlyphID + (C - startCharCode). Bounds-checked on every multi-byte read; truncated or malformed groups silently skipped.
  • Subtable scoring updated:
    • platform 3 (Microsoft) + encoding 10 (Unicode full) + format 12: 120 (best)
    • platform 0 (Unicode) + encoding 4 or 6 + format 12: 110
    • any other format 12: 105
    • platform 3 + encoding 1 (Unicode BMP) + format 4: 100 (legacy BMP fallback)
    • platform 0 + encoding 3..4 + format 4: 80
    • platform 3 + encoding 10 + format 4: 70
    • any other format 4: 50
  • SMP scope: CpToGid output array stays BMP-sized ($10000 entries × 2 bytes = 128 KB) because our Identity-H encoding uses 2-byte CIDs (0..65535). Format 12 SMP entries (U+10000+) are PARSED but not stored; the format 12 walker clips endCharCode to $FFFF before iterating. Full SMP coverage in /CIDToGIDMap would require a different encoding (Identity-V with surrogate-pair CMap or Adobe-Japan1) and is out of scope.
  • Visible impact: fonts that ship only format 12 (some modern CJK fonts, Segoe UI Emoji / Symbol) now correctly populate /W, /CIDToGIDMap, /FontFile2, and the v2.84 subsetter. Pre-v2.86 these fonts hit the cmap-parse silent fallback and skipped all four stream emissions, dropping back to the v2.74 metric-only result.
  • Byte-stability: fonts with format 4 only (Arial, Tahoma) still go through the same code path and produce identical CpToGid output. Fonts with BOTH format 4 and format 12 now use format 12 for BMP, which typically yields the same BMP mapping (format 12 is a strict superset). Existing v2.74-v2.85 smokes for these fonts remain byte-stable.
  • This closes the v2.4.0-v2.86.0 dense PDF spec compliance phase. The remaining v2.86+ work (SMP encoding via surrogate-pair CMaps, font-specific GSUB stylistic alternates, more advanced typography) falls outside the "basic Type 0 / Identity-H + AcroForm Unicode" scope and is deferred indefinitely pending real user demand.

2026-05-19 Version 2.85.0

  • Added opt-in Arabic Presentation Form-B contextual shaping via the new `AutoShapeArabic` property. When True, the three BuildUnicode*FieldContent helpers replace basic Arabic letters (U+0621..U+064A) with their position-appropriate presentation form (U+FE70..U+FEFF) BEFORE bidi reversal. Consumer PDF readers no longer need an in-process text shaper (HarfBuzz / FreeType / GSUB binary lookup) to pick the correct positional form at render time.
  • Shaping algorithm: scan the logical-order UTF-16 buffer; for each Arabic letter find the previous and next non-Transparent letters (NSMs / harakat U+064B..U+065F + alef khanjareeya U+0670 transparent). Determine position from joining-class context: D (dual-joining: BEH, TEH, SEEN, LAM, MEEM, NOON, etc.) supports isolated / initial / medial / final based on whether the surrounding strong letters allow joining on their respective sides; R (right-joining: ALEF, DAL, ZAL, REH, ZAIN, WAW, etc.) supports only isolated / final.
  • Direct user impact (with AutoShapeArabic=True): input "بسم" (BEH + SEEN + MEEM) emits Tj with presentation forms FE91 (BEH initial), FEB4 (SEEN medial), FEE2 (MEEM final). After RTL reversal the Tj operand is FEE2 FEB4 FE91, which any PDF reader renders as connected Arabic script even without a built-in shaper.
  • Coverage: 35 basic Arabic letters (HAMZA variants, ALEF variants, the 28-letter Arabic alphabet, YEH MAKSURA). Static Unicode-standard lookup replaces a font-specific GSUB binary parse, so the shaping works with any font that has glyphs in U+FE70..U+FEFF (Arial, Tahoma, Segoe UI Arabic, Noto Sans Arabic, Amiri, etc.).
  • NOT covered in this slice: Persian / Urdu extension letters (U+0671..U+06D3); LAM-ALEF ligature glyphs (FEFB / FEFC); Mongolian / Syriac (different shaping models); font-specific GSUB stylistic alternates beyond the 4-position basic model.
  • Byte-stability: AutoShapeArabic defaults False so all v2.74-v2.84 callers retain byte-identical /AP output. Callers that already pre-shape upstream (or use a consumer-side shaper) are unaffected.
  • Pipeline placement: shaping runs in each Build*UnicodeFieldContent helper AFTER UTF-8 decode but BEFORE bidi reversal, so the post-shape presentation forms get correctly RTL-reversed when AutoDetectFormBidi / FormUnicodeRTL is also enabled.
  • Remaining UAX #9 work: SMP cmap format 12 (U+10000+ codepoint coverage in RegisterUnicodeTTF) still ahead.

2026-05-18 Version 2.84.0

  • Added a file-based TrueType subsetter that fires at EndDoc time. After RegisterUnicodeTTF + AddTextField, the document accumulates a per-codepoint usage set during BuildUnicode*FieldContent emission; EndDoc translates the used codepoints to glyph IDs, walks composite glyph closure, and rebuilds the embedded font program with only those glyphs. The v2.82 raw embed is replaced; remaining table structure stays valid because the subsetter keeps glyph IDs unchanged (glyf/loca compaction only).
  • Direct user impact: PDFs with embedded Unicode TTFs shrink dramatically. A "Hello World" document with Arial embedded was ~350 KB compressed in v2.82; v2.84 drops to ~10-15 KB. CJK fonts with the typical 1000-3000 glyph subset shrink from ~14 MB to ~500 KB-2 MB compressed.
  • Subsetter algorithm: keep glyph IDs unchanged so cmap, hmtx, /CIDToGIDMap, /W stay valid. For each glyph index 0..numGlyphs-1, the new glyf table either copies the original bytes (if used) or emits an empty entry via loca[i+1] = loca[i] (if not used). loca format (short / long per head.indexToLocFormat) is preserved. Per-table checksums + head.checksumAdjustment are recomputed for spec-strict TTF validation.
  • Composite glyph closure: when a used glyph has numContours == -1 (composite), the subsetter walks its component list (parsing each component's flags + glyphIndex + variable args/transform skip), marks the components used, and iterates until no new glyphs are added. Bounds-checked walk so malformed composite chains can't infinite-loop.
  • Spec compliance: /BaseFont on the Type 0 dict + CIDFontType2 descendant + /FontName on the FontDescriptor all get a 6-letter AAAAAA+ subset prefix derived from a deterministic FNV-1a hash of the used-glyph set. Per spec 9.6.4, the prefix indicates the font program has been subsetted; without it spec-strict readers warn about embedded font modifications.
  • Usage tracking infrastructure: a 65536-entry FUnicodeUsedCps[] boolean array is lazily allocated on first RegisterUnicodeTTF call. Each of the three BuildUnicode*FieldContent helpers (single-line, multi-line, comb) marks code units as it emits UTF-16BE hex Tj operands. SetFormUnicodeFontDict('', nil) and a future BeginDoc reset clear the state.
  • Failure-mode safety: any subsetter step that can't complete (missing tables, malformed loca, composite closure failure, compression failure) leaves the v2.82 raw embed in place. The PDF stays valid; only file size suffers. This preserves the v2.82 reliability guarantee while delivering the subset benefit on the happy path.
  • What is NOT in this slice: OpenType GSUB Arabic/Syriac/Devanagari contextual joining; SMP cmap format 12 (still BMP-only). Both deferred to v2.85+.

2026-05-18 Version 2.83.0

  • Added PDF 1.7 ISO 32000-1 9.10.3 /ToUnicode CMap to RegisterUnicodeTTF. The Type 0 font dict now carries a /ToUnicode indirect reference pointing to an Adobe-Identity-UCS CMap stream, so consumer readers can extract Unicode text from the PDF. Without /ToUnicode, copy-paste from a v2.74-v2.82 PDF produced raw CID indices instead of Unicode codepoints; screen readers couldn't read aloud the content; PDF/A compliance failed.
  • CMap structure: minimal Adobe-Identity-UCS CMap with /CIDSystemInfo (Adobe / UCS / 0), /CMapName /Adobe-Identity-UCS, /CMapType 2, a single codespacerange covering <0000>..<FFFF>, and a single bfrange mapping <0000> <FFFF> to <0000> (identity). The PostScript CMap text compresses to ~150 bytes via FlateDecode, so the embedding overhead is negligible.
  • Identity mapping rationale: our v2.74-v2.82 setup is Identity-H + Adobe-Identity-0 + CID = Unicode codepoint, so the ToUnicode reverse mapping is genuinely identity for the entire BMP. A single bfrange entry captures the whole mapping; no per-codepoint bfchar list needed.
  • Visible user impact: text selection / copy-paste from PDF readers (Adobe, Foxit, Chrome PDF viewer) now produces the original Unicode codepoints instead of CID garbage. Screen readers (NVDA, JAWS, VoiceOver) can read aloud the AcroForm text content for accessibility. PDF/A-1/2/3 compliance becomes possible (PDF/A requires /ToUnicode on all Type 0 fonts).
  • Pipeline placement: /ToUnicode emission lives OUTSIDE the WArrayValid try/except block because the identity mapping does not depend on cmap parse succeeding. Even if /W / /CIDToGIDMap / /FontFile2 were silently skipped due to corrupt font tables, the ToUnicode CMap is still emitted and semantically correct.
  • Stream object: indirect THPDFStreamObject + /Filter /FlateDecode (no /Length1 since ToUnicode is a text stream, not a font program). Attached to the Type 0 wrapper dict via Result.AddValue('ToUnicode', ...). Shared zlib compression pattern with v2.77 /CIDToGIDMap and v2.82 /FontFile2.
  • What is NOT in this slice: per-codepoint bfchar entries (would enable mapping non-identity CIDs to multi-char Unicode sequences like fi -> f+i, but our Identity setup never needs that); SMP coverage beyond BMP (still 0x0000..0xFFFF range). Both deferred to v2.84+.

2026-05-18 Version 2.82.0

  • Added PDF 1.7 ISO 32000-1 9.6.4 + 9.9 /FontFile2 raw font embed to RegisterUnicodeTTF. The actual font program (the binary TTF / OTF bytes) now ships inside the PDF as a /FontFile2 stream attached to the FontDescriptor, so consumer readers no longer have to resolve the /BaseFont name via OS / app font cache. Result: a self-contained PDF that renders correctly on systems lacking the named font.
  • Stream structure: indirect THPDFStreamObject with /Filter /FlateDecode and /Length1 = uncompressed font file size (per spec Table 124). Compression uses TZCompressionStream (zcDefault, windowBits 15) following the v2.77 /CIDToGIDMap pattern. Typical Latin TTFs compress 30-50% (e.g., 700 KB Arial -> 350 KB compressed embed).
  • Pipeline integration: emission happens in the same WArrayValid branch as the v2.75 /W array and v2.77 /CIDToGIDMap, so when cmap parsing fails (corrupt subtable / unsupported format) all three feature streams are skipped together for graceful degradation back to the v2.74 metric-only result.
  • Visible impact: v2.74-v2.81 PDFs were technically Type 0 / CIDFontType2 composite fonts but the font program was MISSING. Consumer readers had to match the /BaseFont (PostScript name) against their own font cache. On systems with the exact named font installed, rendering happened to work; on systems without, the reader either substituted a different font (giving wrong glyphs / metrics) or rendered placeholder boxes. v2.82 ships the font program in the PDF itself - the same font program the producer parsed in RegisterUnicodeTTF - so consumer rendering is deterministic regardless of OS font availability.
  • Trade-off: PDFs gain ~150-500 KB per embedded font for typical Latin TTFs (compressed size); CJK fonts add 2-20 MB compressed. This is the SAME bloat consumer-side font substitution avoided, but at the cost of nondeterministic rendering. Subsetting (v2.83+ follow-up) will reduce the bloat by stripping unused glyphs from the embedded program.
  • Spec notes: /BaseFont in our v2.82 emission stays at the original PostScript name (no "AAAAAA+" subset prefix) because we embed the full font. The 6-letter prefix per spec 9.6.4 only applies when the font program has been subsetted; v2.83+ will add the prefix as part of subsetter integration.
  • What is NOT in this slice: font subsetting (so a single embedded font adds the full file size to the PDF). ToUnicode CMap (for PDF/A copy-paste). SMP cmap format 12 (U+10000+ codepoints still unmapped). All deferred to v2.83+.

2026-05-18 Version 2.81.0

  • Mirrored UAX #9 W4 (separator-between-digits) and W5 (terminator-adjacent-to-digit) into the LTR paragraph reorder path. Previously RTL-only (since v2.72.0 / v2.73.0); v2.81 extracts both passes into shared `_ApplyUAX9W4Rules` and `_ApplyUAX9W5Rules` helpers so both paragraph directions apply the same weak-rule transformations.
  • Refactor: RTL path Step 1b (W4) and Step 1c (W5) inline code replaced with single-line helper calls. The helpers take the Wide string + Classes[] array and modify Classes[] in place; logic is identical to the v2.72/v2.73 inline implementations, so RTL output is byte-stable across the refactor.
  • LTR path adds Step 1c (W4) and Step 1c.2 (W5) between W1 NSM inheritance (Step 1b) and W7 EN-preceded-by-L (Step 1d). Subsequent N rules (Step 1e) and I rule (Step 1f) renumbered to keep the pipeline order explicit.
  • Visible LTR impact: in mixed LTR contexts where an RTL strong precedes a digit/ET expression, W5 now promotes the ET to EN before N1 has a chance to lump it into the R run. Example: "Hi shin $1" (LTR para with internal shin Hebrew letter then "$1") previously emitted "Hi $ shin 1" with the $ pulled into the R run and swapped with shin. v2.81 emits "Hi shin $1" with $ staying adjacent to its digit. The same applies to W4: "Hi shin $1,2 widgets" preserves "$1,2" as a logical chunk instead of splitting around the comma.
  • Byte-stable scenarios in LTR path: pure-LTR inputs with no preceding R (e.g. "12,345 widgets") see no visible change because W7 sor=L already absorbs all ENs to L; LTR inputs containing only L + RTL with no digits or ETs are also unaffected. Existing v2.79 LTR smokes (smoke_bidi_n_rules_ltr, smoke_bidi_ltr_reorder) continue to pass byte-identically.
  • Pipeline order (RTL path unchanged): W1 -> W4 -> W5 -> W7 -> N0 -> N1 -> N2 -> I2 -> L2. LTR path now: W1 -> W4 -> W5 -> W7 -> N0 -> N1 -> N2 -> I rule -> L2. The two paths share W4/W5/W7/N rules helpers; only classification (Step 1a) and final L2 walk differ between RTL and LTR.
  • Remaining UAX #9 work: OpenType GSUB Arabic / Syriac / Devanagari contextual joining, and RegisterUnicodeTTF follow-ups (ToUnicode CMap, SMP cmap format 12, /FontFile2 + subsetting) still ahead.

2026-05-18 Version 2.80.0

  • Added Unicode UAX #9 W7 European Number bridging: an EN whose scan-back finds a strong L (or sor=L when the scan reaches the start of the paragraph buffer) becomes L. The transformation runs after W4 (separator-between-digits) and W5 (terminator-adjacent-to-digit) on both RTL and LTR paragraph paths, so any separator promoted to EN by W4 is also subject to L-bridging.
  • Visible RTL impact: an input like "Hello 123 Arabic" in an RTL paragraph previously emitted the visual layout "Arabic SP 123 SP Hello" because the EN run sat between L (Hello) and AL (Arabic) and the N rules split the LTR phrase into two independent runs (with SP at embedding level between them). v2.80 W7 converts the EN to L (since L precedes), and N1 absorbs the leading SP between Hello and 123 into the L run; the entire "Hello 123" becomes one level-2 substring reversing as a unit. Final visual layout becomes "Arabic SP Hello SP 123", matching strict UAX #9 behaviour.
  • W7 does NOT fire when: the EN's nearest strong is R or AL (Arabic letters precede the digits); the EN is at the start of the buffer in an RTL paragraph (sor=R); or there is no L anywhere preceding the EN. Pre-v2.80 behaviour preserved in these cases - the v2.72/v2.73 W4/W5 baseline smokes remain byte-stable.
  • LTR paragraph effect: W7 also runs on the LTR path (for spec consistency), with sor=L when the scan reaches the buffer start. Under HotPDF's simplified single-embedding LTR I rule, L and EN both land at level 0 so the EN -> L transformation does not directly change Levels[], but it does change N1 absorption pattern: a SP between L and an L-bridged EN now satisfies N1's same-direction condition and joins the run. For pure-LTR inputs with no internal RTL the change is a byte-stable no-op; for LTR inputs containing trailing RTL (e.g. "Hello 123 shalom") the SP placement relative to the RTL chunk shifts by one position.
  • W6 (residual ES/CS/ET -> ON per strict UAX #9) is implicit in HotPDF's simplified Classes[] table: ES, CS, ET, WS, ON all share class 0 (bcOther), and the N rules treat class 0 uniformly. The W6 promotion to ON is a no-op in our model since N1/N2 do not differentiate ON from WS. Documented in the code comment alongside the W7 helper.
  • Pipeline order (RTL path): W1 NSM inheritance -> W4 ES/CS between digits -> W5 ET adjacent to digits -> W7 EN preceded by L -> N0 paired brackets -> N1 same-direction NI absorption -> N2 default-to-embedding -> I2 level assignment -> L2 reverse-of-reverse. Same ordering on LTR path minus W4/W5 (those remain RTL-only in this slice; LTR W4/W5 port is the next candidate).
  • New shared `_ApplyUAX9W7Rules(Classes, ParaIsRTL)` helper procedure operates on Classes[] in-place. Scan-back skips weak/NSM/EN/AN positions; only L/R/AL count as strong types. When the scan exhausts without finding a strong, the paragraph direction (sor) is used as the implicit strong type. RTL path passes ParaIsRTL=True, LTR path passes False.
  • Remaining UAX #9 work: LTR-path W4/W5 port (currently RTL-only), OpenType GSUB Arabic / Syriac / Devanagari contextual joining, and RegisterUnicodeTTF follow-ups (ToUnicode CMap, SMP cmap format 12, /FontFile2 + subsetting) still ahead.

2026-05-18 Version 2.79.0

  • Mirrored the v2.78.0 N0/N1/N2 rules into the LTR paragraph reorder path. Fixes the symmetric bug where a multi-word RTL phrase embedded in an LTR paragraph (e.g. English sentence containing multiple Hebrew or Arabic words separated by spaces) had its words appear visually SWAPPED to an RTL reader because the inter-word space stayed at level 0 and broke the RTL phrase into independent R runs. With v2.79 N1, the space is absorbed into the surrounding R run; the whole phrase becomes one level-1 substring and the L2 reverse keeps the words in logical order.
  • Refactored the LTR paragraph path (`_ApplyUAX9L2ReversalLTRPara`) to mirror the RTL path layout: classify each UTF-16 code unit into a Classes[] byte array, apply W1 NSM inheritance on Classes[] (was Levels[] pre-v2.79), then call a shared `_ApplyUAX9NRules` helper that operates on Classes[] given an embedding-direction parameter (1 = L for LTR, 2 = R for RTL). The I rule then derives Levels[] from Classes[]. Both RTL and LTR paths now share a single N rules implementation.
  • Helper `_ApplyUAX9NRules(Wide, Classes, EmbedDirCls)` accepts the embedding direction as a class value (1 or 2) and applies N0 paired bracket resolution + N1 same-direction NI absorption + N2 default-to-embedding uniformly. RTL path passes 2 (R); LTR path passes 1 (L). All v2.78 RTL-side behaviour is preserved byte-for-byte.
  • Visible LTR-side impact: "Hi shalom olam World" with logical input H i SP shin lamed vav final-mem SP ayin vav lamed final-mem SP W o r l d previously emitted with the two Hebrew words reversed against each other (each word individually correct, but their RELATIVE positions swapped). v2.79 preserves logical word order so an RTL reader sees "Hi shalom olam World" with the Hebrew chunk in expected order. Same fix applies to bracket-wrapped multi-word RTL phrases via N0 + N1 cooperation.
  • Pipeline order in LTR path (mirroring RTL): classify into Classes[] -> W1 NSM inheritance -> N0 paired brackets -> N1 same-direction NI absorption -> N2 default-to-embedding -> I rule (derive Levels[]) -> L2 reverse level-1 runs. W4/W5 digit-handling passes are not yet ported to the LTR path; LTR paragraphs with digit + separator combinations route through N rules unchanged.
  • Byte-stable on existing LTR smokes: pure LTR ASCII, single-word RTL in LTR para, RTL + LTR mixed without inter-RTL-word spaces all produce identical Levels[] arrays after the refactor (the W1 NSM inheritance step is observably equivalent whether it works on Levels[] or Classes[]). The v2.67 / v2.69 / v2.71 LTR smokes continue to pass byte-identically.
  • Remaining UAX #9 work: W6/W7 residual weak cleanup, W4/W5 digit handling on LTR path (currently RTL-only), OpenType GSUB Arabic / Syriac / Devanagari contextual joining, and RegisterUnicodeTTF follow-ups (ToUnicode CMap, SMP cmap format 12, /FontFile2 + subsetting) still ahead.

2026-05-18 Version 2.78.0

  • Added Unicode UAX #9 simplified N0 paired-bracket resolution + N1 neutral-between-same-strong absorption + N2 neutral-default-to-embedding rules in the RTL paragraph reorder pipeline. Fixes the visible bug where a multi-word LTR phrase embedded in an RTL paragraph (e.g. Hebrew or Arabic surrounding English text with internal spaces) was emitted with the WORDS reversed because the space between L runs stayed at the embedding level and broke the LTR phrase into independent sub-runs. With N1, the space is absorbed into the surrounding L run so the whole phrase becomes one level-2 substring and L2 reverse-of-reverse keeps it in logical order.
  • N0 brackets: 22 BMP bracket pairs covered, from ASCII paren / square / curly to mathematical / typographical (ceiling, floor, math angle, math white square, white curly) to CJK punctuation brackets (angle, double angle, corner, white corner, black lenticular, tortoise shell, white tortoise, white square) to full-width forms. BD16 stack-based paired-bracket detection (cap 63 entries per spec); pair resolution scans inside for first strong direction (L, R, AL, EN, AN, where EN/AN count as R per spec), with look-before-opening-bracket fallback when inside differs from embedding direction.
  • N1 absorption: walks Classes[] for maximal runs of bcOther (neutral), finds left + right non-NSM strong types, and when both sides have the same direction influence (both L, or both R-equivalent counting EN/AN as R) the whole run takes that direction. Real-world impact: spaces / commas / periods / colons inside English phrases embedded in Arabic / Hebrew paragraphs are now grouped with the phrase rather than splitting it.
  • N2 default: remaining bcOther positions (no surrounding strong, or mismatched left/right) take the embedding direction. For RTL paragraphs this is R; class 2. After N2 every Classes[] entry is in the resolved set {1..6} so I2 level assignment no longer relies on bcOther defaulting to level 1.
  • Visible impact: previously HotPDF emitted "aleph aleph SP a b c SP d e f SP aleph aleph" (logical input with English phrase "abc def" in an RTL paragraph) as "aleph aleph SP d e f SP a b c SP aleph aleph", with the words swapped because the inter-word space was at level 1 (= embedding RTL). v2.78 produces "aleph aleph SP a b c SP d e f SP aleph aleph" with the phrase intact. Brackets around the embedded phrase now resolve correctly via N0 + L4 mirroring at the consumer.
  • Pipeline order: W1 NSM inheritance -> W2/I2 (EN/AN class assignment in BIDI table, not in this pass) -> W4 ES/CS between digits -> W5 ET adjacent to digits -> N0 paired brackets -> N1 same-direction absorption -> N2 embedding default -> I2 level assignment -> L2 reverse-of-reverse. N rules slot between W and I; existing W-rule outputs feed N rules as input.
  • Byte-stable for existing smokes that do not have a multi-word LTR phrase inside RTL: pure RTL, pure LTR (function returns input), single-word LTR in RTL (no internal neutrals to absorb), Arabic + formatted digits with space + comma (spaces transition from bcOther to R-equivalent but land at the same level so the L2 reverse output is unchanged). The v2.66/v2.67/v2.68/v2.71/v2.72/v2.73 baseline smokes continue to pass byte-identically.
  • Remaining UAX #9 work: W6/W7 residual weak cleanup, LTR-paragraph mirror of the N rules (multi-word RTL phrase embedded in LTR paragraph), OpenType GSUB Arabic / Syriac / Devanagari contextual joining, and RegisterUnicodeTTF follow-ups (ToUnicode CMap, SMP cmap format 12, /FontFile2 + subsetting) still ahead.

2026-05-18 Version 2.77.0

  • Added the PDF 1.7 ISO 32000-1 9.7.4.2 /CIDToGIDMap stream to RegisterUnicodeTTF. Each Unicode codepoint (CID) in the BMP now maps to the actual glyph index inside the embedded font program. Before v2.77 HotPDF's Type 0 / CIDFontType2 + Identity-H setup left the /CIDToGIDMap entry unspecified, which defaults to /Identity per spec - the consumer reader then indexed the font's glyph table by CID directly, hitting the wrong glyph for nearly every character because real font glyph tables rarely align glyph index N with Unicode codepoint N.
  • Implementation reuses the cmap parse already performed for the v2.75 /W array. The CpToGid[0..$FFFF] in-memory table is serialised as a 131072-byte stream (2 bytes big-endian per CID) and FlateDecode-compressed; typical real-font shrink is 80-95% because the vast majority of BMP codepoints have no glyph and serialise as 0x0000. The compressed stream is attached as an indirect /CIDToGIDMap entry on the CIDFontType2 descendant dict.
  • Visible impact: a PDF generated via RegisterUnicodeTTF + SetFormUnicodeFontDict + AcroForm Tx widgets now renders the same glyphs in the consumer reader that the source font would render natively. Pre-v2.77 PDFs relying on the /Identity default produced visibly wrong rendering (random glyphs in place of the intended Latin / digit / punctuation characters) on spec-strict readers; readers that fall back to interpreting the cmap by Unicode anyway happened to render correctly but did so via non-standard behaviour.
  • Pipeline placement: /CIDToGIDMap construction happens inside the same WArrayValid branch that builds /W, so the two attachments share the cmap walk and only the second compression pass is new. If cmap / hmtx / maxp parse fails, the silent fallback skips both /W and /CIDToGIDMap; the consumer reader then sees the pre-v2.77 dict and falls back to its default cmap interpretation.
  • What is NOT in this slice (deferred to v2.78+): SMP cmap format 12 (U+10000+ codepoints still map to GID 0 in the table), /FontFile2 stream embedding (so the consumer reader still resolves the /BaseFont name via its own font matching), font subsetting (the 128 KB raw map is full-range), and ToUnicode CMap for copy-paste / accessibility round-trips.

2026-05-18 Version 2.76.0

  • Wired the v2.75.0 TTF /W advance-width array into HotPDF's own multiline word-wrap algorithm. The v2.65.0 BuildUnicodeMultilineFieldContent.CodeUnitAdvance helper now consults a per-codepoint advance cache populated by RegisterUnicodeTTF, replacing the 0.5/1.0 narrow/wide heuristic with the actual glyph metrics. Producer-side line breaks now align with consumer-side /W rendering: the wrap point HotPDF picks matches where the consumer reader will actually wrap when drawing the /Tj operand.
  • New private fields: FAcroFormUnicodeAdvances (dynamic array of Word, sized 65536 or empty) caches per-codepoint advance widths in PDF design units (1000 per em). FAcroFormUnicodeAdvancesActive flag distinguishes "cache populated by RegisterUnicodeTTF" from "no font loaded (heuristic falls back)". RegisterUnicodeTTF populates these fields during the same hmtx + cmap walk that builds the /W array, so no extra parsing pass is needed.
  • CodeUnitAdvance lookup order: if FAcroFormUnicodeAdvancesActive AND cache has a non-zero entry for the code unit, divide the scaled width by 1000.0 and return as em fraction; otherwise fall through to the v2.65 heuristic (narrow for ASCII / Latin-1, wide for CJK / Hangul / Hiragana / Katakana / full-width). Surrogate-pair halves still flow through the heuristic since the cache is BMP-only.
  • Cache lifecycle: cleared on THotPDF.Create (default empty / inactive), cleared on SetFormUnicodeFontDict('', nil) when the caller explicitly resets the Unicode font registration, and re-populated on each subsequent RegisterUnicodeTTF call. Holds 128 KB (64K codepoints * 2 bytes) only when at least one TTF has been loaded; instances that never use Unicode fonts pay zero memory cost.
  • RegisterUnicodeFontDict (v2.70.0) and RegisterUnicodeTTF (v2.74.0) without a /W array (e.g. missing maxp / cmap / hmtx tables) leave the cache empty / inactive, so multiline word-wrap falls back to the v2.65 heuristic - byte-identical output to v2.65 for those paths.
  • Visible impact: a multiline AcroForm Tx widget with a typical Latin font (Arial, Segoe UI, Times) now wraps at character boundaries that match the consumer reader's actual layout. Without v2.76, the v2.65 heuristic treated every ASCII char as 0.5 em width; for a real font where "M" is ~0.8 em and "i" is ~0.3 em the heuristic over- or under-estimates by 30-60%, causing the consumer to wrap differently than HotPDF planned, leading to text overflow or short lines.
  • Single-line BuildUnicodeTextFieldContent and comb BuildUnicodeCombFieldContent do not use CodeUnitAdvance and remain unaffected (single-line has no wrap; comb uses equal-width cells).
  • What is NOT in this slice: SMP cmap format 12 (U+10000+ codepoints still flow through heuristic per surrogate half), ToUnicode CMap, /FontFile2 stream embedding, font subsetting. Those land in v2.77+ slices.

2026-05-18 Version 2.75.0

  • Extended v2.74.0 THotPDF.RegisterUnicodeTTF with hmtx + maxp + cmap parsing so the resulting CIDFontType2 descendant carries a real per-codepoint /W advance-width array. Consumer readers now use real font metrics for text width rendering instead of falling back to /DW = 1000 for every glyph - text within an AcroForm /AP appearance stream now occupies the same width as the source font would render it.
  • New unit-level helpers parse the three additional SFNT tables: _TTFParseMaxpNumGlyphs (reads numGlyphs from maxp), _TTFParseHmtxAdvances (per-glyph advance width array from hmtx, sized by numberOfHMetrics from hhea), _TTFFindAndParseCmap + _TTFParseCmapFormat4 (Unicode codepoint -> glyph ID lookup via the BMP segment-mapping format 4 subtable). _TTFBuildWArray composes the PDF 1.7 9.7.4.3 form 1 sparse /W array, grouping consecutive non-default-width codepoints into [startCID [w1 w2 w3 ...]] runs and skipping codepoints whose width equals /DW = 1000 to trim array size.
  • Cmap subtable selection prefers Microsoft Unicode BMP (platform 3, encoding 1, format 4) - the universal Windows font cmap layout. Falls back to Unicode platform (0, encoding 3 or 4) for cross-platform OpenType fonts, then platform 3 encoding 10 (Unicode full / format 4 only), then any other format 4. Non-format-4 subtables (format 0, 2, 6, 8, 12, 13, 14) are ignored in this slice - SMP coverage via format 12 lands in v2.76+.
  • The /W array attaches to the CIDFontType2 descendant dict via direct walk: Type0 -> /DescendantFonts -> [0] -> AddValue('W', WArray). v2.70 RegisterUnicodeFontDict signature unchanged; the descendant dict is mutated in place after assembly.
  • FUnit widths from hmtx are scaled to PDF design units (1000 per em) using the head table's unitsPerEm value. Codepoints with no glyph mapping (cmap returns 0 / .notdef) treated as DW = 1000 and omitted from /W. For a typical Latin font (Arial, Segoe UI, Times) the resulting /W carries 500-1500 codepoint entries; for CJK fonts the array can grow to 10K+ entries.
  • What is NOT in this slice (deferred to v2.76+): /W width data is NOT yet consumed by HotPDF's own multiline word-wrap algorithm - the v2.65 BuildUnicodeMultilineFieldContent still uses the 0.5/1.0 narrow/wide heuristic for line-break calculation; the /W array is purely for consumer-side rendering accuracy. Full producer-side perfect-wrap (using /W to drive the wrap math) lands in v2.76+. Also deferred: ToUnicode CMap from cmap reverse mapping, /FontFile2 stream embedding, font subsetting, and SMP cmap format 12.
  • /W parsing has a try/except fallback - if maxp / cmap / hmtx is missing or any subtable is malformed, RegisterUnicodeTTF silently continues with the v2.74 metric-only result; the consumer reader falls back to /DW = 1000 for every codepoint. No silent corruption: tables are bounds-checked on every multi-byte read.

2026-05-18 Version 2.74.0

  • Added THotPDF.RegisterUnicodeTTF, a file-based wrapper around v2.70.0 RegisterUnicodeFontDict that loads a TTF / OTF font, parses the head, hhea, OS/2, post and name tables, scales the FUnit metrics into PDF design units (1000 per em), and hands the values to RegisterUnicodeFontDict to build the complete Type 0 / CIDFontType2 + Identity-H composite font dictionary. The returned dict is ready for SetFormUnicodeFontDict - callers no longer need to know either the font's exact metric values or the SFNT binary format.
  • API: RegisterUnicodeTTF(FontPath, LogicalName=''). FontPath is an absolute or relative path to a .ttf / .otf file. LogicalName is reserved for v2.75+ (where it may disambiguate fonts that share a PostScript name).
  • Parsed tables: head (unitsPerEm, FontBBox xMin/yMin/xMax/yMax in FUnits), hhea (ascender, descender in FUnits), OS/2 (usWeightClass for /StemV estimate, sCapHeight, fsSelection for italic bit), post (italicAngle in FIXED 16.16 -> degrees, isFixedPitch -> /Flags bit 1), name (PostScript name at nameID 6 from Windows or Mac platform, falling back to full name at nameID 4). Bounds checking on every table offset / length + on every multi-byte read so a malformed font surfaces as a clear exception rather than a memory fault.
  • Auto-derived metrics: /FontBBox L/B/R/T scaled to PDF design units, /Ascent + /Descent scaled, /CapHeight from OS/2 sCapHeight (falls back to ascent for pre-version-2 OS/2 tables), /ItalicAngle from post.italicAngle, /StemV estimated as 50 + (usWeightClass - 400) / 5 clamped to [50, 250], /Flags = Symbolic + FixedPitch (if post says so) + Italic (if fsSelection bit 0 set or italicAngle != 0). /BaseFont uses the parsed PostScript name; falls back to "UnnamedTTF" if the name table yields nothing usable.
  • Errors raise descriptive exceptions: missing file, implausible size (< 12 bytes or > 64 MB), unrecognised sfntVersion (must be 0x00010000 / 'OTTO' / 'true' / 'typ1'), missing required tables (head / hhea / OS/2 / post / name), table-too-small for the fields we need.
  • What this slice does NOT include (deferred to v2.75+): the /W advance-width array from hmtx (needs maxp for numGlyphs + cmap for Unicode -> glyph mapping; today the v2.65 multiline word-wrap keeps its 0.5/1.0 heuristic), a ToUnicode CMap from cmap (today the v2.56+ Identity-H mapping assumes CID == Unicode code point), embedding the raw font bytes as a /FontFile2 stream, and font subsetting. With v2.74.0 the consumer reader still has to resolve the /BaseFont name via its own font-matching machinery.
  • Smoke smoke_unicode_ttf.dpr loads a Windows system font (probes %SystemRoot%\Fonts for arial.ttf / segoeui.ttf / times.ttf / verdana.ttf in order) and verifies RegisterUnicodeTTF returns a Type 0 dict carrying file-parsed metrics. Verifier checks the resulting /BaseFont is not a stub placeholder, FontBBox / Ascent / Descent / ItalicAngle are in plausible ranges for a real font.

2026-05-18 Version 2.73.0

  • Added Unicode UAX #9 simplified W5 (European Terminator adjacent to European Number) for RTL paragraphs. Fixes the visible bug where Arabic text with currency / percent symbols — "$12.50", "50%", "€100", "شار 12.50$ ريال" — left the currency or percent glyph stranded at the boundary between the digit substring and the surrounding RTL text. With v2.73 W5, ETs adjacent to digits join the digit run and reverse with the digits, preserving the currency-and-number unit as one LTR substring inside the RTL paragraph.
  • W5 ETs recognised (codepoint set): U+0023 # (number sign), U+0024 $ (dollar), U+0025 % (percent), U+00A2..U+00A5 (¢ £ ¤ ¥), U+00B0 ° (degree), U+00B1 ± (plus-minus), U+066A ٪ (Arabic percent), U+2030..U+2031 (‰ ‱ per-mille / per-ten-thousand), U+20A0..U+20CF (Currency Symbols block including € ₹ ₽ ₩ etc.). A maximal sequence of ETs adjacent to an EN on either side (skipping NSMs only) is transformed to EN.
  • Spec extension: HotPDF's simplified W5 also transforms ETs adjacent to AN (Arabic-Indic digits) to AN, in addition to the spec's EN-only rule. This keeps ASCII and Arabic-Indic digit contexts symmetric so "٥٠٪" (Arabic-Indic 50 + Arabic percent) treats the percent the same as "50%" treats the ASCII percent.
  • Concrete fix: "شار $12.50 ريال" (logical UTF-16 0634 0627 0631 0020 0024 0031 0032 002E 0035 0030 0020 0631 064A 0627 0644). v2.72 output: 0644 0627 064A 0631 0020 0031 0032 002E 0035 0030 0024 0020 0631 0627 0634 — the $ glyph stranded at memory position 11, between the digit substring and the trailing space, reading awkwardly. v2.73 output: 0644 0627 064A 0631 0020 0024 0031 0032 002E 0035 0030 0020 0631 0627 0634 — "$12.50" intact as one LTR substring inside the RTL flow.
  • BIDI_Class table correction: U+066A ARABIC PERCENT SIGN moved from bcAL to bcOther (so the W5 ET pass can detect and transform it). The Arabic block lookup now has carve-outs for the digit ranges (bcAN), the harakat / shadda block (bcNSM), the alef khanjareeya (bcNSM), the decimal / thousands separators (bcAN), AND the percent sign (bcOther + ET via W5).
  • Pipeline order: W1 NSM inheritance (LTR path explicit, RTL implicit) -> W4 ES/CS between digits -> W5 ET adjacent to digits -> I2 level assignment. W5 runs after W4 so a W4-transformed separator that became EN is correctly identified as "EN adjacent" by W5 for the next pass; multi-rule interactions inside complex digit-with-separator-and-currency expressions resolve in a single pipeline traversal.
  • Pure text without ET adjacent to digits is byte-identical to v2.72 output. Existing v2.59-v2.72 smoke (RTL Arabic / Hebrew / SMP RTL / NSM positioning / LTR-para reorder / digit handling / W4) all stay byte-stable.
  • Remaining UAX #9 work (W6 / W7 cleanup, N0 bracket pair via UAX #9 BD16 stack scan, N1 / N2 neutral resolution, OpenType GSUB Arabic / Syriac contextual joining, file-based RegisterUnicodeTTF) still ahead.

2026-05-18 Version 2.72.0

  • Added Unicode UAX #9 simplified W4 (ES / CS between same-typed digits) for RTL paragraphs. Fixes the visible bug where Arabic text with formatted numbers — "السعر 12,345 ريال" (price with thousands comma), "12.50" (decimal point), "12:30" (time colon) — split the digit run at the separator in the reversed /Tj output, producing visually-broken numbers that read backwards within the RTL flow. With v2.72 W4 the separator joins the digit run, both reverse together once and then reverse together again at the whole-string pass, preserving the digit + separator logical order inside the RTL paragraph.
  • W4 separators recognised: European Separator U+002B (+) / U+002D (-), Common Separator U+002C (,) / U+002E (.) / U+002F (/) / U+003A (:) / U+00A0 (NBSP), and Arabic Common Separator U+060C (Arabic comma). Each transforms to EN when sandwiched between two ENs, or to AN when sandwiched between two ANs. Mixed-typed neighbours (EN + AN, or digit + non-digit) leave the separator unchanged in the bcOther / level-1 / surrounding-RTL stream as before. Simplified vs full UAX #9 W4: the peek across NSMs is handled (NSMs near the separator do not break the W4 lookup), but multi-separator chains follow a per-position transformation rather than the spec's "single" separator restriction (low practical impact since real-world numbers rarely have adjacent separators).
  • Concrete fix: "شار 12,345 ريال" (logical UTF-16 starts with Arabic letters, then space, "1" "2" "," "3" "4" "5", then space, then Arabic letters). v2.71 previous output: 0644 0627 064A 0631 0020 0035 0034 0033 002C 0031 0032 0020 0631 0627 0634 — digits split at the comma, reversed within each fragment ("345" before "12"), reads as "345,12" inside the LTR substring user perception. v2.72 fixed output: 0644 0627 064A 0631 0020 0031 0032 002C 0033 0034 0035 0020 0631 0627 0634 — "12,345" intact as one LTR substring, user reads it naturally in numeric order.
  • BIDI_Class table correction (v2.72.0): U+066B ARABIC DECIMAL SEPARATOR and U+066C ARABIC THOUSANDS SEPARATOR now classify as bcAN (return value 5) per Unicode UnicodeData rather than as bcAL. These separators are spec-defined as part of Arabic numeric formatting, so they naturally join the Arabic-Indic digit run for L2 reorder without needing the W4 pass. U+066A ARABIC PERCENT SIGN now classifies as bcAL (placeholder; spec ET / European Number Terminator handling is deferred to W5).
  • Internal refactor: the level-assignment step in _ApplyUAX9L2Reversal now builds an explicit Classes[] parallel array (one byte per UTF-16 code-unit position) so the W4 pass can peek at neighbour classes without re-decoding codepoints. Surrogate-pair codepoints share both the Classes[] and Levels[] byte across high+low halves. Equivalent refactor for _ApplyUAX9L2ReversalLTRPara is deferred (W4 has no visible impact in LTR paragraphs because EN / AN already stay at base level 0 in the simplified single-embedding-level model).
  • Pure RTL / LTR / mixed text with no W4 separators between same-typed digits is byte-identical to v2.66 through v2.71 output. Existing v2.59-v2.71 smoke (RTL Arabic / Hebrew / SMP RTL / NSM positioning / LTR-para reorder / digit handling / detector) all stay byte-stable.
  • Remaining UAX #9 work (W5 ET adjacent to EN, W6 / W7 cleanup, N0 bracket pair via UAX #9 BD16 stack scan, N1 / N2 simplified neutral resolution, OpenType GSUB Arabic / Syriac contextual joining, file-based RegisterUnicodeTTF) still ahead.

2026-05-18 Version 2.71.0

  • Fixed the NSM (non-spacing mark) positioning bug in Arabic / Hebrew text reordering. Previously, naive whole-string reversal moved combining marks (Arabic harakat such as fatha / kasra / damma / shadda; Hebrew niqqud such as sheva / kamatz / patah; Latin combining diacritics in the U+0300..U+036F block) to the wrong side of their base character in the reversed /Tj stream. PDF readers' combining-mark renderers then attached the NSM to the preceding glyph in display order — the wrong base. The visible bug: "ALEF + FATHA + BA" (Arabic) rendered the fatha on the BA glyph instead of the ALEF.
  • Added UAX #9 W1 simplified NSM inheritance plus a group-aware reverse pass shared by all three L2 reorder algorithms (RTL paragraph step 2 + step 3, LTR paragraph single-pass). The reverse pass now treats [base, NSM*] as a unit: when walking right-to-left, NSMs buffer onto a stack and flush in original logical order immediately after the next non-NSM base in the output. Surrogate-pair codepoints continue to stay intact across the same reverse pass, and SMP-range NSMs (rare) are handled symmetrically.
  • Concrete fix: "ALEF + FATHA + BA" (logical UTF-16 0623 064E 0628) previously emitted as 0628 064E 0623 (whole-reverse, fatha between BA and ALEF). With v2.71.0 NSM-aware reverse the same input emits 0628 0623 064E — BA on the left, ALEF in the middle, FATHA after ALEF so the renderer attaches it to the correct base.
  • Internal BIDI_Class table extended with bcNSM class (return value 6) for the major combining-mark blocks: General Combining Diacritical Marks U+0300..U+036F (Latin / general accents), Hebrew niqqud U+0591..U+05BD plus the scattered U+05BF / U+05C1..U+05C2 / U+05C4..U+05C5 / U+05C7 entries (carved out of the Hebrew R range), Arabic harakat U+064B..U+065F and alef khanjareeya U+0670 (carved out of the Arabic AL range). NSMs are still skipped during paragraph-direction P2/P3 first-strong scan.
  • For LTR paragraphs, an explicit W1 NSM-level-inheritance pass runs after the initial level assignment: each NSM adopts the level of the preceding non-NSM codepoint, so an Arabic NSM following an AL base joins that base's level-1 run for the single-pass L2 reverse. RTL paragraphs implicitly inherit the level (NSM and R / AL both resolve to level 1 in the simplified single-embedding-level model), so no separate W1 pass is needed there.
  • Pure RTL / LTR / mixed inputs with no NSMs are byte-identical to v2.66 / v2.67 / v2.68 / v2.69 / v2.70 output (no NSM means no behavioural change). Existing v2.59 RTL Arabic / v2.66 LTR-in-RTL / v2.67 LTR-para / v2.68 EN-AN / v2.69 SMP-RTL smoke all stay byte-stable.
  • Remaining UAX #9 work (W3-W7 detailed weak-type transitions, N0-N2 neutral + bracket-pair resolution, OpenType GSUB Arabic / Syriac contextual joining, file-based RegisterUnicodeTTF) still ahead.

2026-05-18 Version 2.70.0

  • Added THotPDF.RegisterUnicodeFontDict helper that assembles a complete PDF 1.7 ISO 32000-1 9.7 / 9.7.6 Type 0 / CIDFontType2 + Identity-H composite font dictionary in one call. Callers using the v2.56.0 SetFormUnicodeFontDict AcroForm /AP path no longer need to hand-code the ~30 lines of CIDSystemInfo / FontDescriptor / CIDFontType2 / Type 0 / Identity-H sub-dict construction. Returns an indirect THPDFDictionaryObject ready to pass to SetFormUnicodeFontDict.
  • API: RegisterUnicodeFontDict(BaseFont, FontBBoxL/B/R/T, Ascent, Descent, CapHeight, ItalicAngle, StemV, Flags, DefaultWidth). All metric arguments have sensible defaults (CJK / Arabic embedded-font geometry: FontBBox [-1000 -300 2000 1300], Ascent 1000, Descent -300, CapHeight 750, StemV 100, Flags 4 = Symbolic, DW 1000). Override individually for Latin / Hebrew / Cyrillic fonts with smaller bbox and non-symbolic Flags = 32.
  • The dict structure built matches the spec-correct Type 0 + CIDFontType2 layout used by smoke_acroform_cid_rtl / smoke_bidi_* throughout v2.59-v2.69: Adobe / Identity / Supplement 0 CIDSystemInfo, FontDescriptor with all 9.8.2 mandatory entries, Identity-H encoding, CIDFontType2 descendant in the DescendantFonts array, /DW for default character width. The /CIDToGIDMap defaults to /Identity (CID == GID) when not specified, which is what Identity-H + Adobe-Identity-0 ordering implies.
  • Future v2.71+ slice will add a file-based RegisterUnicodeTTF that parses a .ttf / .otf, extracts real metrics + hmtx /W advance widths for perfect-wrap calculations, embeds the font as /FontFile2, generates a ToUnicode CMap from cmap, and calls RegisterUnicodeFontDict internally for the structural pieces.

2026-05-18 Version 2.69.0

  • Extended the internal Unicode BIDI_Class lookup table to cover the Supplementary Multilingual Plane (SMP) right-to-left script blocks at U+10800..U+10FFF. Paragraph direction detection and L1-L2 reorder now correctly recognise Phoenician, Imperial Aramaic, Palmyrene, Nabataean, Hatran, Lydian, Meroitic Hieroglyphs / Cursive, Kharoshthi, Old South Arabian, Old North Arabian, Manichaean, Avestan, Inscriptional Parthian / Pahlavi, Psalter Pahlavi, Old Turkic, Old Hungarian, Hanifi Rohingya (AL, the script in active use today for the Rohingya language in Burma), Garay, Yezidi, Arabic Extended-C (AL), Old Sogdian, Sogdian, Old Uyghur, Chorasmian, and Elymaic.
  • SMP RTL paragraphs now auto-detect as RTL via DetectBidiParagraphDirection (the first strong character scan walks across surrogate pairs and matches the appropriate R / AL class) and run the v2.66 RTL L1-L2 reorder for visual UTF-16BE Tj output. LTR paragraphs containing embedded SMP RTL substrings will additionally reverse those runs when EnableLTRParaReorder is set (v2.67 opt-in path); otherwise they emit in logical order and rely on the consumer reader's intrinsic BIDI.
  • Surrogate pair preservation continues to hold across all L2 reversal passes: SMP RTL codepoints stay as intact high+low pairs in the /Tj hex stream while the surrounding paragraph reorders correctly.
  • v2.67 smoke test (smoke_bidi_ltr_reorder) assertion #7 had used U+10840 IMPERIAL ARAMAIC LETTER ALEPH to demonstrate "SMP codepoint treated as bcOther in LTR paragraphs." That assertion is updated as part of this release to reflect the now-correct bcR classification: U+10840 in an LTR paragraph becomes part of the level-1 R/AL run and gets reordered to visual position alongside any neighbouring Hebrew / Arabic substrings.
  • Remaining UAX #9 work (W1 NSM inheritance + W3-W7 weak-type transitions + N0-N2 neutral + bracket-pair resolution + I1 explicit-level resolution + OpenType GSUB Arabic / Syriac contextual joining) still ahead. With SMP RTL coverage in place, all standardised Unicode RTL scripts now classify correctly for paragraph direction and basic L1-L2 reorder.

2026-05-18 Version 2.68.0

  • Fixed visible direction bug for digits embedded in Arabic / Hebrew paragraphs. Added Unicode UAX #9 weak-class BIDI_Class entries for European digits (EN, U+0030..U+0039) and Arabic-Indic digit ranges (AN, U+0660..U+0669 + U+06F0..U+06F9), with simplified UAX #9 I2 + L2 level assignment: in an RTL paragraph these digit codepoints now get resolved level 2 (same as embedded LTR runs) rather than level 1 alongside the surrounding RTL text. The two-pass L2 reorder then preserves their logical order inside the visual RTL flow, matching the spec-correct "شارع 12345" user reading even when the paragraph is rendered LTR for the PDF /Tj stream.
  • Concrete fix: an input "شار 12345" (logical UTF-16 0634 0627 0631 0020 0031 0032 0033 0034 0035) previously emitted as the Tj operand 0035 0034 0033 0032 0031 0020 0631 0627 0634 (whole-string reverse - digits inverted, the visible bug). With the v2.68 fix the same input emits 0031 0032 0033 0034 0035 0020 0631 0627 0634 - digits stay 1-2-3-4-5 in reading order while the Arabic word still renders in visual RTL.
  • DetectBidiParagraphDirection now treats EN and AN as weak per UAX #9 P2: digit-only or digit-leading paragraphs no longer anchor RTL on the digits; the first STRONG character (L / R / AL) still wins. Empty / weak-only input still falls back to LTR per P3. Smoke battery verifies pure digits return LTR while digits-then-Arabic returns RTL (Arabic AL is the first strong) and digits-then-Latin returns LTR (Latin L is the first strong).
  • The Arabic block range U+0600..U+06FF was split in the lookup table to carve out the two digit ranges as bcAN; the surrounding U+0600..U+065F + U+066A..U+06EF + U+06FA..U+06FF stay bcAL. Other Arabic block codepoints (punctuation, percent / per-mille signs, currency, etc.) keep the bcAL classification for now; finer-grained weak-class handling (ET / CS / ON / NSM) lands in a future release alongside the rest of the W1-W7 / N0-N2 pipeline.
  • Pure-RTL input with no digit runs is byte-identical to v2.66 / v2.67 output (no W-class transitions trigger). LTR paragraphs are unaffected (digits already render LTR by paragraph base direction). The fix lights up automatically for any existing caller using AutoDetectFormBidi or FormUnicodeRTL with UseUAX9LevelReversal=True (defaults).
  • Remaining UAX #9 + GSUB scope (W1 NSM inheritance + full W3/W4/W5/W6/W7 + N0-N2 brackets + I1 explicit level resolution + OpenType GSUB Arabic / Syriac contextual joining + SMP RTL scripts BIDI_Class) still ahead.

2026-05-18 Version 2.67.0

  • Added producer-side Unicode UAX #9 L2 reordering for LTR paragraphs containing embedded RTL substrings. New property THotPDF.EnableLTRParaReorder (default False) opts callers into spec-strict / reader-independent /AP output: an LTR paragraph with embedded Hebrew / Arabic is reordered at /AP generation time so consumer readers do not need to run their own intrinsic BIDI pass on logical-order RTL substrings.
  • Single-pass algorithm symmetric to v2.66.0's RTL-paragraph L1-L2: LTR paragraph base level 0; each strong-R / strong-AL codepoint resolves to level 1; everything else stays at level 0. UAX #9 L2 lowest odd level is 1, so the loop has one pass: reverse each maximal level-1 substring (contiguous R/AL run). LTR chars and weak / neutral chars copy unchanged. Surrogate pairs preserved across the reversal.
  • Public method THotPDF.ApplyUAX9L2ReversalLTRPara exposes the new algorithm standalone (UTF-8 in, UTF-8 out). Distinct from v2.66.0 ApplyUAX9L2Reversal: that method's ParagraphRTL=False path remains v2.66 passthrough so callers depending on the v2.66 contract see byte-identical output; use the new method for LTR-direction reorder.
  • EnableLTRParaReorder defaults False, preserving v2.59-v2.66 byte-identical /AP output for LTR-paragraph callers. The toggle has no effect when UseUAX9LevelReversal=False (the v2.59 naive reverse never reorders LTR paragraphs); the two toggles are otherwise independent.
  • Full UAX #9 W1-W7 weak-type transitions + N0-N2 neutrals + bracket-pair resolution + I1-I2 explicit-level resolution + OpenType GSUB shaping remain v2.68+ scope.

2026-05-18 Version 2.66.0

  • Added Unicode UAX #9 L1-L2 run-aware reversal for AcroForm Unicode appearance streams. New property THotPDF.UseUAX9LevelReversal (default True) replaces the v2.59.0 naive whole-string reverse for RTL paragraphs with a spec-correct two-pass L2 reordering that preserves the internal logical order of LTR substrings embedded inside RTL paragraphs.
  • Concrete fix: an input "<Hebrew>AB" (logical UTF-16 05D0 05D0 0041 0042) previously emitted as the Tj operand hex 0042 0041 05D0 05D0 ("BAאא"), which inverts the embedded LTR "AB" - wrong per UAX #9. With L1-L2 active the same input now emits 0041 0042 05D0 05D0 ("ABאא"). Pure-RTL input with no L runs (e.g. the v2.59.0 pre-shaped Arabic smoke) remains byte-identical to the legacy naive reverse.
  • Public method THotPDF.ApplyUAX9L2Reversal exposes the new algorithm as a standalone helper (UTF-8 in, UTF-8 out) for callers that build their own appearance streams or want to preview the reordering before /AP generation.
  • Simplified single-embedding-level subset of UAX #9 I1 + L2 used in this slice: in an RTL paragraph each strong-L codepoint gets level 2; everything else (R, AL, weak, neutral, surrogate) stays at level 1. L2 then reverses level-2 runs first (restoring logical order to embedded LTR substrings) and finally reverses the whole-string level-1 segment (producing the visual RTL layout). Surrogate pairs are preserved across both passes.
  • Full UAX #9 W1-W7 (weak-type transitions) + N0-N2 (neutrals + bracket pair) + I1-I2 (explicit-level resolution) + reordering for LTR paragraphs with embedded RTL runs remain v2.67+ scope. v2.66.0 covers the most common observable bug: RTL paragraphs with multi-character LTR substrings.
  • UseUAX9LevelReversal defaults True. Set False to revert to v2.59.0 naive whole-string reverse for byte-identical legacy output. Both forms continue to read FormUnicodeRTL and AutoDetectFormBidi the same way.

2026-05-18 Version 2.65.0

  • Added Unicode Standard Annex #9 paragraph-direction auto-detection for AcroForm Unicode AP appearance streams. New property THotPDF.AutoDetectFormBidi switches the BuildUnicode*FieldContent helpers (single-line, multi-line, comb) from the v2.59.0 manual FormUnicodeRTL toggle to per-call detection of paragraph direction. With this, the same setter sequence handles a mixed batch of LTR and RTL fields in one document without manual flag toggling.
  • Direction is resolved by scanning the input for the first strong character (UAX #9 rules P2 + P3) and classifying it with BIDI_Class L (Left), R (Right-to-Left), or AL (Arabic Letter). Strong R or AL anchors the paragraph as RTL; strong L anchors LTR; a paragraph with no strong character defaults to LTR per P3.
  • Recognised BIDI_Class: Strong L - ASCII Latin, Latin-1, Latin Extended, Greek, Coptic, Cyrillic, Armenian, CJK Unified Ideographs, Hangul, Hiragana, Katakana, Yi. Strong R - Hebrew block (U+0590..U+05FF), Hebrew presentation forms (U+FB1D..U+FB4F), N'Ko, Samaritan, Mandaic. Strong AL - Arabic block, Arabic Supplement, Arabic Extended-A, Syriac, Thaana, Arabic presentation forms A + B (U+FB50..U+FDFF + U+FE70..U+FEFF). Surrogate pairs and weak / neutral characters (digits, punctuation, whitespace) skipped during the search.
  • Public method THotPDF.DetectBidiParagraphDirection exposes the detector for callers that want to pre-compute direction (e.g. for /Lang tagging, mirrored neutral output, custom field routing). Accepts a UTF-8 AnsiString and returns Boolean (True = RTL). Empty input returns False per P3.
  • AutoDetectFormBidi defaults to False; existing v2.59.0-v2.64.0 byte-identical behaviour is preserved. When True, the helpers ignore FormUnicodeRTL on a per-call basis. Switch off AutoDetectFormBidi to restore manual control.
  • Full UAX #9 implicit-level + weak-type + neutral-bracket resolution (rules W1-W7, N0-N2, I1-I2, L1-L4) and automatic Arabic / Syriac OpenType GSUB contextual joining still scheduled for v2.66+. Mixed bidi paragraphs still need manual caller intervention beyond paragraph-level direction.

2026-05-18 Version 2.64.0

  • Added PDF 1.2 ISO 32000-1 10.5.5 ExtGState halftone screen (/HT) entry plus the Type 1 spot-function halftone dictionary builder. New THotPDF.RegisterHalftoneType1 builds the PDF 1.7 Table 76 Type 1 halftone dict for caller-controlled CMYK separation screening (frequency in lines per inch, screen angle in degrees, spot shape, optional accurate screening, transfer function, descriptive halftone name). Pair it with THotPDF.RegisterHalftoneState to wrap that dict reference into an ExtGState /HT entry for use with THPDFPage.SetGraphicsState.
  • /SpotFunction accepts either a PDF 1.7 Table 78 built-in spot name (SimpleDot, InvertedSimpleDot, DoubleDot, InvertedDoubleDot, CosineDot, Double, InvertedDouble, Line, LineX, LineY, Round, Ellipse, EllipseA, InvertedEllipseA, EllipseB, EllipseC, InvertedEllipseC, Square, Cross, Rhomboid, DiamondRhomboid) or a caller-built spot Function dict reference. Unknown built-in names raise with the full spec list quoted in the message. Spot-name and Function are mutually exclusive.
  • /TransferFunction is optional. Pass a Function dict reference for caller-controlled tone reproduction, or set TransferFunctionDefault = True to emit /TransferFunction /Identity (consumer-reader default). The two forms are mutually exclusive.
  • RegisterHalftoneState accepts either a halftone dict reference (typically the return value of RegisterHalftoneType1) or HTDefaultName = True for the /HT /Default name-literal reset path that restores the consumer reader's built-in halftone.
  • All emissions auto-bump the document version to PDF 1.2. Frequency must be > 0; invalid spot-function names raise before the dict allocates.
  • Remaining halftone types scheduled for v2.65+: Type 5 multi-component, Type 6 / 10 / 16 threshold-stream halftones. With Type 1 in place the matrix's "ExtGState entries" candidate is one slice from full coverage.

2026-05-18 Version 2.63.0

  • Added PDF 1.4 ISO 32000-1 11.6.5 + 11.7.4 ExtGState soft-mask + alpha-is-shape entries. New THotPDF.RegisterSoftMaskState bundles the transparency-related /SMask and /AIS graphics-state parameters into one ExtGState dict, completing the v2.20.0 + v2.60.0 + v2.61.0 + v2.62.0 + v2.63.0 ExtGState entry coverage chain for every spec-defined entry except the multi-typed /HT halftone screen (scheduled for v2.64+).
  • /SMask carries either the name-literal /None (caller passes SMaskNone = True, common reset pattern after a complex composite) or an indirect soft-mask dict reference (caller passes SMaskDict, advanced use case). The two forms are mutually exclusive. Caller is responsible for the soft-mask dict contents and the underlying transparency Form XObject; v2.64+ will add a dedicated builder helper.
  • /AIS controls whether the current alpha values are interpreted as alpha (false) or as shape (true) for the rest of the graphics state. Sentinel default AIS = -1 skips the /AIS entry entirely. Passing AIS = 0 or 1 emits /AIS false or /AIS true. Any other AIS value raises with a descriptive message.
  • At least one of /SMask (None or Dict) or /AIS must be supplied; an all-defaults call raises. Any entry emission auto-bumps the document to PDF 1.4.
  • Returns the auto-generated ExtGState name (GS1, GS2, ...) for use with THPDFPage.SetGraphicsState. Distinct from v2.42.0 AddImageWithSMask: that path is image-level alpha on a single XObject; this one is the ExtGState-level mask which affects every subsequent painting operator.
  • Remaining ExtGState entry scheduled for v2.64+: /HT halftone screen (Type 1 / 5 / 6 / 10 / 16).

2026-05-18 Version 2.62.0

  • Added PDF 1.2/1.3+ ISO 32000-1 11.7.5.4 ExtGState black-generation + undercolor-removal entries. New THotPDF.RegisterBlackGenerationState bundles the four Function-referencing graphics-state parameters /BG, /BG2, /UCR, /UCR2 into one ExtGState dict for CMYK prepress workflows where RGB-to-CMYK conversion happens at rasterise time (typical for newspapers / packaging / wide-format production where ink-laying-down decisions are output-device specific).
  • /BG and /UCR (PDF 1.2) are 1-input / 1-output Function dicts; /BG2 and /UCR2 (PDF 1.3+) are either Function dicts or the literal name /Default that restores the consumer viewer's default curve. The setter accepts a THPDFObject for each Function slot (typically built through v2.52.0 RegisterSampledFunction) and two Boolean flags BG2DefaultName / UCR2DefaultName for the /Default name-literal form. Mixing a Function dict and the Default flag for the same /BG2 or /UCR2 slot raises with a descriptive message.
  • At least one of /BG, /BG2, /UCR, /UCR2 (Function or Default) must be supplied; all-defaults call raises. Auto-bumps to PDF 1.3 when any /BG2 / /UCR2 entry (Function or Default) is emitted.
  • Returns the auto-generated ExtGState name (GS1, GS2, ...) for use with THPDFPage.SetGraphicsState. Combines naturally with v2.60.0 RegisterTransferFunctionState (per-channel output curves) - a typical CMYK production chain registers a BG/UCR ExtGState for RGB -> CMYK conversion plus a /TR ExtGState for the final tone reproduction on the output device.
  • Remaining ExtGState entries scheduled for v2.63+: /HT halftone screen (Type 1 / 5 / 6 / 10 / 16) and ExtGState-level /SMask + /AIS soft mask.

2026-05-17 Version 2.61.0

  • Added PDF 1.2/1.3+ ISO 32000-1 8.6.5.7 + 8.6.5.8 + 11.7.4 ExtGState print-control entries. New THotPDF.RegisterPrintControlState bundles the prepress-oriented graphics-state parameters /OP, /op, /OPM, /SA, /RI and /FL into one ExtGState dict, suitable for PDF/X-1/X-3/X-4 production workflows that need overprint, stroke adjustment, rendering intent and curve flattening controls.
  • All six parameters are independently optional. Default sentinels: Overprint = -1 (skip /OP + /op), OverprintMode = -1 (skip /OPM), StrokeAdjustment = -1 (skip /SA), RenderingIntent = '' (skip /RI), Flatness < 0 (skip /FL). Caller must supply at least one entry; empty dicts raise.
  • Overprint sets /OP and /op together (PDF spec defaults /op to /OP when only the latter is set, but spec-strict PDF/X pipelines expect both entries explicit). OverprintMode = 1 enables PDF 1.3 CMYK-aware overprint where any zero component paints transparent; auto-bumps the document version to 1.3.
  • RenderingIntent accepts the four standard values 'AbsoluteColorimetric' / 'RelativeColorimetric' / 'Saturation' / 'Perceptual'; any other non-empty string raises. Flatness clamps to the spec range [0.0..100.0]; out-of-range raises.
  • Returns the auto-generated ExtGState name (GS1, GS2, ...) for use with THPDFPage.SetGraphicsState. Combines naturally with v2.20.0 RegisterExtGState (transparency / blend mode) and v2.60.0 RegisterTransferFunctionState (output curves) - typical PDF/X setup chains one of each.
  • Function-driven entries (/BG, /UCR, /BG2, /UCR2 black generation + undercolor removal) and the complex /HT halftone screen + ExtGState-level /SMask soft mask remain as future scope.

2026-05-17 Version 2.60.0

  • Added PDF 1.3+ ISO 32000-1 11.7.3.4 ExtGState /TR (transfer function) support. New THotPDF.RegisterTransferFunctionState method registers an ExtGState dict carrying a /TR entry that applies a tone-reproduction curve to output as colours are rasterised. The curve is supplied as a v2.52.0 Function Type 0 Sampled LUT (or any other Function dict the caller has built); the wrapper pairs naturally with the existing RegisterSampledFunction primitive for hand-tuned gamma / brightness / press-characterisation curves without going through ICC profile machinery.
  • Two invocation paths:
  • SingleFunc form: one Function applied to every output channel (gamma / brightness use cases).
  • PerChannel array form: a 4-element array [TR_R, TR_G, TR_B, TR_A] for RGB-A pipelines or [TR_C, TR_M, TR_Y, TR_K] for CMYK reproduction curves; all four slots must be non-nil per PDF spec.
  • Returns the auto-generated ExtGState name (GS1, GS2, ...) for use with THPDFPage.SetGraphicsState. Apply the state before drawing operators (text / paths / images) that should receive the transfer treatment.
  • Mixed callers (SingleFunc non-nil + PerChannel non-empty) raise an exception so the spec-mandated mutual exclusion is enforced at the API surface.

2026-05-17 Version 2.59.0

  • Added right-to-left direction support for AcroForm Unicode /AP (PDF 1.7 ISO 32000-1 12.7.4.3). New THotPDF.FormUnicodeRTL boolean property; when true, all three Unicode /AP helpers (single-line, multi-line, comb) reverse the UTF-16 code-unit order in their emitted hex Tj operands so pre-shaped Arabic / Hebrew / Syriac content renders in visual right-to-left order. UTF-16 surrogate pairs are kept as a unit during reversal (high stays adjacent to low in original order so SMP code points such as ancient-script letterforms display correctly).
  • Scope: v2.59.0 handles the directional reversal step only. The caller is responsible for two upstream pieces that v2.59.0 deliberately does NOT perform: (1) full UAX #9 bidirectional algorithm resolution (mixed LTR / RTL / numeric / neutral content), and (2) Arabic / Syriac contextual joining (Initial / Medial / Final / Isolated glyph form selection). Callers should pre-shape Arabic to its Unicode presentation forms (U+FB50..U+FDFF + U+FE70..U+FEFF) before passing into AddTextField. Hebrew has no contextual joining so it works without pre-shaping.
  • /V (logical Unicode order) is unchanged: PDF text strings always carry the typed order so spec-following text extractors see the correct logical sequence. Only the /AP Tj operands switch order so the renderer draws right-to-left visually. With this release the v2.55.0-v2.59.0 AcroForm international /AP chain is complete: /RV rich text, single-line / multi-line / comb CID font /AP, and pre-shaped RTL directional reversal. Full UAX #9 + automatic GSUB shaping remain as v2.60.0+ scope.

2026-05-17 Version 2.58.0

  • Extended the v2.56.0 / v2.57.0 caller-supplied Unicode font /AP chain to the ffComb branch (PDF 1.7 ISO 32000-1 12.7.4.3 Tx /Ff bit 25). When SetFormUnicodeFontDict has registered a Type 0 composite font and a Tx widget is added with ffComb + a non-ASCII initial value, HotPDF now emits a real /AP /N appearance stream that lays one CJK code point per equal-width comb cell using an absolute Tm matrix per cell and UTF-16BE hex-string Tj operands.
  • UTF-16 surrogate pairs (code points >= U+10000, e.g. emoji and CJK Extension B/C/D supplementary ideographs) occupy a single comb cell, matching how Acrobat lays out single-glyph composite-font comb cells. The cell-centring formula is identical to the ASCII comb branch from v2.46.0 so visual output mirrors the ASCII single-character layout.
  • ASCII comb fields stay byte-identical to v2.46.0 output. Non-ASCII comb without SetFormUnicodeFontDict still falls back to the v2.46.0 empty-AP placeholder (unchanged). With this release the v2.55.x-v2.58.0 AcroForm international /AP chain (single-line, multi-line, comb) is complete; RTL bidi shaping (UAX #9 + Arabic contextual joining) remains as v2.59.0+ scope.

2026-05-17 Version 2.57.0

  • Extended the v2.56.0 caller-supplied Unicode font /AP path to multi-line text fields (PDF 1.7 ISO 32000-1 12.7.4.3 ffMultiline / Tx /Ff bit 13). When SetFormUnicodeFontDict has registered a Type 0 composite font and a Tx widget is added with ffMultiline + a non-ASCII initial value, HotPDF now word-wraps the UTF-16 text and emits one UTF-16BE hex-string Tj per visible line through Td/T* with /TL leading (matching the v2.46.0 ASCII multi-line layout).
  • Word-wrap budget uses a simple advance heuristic - 1 em per CJK / wide code unit (>= U+2E80) and 0.5 em per narrow code unit (ASCII range and Latin-1 punctuation) - so mixed Latin / CJK content wraps sensibly without reading the font's /W array. Hard line breaks (CR / LF / CRLF) are honoured. Lines that exceed the widget rectangle are truncated to the row count that fits at the current font size; this is the same truncation behaviour as the ASCII multi-line branch.
  • ASCII multi-line, ASCII single-line, ASCII comb, and the v2.56.0 non-ASCII single-line paths all stay byte-identical. The non-ASCII comb path (ffComb + multi-byte text) still falls through to the v2.56.0 single-line helper for now; the ffComb-specific Unicode layout is v2.58.0+ scope, as is RTL bidi shaping (UAX #9 + Arabic contextual joining).

2026-05-17 Version 2.56.0

  • Added caller-supplied Unicode font support for AcroForm /AP appearance-stream rendering (PDF 1.7 ISO 32000-1 12.7.2 + 12.7.4.3). Before this release, the GenerateTextFieldAP path emitted an empty /AP placeholder whenever the field's initial value contained any byte >= 0x80, so non-ASCII text could only render through /NeedAppearances=true regeneration (depending on the reader's installed fonts) or through the v2.55.0 /RV rich-text path (depending on the reader's rich-text engine). v2.56.0 closes that gap: callers can register a Type 0 / CIDFontType2 + Identity-H composite font dict through SetFormUnicodeFontDict and HotPDF emits a real /AP /N appearance stream that selects the registered font via Tf and renders the value as a UTF-16BE hex-string Tj operator.
  • New THotPDF.SetFormUnicodeFontDict(LogicalName, FontDict): registers a logical font name + font dict pair. Once registered, the AcroForm-level /DA, every Tx widget's /DA, and the /AP stream for non-ASCII Tx initial values all switch to the logical name; the AcroForm /DR/Font dict carries the font alongside /Helv and /ZaDb; the Form XObject's /Resources/Font sub-dict also references the same indirect font so the AP is self-contained without depending on /DR resolution at render time. Pass empty + nil to revert to the v2.46.0 /Helv-only behaviour.
  • New THotPDF.CreateIndirectFontDict: public helper that allocates a fresh empty THPDFDictionaryObject registered as an indirect PDF object so callers can build custom font / colour / OCG / Function dicts that must serialise as "N G R" references. Pairs naturally with SetFormUnicodeFontDict for building Type 0 composite font dicts.
  • ASCII Tx fields continue to use /Helv and emit byte-identical /AP output to v2.46.0 / v2.55.0 callers. Multi-byte Tx without SetFormUnicodeFontDict still falls back to the v2.46.0 empty-AP placeholder behaviour (preserved verbatim) for compatibility.
  • Multi-line + comb non-ASCII /AP coverage and RTL bidi shaping (UAX #9 + Arabic contextual joining) remain as v2.57.0+ scope; v2.56.0 covers single-line logical-order CJK / Cyrillic / Vietnamese / Greek and similar scripts.

2026-05-17 Version 2.55.0

  • Added AcroForm rich-text Tx widget support (PDF 1.7 ISO 32000-1 12.7.4.3 + Annex L). The new THPDFPage.AddRichTextField method emits a text widget carrying /RV (rich-text XHTML body), /DS (CSS-like default style), the ffRichText (/Ff bit 26) flag, and the plain-text /V + /DV fallback used by readers that do not parse /RV. Acrobat and Foxit render directly from /RV when the RichText bit is on, picking up font fallbacks from the reader's installed Unicode fonts - so multi-byte content (CJK / Cyrillic / Arabic / accented Latin) displays correctly without HotPDF embedding a CID font in /DR Resources. Embedding a CID font for HotPDF's own appearance-stream (/AP) generator remains future work; rich-text-aware viewers do not need it.
  • Both /V and /RV auto-detect multi-byte input and switch to UTF-16BE hex-string encoding (FE FF BOM + big-endian code units) when needed, matching the existing AddTextField behaviour for international plain-text fields. ASCII content stays as literal PDF strings for byte-identical output to v2.46.0's plain text emit path.
  • Three follow-ups remain in the AcroForm international-character candidate: (1) CID-font /DR Resources path so HotPDF-generated /AP streams cover non-ASCII without relying on the reader's rich-text engine, (2) RTL bidi shaping for Arabic / Hebrew rich text, and (3) the /AA /K keystroke action helper.

2026-05-17 Version 2.54.0

  • Extended PDF 1.7 ISO 32000-1 7.10.2 Function Type 0 (Sampled) with cubic-spline interpolation. RegisterSampledFunction gains a new optional Order parameter (default 1, accepted values 1 or 3 per PDF 1.7 Table 38). Order = 3 emits cubic interpolation across each input axis; Order = 1 keeps the existing linear behaviour exactly byte-identical so v2.52.0 / v2.53.0 callers see no surface change.
  • Added end-to-end regression coverage for the multi-input / multi-output Function Type 0 code path. A new smoke registers a 2-input / 3-output sampled function on a 4 x 4 grid with cubic interpolation and exercises the spec's sample-order rule ("input coordinates increase fastest along the first input dimension"), byte-matching all 16 grid points x 3 channels against an analytic formula re-derived in the Python verifier.
  • Acrobat / Foxit / qpdf all support Order = 3; some older mobile viewers silently fall back to linear interpolation when encountering an unfamiliar Order value. Cubic remains opt-in to preserve maximum reader compatibility.

2026-05-17 Version 2.53.0

  • Added a high-level Separation colour space registrar that uses a sampled Function Type 0 (LUT) as its tint transform (PDF 1.7 ISO 32000-1 8.6.6.4 + 7.10.2). New THotPDF.RegisterSeparationLUT (ColorantName, AlternateCS, Samples) builds on v2.52.0's RegisterSampledFunction primitive so callers can express a full non-linear tint curve as a flat byte stream of (M-tuple) samples without manually constructing a Function Type 0 dictionary.
  • Use the new LUT variant when the colour transition is non-linear (PANTONE Hexachrome-style tint ramps, gamma-corrected density curves, hand-drawn tone curves matching a press characterisation, sRGB-to-spot conversion ICC LUTs without the full ICC profile machinery). Use the v2.45.0 RegisterSeparation when a linear ramp from 0 to one full-strength endpoint is sufficient (the common case for single-ink PANTONE spot colours).
  • Samples are 8-bit RGB / CMYK / Gray bytes ordered (c0_0, c1_0, ..., cM-1_0, c0_1, ..., cM-1_S-1) where S is the grid-point count derived as Length(Samples) / M. S must be at least 2 so the function has interpolable range. Linear interpolation between grid points is the default (PDF 1.7 /Order 1). Returns the registered colour-space name (Sep1, Sep2, ...) for use with THPDFPage.SetFillColorSpace / SetStrokeColorSpace + SetFillColor([tint]) where 0 <= tint <= 1.
  • Internally widened THPDFSeparationParams.TintFunc from THPDFDictionaryObject to the common THPDFObject base so it can hold either a Function Type 2 (v2.45.0 path) or a Function Type 0 stream (v2.53.0 path); both forms serialise identically through THPDFArrayObject.AddObject as an "N G R" indirect reference inside the /Separation array.

2026-05-17 Version 2.52.0

  • Added PDF 1.7 ISO 32000-1 7.10.2 Function Type 0 (Sampled) registration. Sampled functions express an arbitrary input-to-output mapping as a regular N-dimensional grid of M-dimensional output samples; the renderer linearly interpolates between grid points to evaluate intermediate inputs. Use Type 0 where ICC profile machinery is overkill but a hand-tuned colour LUT is desired (tone curves for /TransferFunction in ExtGState, sampled tint transforms for /Separation / /DeviceN beyond the linear Type 2 / arithmetic Type 4 paths already supported, halftone threshold curves, or any other PDF construct that takes a Function dictionary).
  • New THotPDF.RegisterSampledFunction(Domain, Range, Size, BitsPerSample, Samples) emits an indirect /FunctionType 0 stream with /Domain (2N), /Range (2M), /Size (N), /BitsPerSample (1, 2, 4, 8, 12, 16, 24, or 32), /Order 1 (linear interpolation), and the raw bit-packed sample payload. Returns the indirect THPDFStreamObject so callers can attach it wherever a Function dictionary is expected.
  • The Samples byte array length is validated against ceil(GridPoints * M * BitsPerSample / 8); BitsPerSample 8 / 16 / 24 / 32 widths are byte-aligned big-endian, sub-byte widths (1 / 2 / 4 / 12) follow PDF 1.7 7.10.2 MSB-first packing with the final byte zero-padded.
  • Function Type 3 (stitching) and Type 4 (PostScript calculator) have been usable internally through HPDFRegisterFunctionType3 / HPDFRegisterFunctionType4 since v2.18.0 / v2.47.0; this release brings Type 0 to the same level of public API support.

2026-05-17 Version 2.51.6

  • Closed the v2.51.x binary-safety sweep by fixing the last remaining TStringList "Source=Target" round-trip site: FStructRoleMap (PDF 1.7 ISO 32000-1 14.7.3 /StructTreeRoot /RoleMap). Custom struct role names registered through AddStructRoleMap no longer get silently mangled on Windows hosts with CP_ACP=65001 ("Beta: Use Unicode UTF-8" region option); storage migrated to a dynamic array of (Source: TBytes; Target: TBytes) records, with byte-preserving Move() on every insert/read access. Standard PDF struct types (Document, P, H1..H6, Span, Div, etc.) are ASCII per spec so previous releases worked in practice; this fix only matters for callers that map authoring-tool-specific Latin-1 role names onto the standard types.
  • Extended the PDF Name #XX escape (PDF 1.7 7.3.5) to dictionary KEYS in SaveDictionaryObject. Previously dict keys were copied raw to the output stream which left any byte >= 0x80 unescaped and produced spec-non-conformant output - reachable via AddStructRoleMap with a non-ASCII source role. The byte-level trim + #XX escape logic is now in a shared helper (_EscapePDFNameBytes) called from both SaveNameObject (name values) and SaveDictionaryObject (dict keys). ASCII keys (the universal case for HotPDF-generated dicts) escape to byte-identical output, so existing files and tests are unaffected.
  • After this release the v2.51.x binary-safety chain (PubSec recipients, DeviceN ColorantNames, PDF Name value emit, PDF Name key emit, struct role map storage) is structurally complete -no remaining AnsiString-via-UnicodeString round-trip on any byte blob known to HotPDF. 34/34 smokes + 27/27 verifiers pass.

2026-05-17 Version 2.51.5

  • Continued the v2.51.4 binary-safety sweep across two more emission paths that could silently mangle non-ASCII bytes on hosts where Windows CP_ACP=65001 ("Beta: Use Unicode UTF-8 for worldwide language support") is active.
  • DeviceN colorant names are now stored as a dynamic array of TBytes inside THPDFDeviceNParams (previously a TStringList of UnicodeString entries). The earlier representation forced an AnsiString -> UnicodeString -> AnsiString round-trip on every ink name, which on UTF-8 ANSI hosts rewrote any byte >= 0x80 as the 3-byte replacement sequence EF BF BD. ASCII-only ink names (PANTONE / Hexachrome and "Red" / "Blue" style names) were unaffected; custom non-ASCII ink names (e.g. Latin-1 accented spot-ink names in non-English print workflows) silently corrupted before this release.
  • The PDF Name emission path (SaveNameObject) is now byte-safe and spec-compliant. The earlier implementation called AnsiString(Trim(String(ValObject.Value))) which performed the same destructive round-trip on every PDF name written to disk. Names are now trimmed by byte comparison ("char <= 0x20" matching Delphi's Trim semantics) and any byte outside the regular-name range 0x21..0x7E is emitted with the spec-mandated #XX escape per PDF 1.7 ISO 32000-1 7.3.5. ASCII names without leading / trailing whitespace and without embedded special bytes serialise to bit-identical output as before, so existing files and tests are unaffected.
  • A new round-trip verifier (smoke_devicen_binary_safe) registers a DeviceN ink whose name contains an embedded 0xA0 byte and asserts the emitted /Names array carries the spec-compliant "#A0" escape rather than the corrupting "#EF#BF#BD" sequence. With this release the v2.51.x binary-safety chain (PubSec recipients, DeviceN ink names, generic PDF Name emission) is closed; 33/33 smokes + 26/26 verifiers pass.

2026-05-17 Version 2.51.4

  • Fixed silent corruption of recipient envelope bytes in the Public-Key Security Handler (PDF 1.7 ISO 32000-1 7.6.5). When the host system has Windows "Beta: Use Unicode UTF-8 for worldwide language support" enabled (CP_ACP=65001), the prior TStringList-based storage of PKCS#7 envelopedData blobs forced an AnsiString -> UnicodeString -> AnsiString round-trip that rewrote every non-ASCII byte as the UTF-8 replacement sequence (EF BF BD). The corruption propagated identically into both the SHA-1 file-key computation (algorithm 9) and the emitted /Recipients hex strings, so the produced PDF would still decrypt with HotPDF's own corrupted key, but no third-party reader could derive the same key from the original envelope bytes - meaning the encrypted file was effectively only readable by HotPDF itself on the same CP_ACP-locked system.
  • The fix replaces the recipient store with a dynamic array of TBytes; the public AddPubKeyRecipient API signature stays the same. Envelope bytes are now copied verbatim via Move(); no codepage conversion path remains.
  • /Recipients hex now reproduces the supplied envelopes byte-for-byte regardless of host locale; Acrobat / Foxit / qpdf and any PKCS#7-aware reader can recover the file encryption key as the spec defines.
  • A new round-trip verifier (smoke_pubsec_verify) was added at v2.33.0 and has been failing this build since then; v2.51.4 brings it from FAIL to OK. With this release the entire v2.51.x audit chain (R=5 audit, R=6 user-pw audit, R=6 owner-pw K0 fix, PubSec recipient byte fix) is closed and 25/25 smoke verifiers pass cleanly for the first time since v2.33.0.

2026-05-16 Version 2.51.3

  • Fixed AES-256 V=5 R=6 owner-password validation hash so it conforms to ISO 32000-2 Algorithm 2.B as written. The PDF 2.0 hash dance prescribes the initial K state as SHA-256(password || salt || udata), where udata for owner-password derivations is the full 48-byte /U entry. Prior HotPDF releases (v2.22.0 through v2.51.2) folded udata only into the iterative K1 step and omitted it from the initial K, producing a non-spec /O[0:32] hash that Acrobat / Foxit / qpdf-style readers would reject at owner-password validation time. User-password decryption was unaffected because its hash dance is called with an empty udata argument and is therefore bit-identical with or without the fix.
  • Acrobat / Foxit / qpdf now validate the owner password against HotPDF-emitted R=6 files. /O[0:32] and /OE values produced before v2.51.3 remain readable for user-password decryption but cannot be unlocked through their owner password by spec-following readers; regenerate affected R=6 files with v2.51.3 if owner-password validation is needed externally.
  • /U / /UE / /Perms / V=5 R=5 / V=4 paths are byte-identical to pre-v2.51.3 output; the fix is scoped to the initial K SHA-256 inside PDF20HashDanceR6 and only changes output when called with a non-empty UEntry (the owner-password call site in CreateKeysV5).
  • A new round-trip audit (smoke_aes256_r6_owner_verify) derives /O[0:32] through both the pre-fix and spec algorithms; it asserts that the emitted /O[0:32] matches the spec variant and that the pre-fix variant no longer reproduces the output.

2026-05-16 Version 2.51.2

  • Extended the v2.51.1 /Perms audit to cover the V=5 R=6 ("hash dance") standard security handler path (ISO 32000-2 7.6.4.3.4 / Algorithm 2.B). A new round-trip verifier reimplements the iterative SHA-256 / SHA-384 / SHA-512 + AES-128-CBC mixer in pure Python, derives the 32-byte file encryption key from the empty user password, AES-256-ECB-decrypts the 16-byte /Perms block, and asserts every field against the encrypt-dict values (/P little-endian, 0xFF filler, 'T' / 'F' tag, 'a' 'd' 'b' literal). No behaviour change; this release is pure regression coverage for the R=6 path matching the R=5 lock-down landed in v2.51.1.

2026-05-16 Version 2.51.1

  • Locked the V=5 R=5 standard security handler's /P -> /Perms binding in the regression suite. The AES-256 encryption path emits a 16-byte /Perms block per ISO 32000-1 Algorithm 10: bytes 0-3 carry the /P value as 32-bit little-endian, bytes 4-7 are 0xFF filler, byte 8 is 'T' / 'F' tracking /EncryptMetadata, bytes 9-11 are the literal 'a' 'd' 'b' marker, and bytes 12-15 are random fill. A new round-trip audit derives the file encryption key from the user password through algorithm 2.A (SHA-256(pw || U_Key_Salt) -> AES-CBC-decrypt /UE -> file key), AES-256-ECB-decrypts /Perms, and asserts every field against the encrypt-dict values. No behaviour change; this release is pure regression coverage to ensure a future refactor cannot silently break the spec-mandated /P <-> /Perms relationship.

2026-05-16 Version 2.51.0

  • Added tensor product patch mesh shading support (PDF 1.3+ ISO 32000-1 8.7.4.5.7, Shading Type 7), the final entry in the mesh shading family after v2.48 Type 4 (free-form), v2.49 Type 5 (lattice) and v2.50 Type 6 (Coons). Each tensor-product patch carries a full 4 x 4 grid of bicubic Bezier control points p[i][j] (12 boundary + 4 interior) plus one colour per corner, giving fine-grained control of both the patch boundary and its interior. Useful for SVG mesh-gradient round-trips and any vector artwork that needs a tensor-product Bezier surface rather than a Coons-blended one.
  • New THotPDF.RegisterTensorProductPatchMesh(XMin, YMin, XMax, YMax, NumComponents, Patches) emits a /ShadingType 7 binary stream with /BitsPerCoordinate 16, /BitsPerComponent 8 and /BitsPerFlag 8, wraps it in a Pattern Type 2, and returns a Pattern name (Sh1, Sh2, ...) usable through the regular SetFillPattern / SetStrokePattern pipeline.
  • The Patches flat array packs each patch as 16 control points (32 floats X+Y in the spec's stream order: p[0][0..3], p[1][3], p[2][3], p[3][3], p[3][2..0], p[2][0], p[1][0], p[1][1], p[1][2], p[2][2], p[2][1]) followed by 4 corner colours (4 * NumComponents floats at p[0][0] / p[0][3] / p[3][3] / p[3][0]), stride = 32 + 4 * NumComponents. Every emitted patch carries flag = 0 (independent patch).
  • With this release, the entire ISO 32000-1 8.7.4.5 mesh shading family (Types 4 / 5 / 6 / 7) is now covered.

2026-05-16 Version 2.50.0

  • Added Coons patch mesh shading support (PDF 1.3+ ISO 32000-1 8.7.4.5.6, Shading Type 6) - the third entry in the mesh shading family after v2.48 Type 4 (free-form) and v2.49 Type 5 (lattice). Each Coons patch is bounded by four cubic Bezier curves and carries one colour per corner; the renderer fits the Coons surface between the four edges, giving an arbitrarily curved colour-bearing quad. Useful for foil and metallic gradients on bent paths, SVG-derived gradient meshes, and any quad with curved edges that would otherwise need many small triangles to approximate.
  • New THotPDF.RegisterCoonsPatchMesh(XMin, YMin, XMax, YMax, NumComponents, Patches) emits a /ShadingType 6 binary stream with /BitsPerCoordinate 16, /BitsPerComponent 8 and /BitsPerFlag 8, wraps it in a Pattern Type 2, and returns a Pattern name (Sh1, Sh2, ...) usable through the regular SetFillPattern / SetStrokePattern pipeline.
  • The Patches flat array packs each patch as 12 control points (24 floats X+Y) followed by 4 corner colours (4 * NumComponents floats), stride = 24 + 4 * NumComponents. The 12 control points are ordered c1..c12 in clockwise order around the patch boundary starting from corner 1, with c1 / c4 / c7 / c10 at the four corners and the remaining 8 as inner Bezier handles. Every emitted patch carries flag = 0 (independent patch); continuation flags 1 / 2 / 3 (edge sharing) are not exposed in this convenience overload.
  • Type 7 (tensor product patch mesh) remains future work.

2026-05-16 Version 2.49.0

  • Added lattice-form Gouraud-shaded triangle mesh support (PDF 1.3+ ISO 32000-1 8.7.4.5.5, Shading Type 5), the companion to v2.48.0's free-form Type 4. Use it when source data is already arranged on a regular sample grid (terrain, FEA results, scientific heatmaps) and the application does not want to emit explicit triangle topology -the renderer auto-triangulates each pair of adjacent rows into a triangle strip.
  • New THotPDF.RegisterLatticeFormGouraudShading(XMin, YMin, XMax, YMax, NumComponents, VerticesPerRow, Vertices) packs an M-row x N-column lattice into a binary Shading stream with /BitsPerCoordinate 16, /BitsPerComponent 8 and /VerticesPerRow N. Unlike Type 4, there is no per-vertex flag byte and /BitsPerFlag is absent from the shading dictionary. Returns a Pattern name (Sh1, Sh2, ...) usable through the regular SetFillPattern / SetStrokePattern pipeline.
  • The Vertices flat array is ordered row-major with each vertex contributing (X, Y, c0, c1, ..., c_{NumComponents-1}) values; total vertex count must be a multiple of VerticesPerRow with at least two rows. NumComponents accepts 1 (DeviceGray), 3 (DeviceRGB) or 4 (DeviceCMYK).
  • Type 6 (Coons patch) and Type 7 (tensor product) mesh shadings remain future work.

2026-05-16 Version 2.48.0

  • Added free-form Gouraud-shaded triangle mesh support (PDF 1.3+ ISO 32000-1 8.7.4.5.4, Shading Type 4) for vector-illustration colour gradients more general than the existing Type 2 / 3 axial / radial variants. Any triangulated surface with per-vertex colours can now be embedded directly.
  • New THotPDF.RegisterFreeFormGouraudShading(XMin, YMin, XMax, YMax, NumComponents, Vertices) packs the triangle mesh into a binary Shading stream with /BitsPerCoordinate 16, /BitsPerComponent 8 and /BitsPerFlag 8, wraps it in a Pattern Type 2, and returns a Pattern name (Sh1, Sh2, ...) usable through the regular THPDFPage.SetFillPattern / SetStrokePattern pipeline.
  • Vertices flat array packs (X, Y, c0, c1, ...) per vertex; every three consecutive vertices form one independent triangle. The output coordinates / components are encoded as big-endian unsigned integers; the /Decode array maps the encoded range back to the user-supplied bounding box in user space.

2026-05-16 Version 2.47.0

  • Added DeviceN colour space support (PDF 1.3+ ISO 32000-1 8.6.6.5), generalising Separation to N colorants for six-colour printing, metallic / fluorescent ink mixes, and PDF/X custom-ink workflows. New THotPDF.RegisterDeviceN(ColorantNames, AlternateCS, TintC1Matrix) returns an auto-generated colour space name (DevN1, DevN2, ...) that drives the regular SetFillColorSpace + SetFillColor pipeline with N tint operands in [0..1].
  • The tint transform is a PostScript-calculator Function Type 4 emitting a linear weighted-blend over the supplied N x M matrix, so fallback rendering on viewers without spot-colour support interpolates each output channel as a sum of colorant contributions.
  • ColorantNames accepts any spot ink name; spaces are escaped to #20 per PDF 1.7 7.3.5 by the existing name-writer path. The special name "None" marks an unused colorant slot.
  • AlternateCS accepts DeviceGray, DeviceRGB or DeviceCMYK; any other value raises an exception.

2026-05-15 Version 2.46.0

  • Extended AcroForm appearance-stream generation (AutoFormAppearances) for text fields per PDF 1.7 ISO 32000-1 12.7.4.3. The v2.28.0 baseline produced a single-line literal for every text widget; multi-line and comb fields now drive their own AP layout paths.
  • Multi-line text fields (Flags including ffMultiline) word-wrap the initial value to the widget width, honour embedded CR / LF / CRLF separators, truncate at the visible row count, and emit a proper Td + T* multi-line layout with /TL leading.
  • Comb text fields (Flags including ffComb together with MaxLen > 0) render each character into its own equal-width cell using an absolute Tm matrix per glyph, matching what Acrobat draws.
  • Single-line text fields keep the v2.28.0 layout byte-for-byte so legacy callers see no behavioural change.

2026-05-14 Version 2.45.0

  • Added Separation colour space support for spot-colour print workflows per ISO 32000-1 8.6.6.4. New THotPDF.RegisterSeparation(ColorantName, AlternateCS, TintC1) returns an auto-generated colour space name (Sep1, Sep2, ...) that drives the regular SetFillColorSpace / SetStrokeColorSpace + SetFillColor([tint]) / SetStrokeColor([tint]) pipeline with a single tint operand in [0..1].
  • AlternateCS accepts DeviceGray, DeviceRGB or DeviceCMYK; TintC1 describes the spot ink colour at full strength (tint = 1.0). A linear Function Type 2 tint transform is built internally so fallback rendering on viewers without spot-colour support interpolates smoothly between zero ink (tint = 0) and TintC1 (tint = 1).
  • Pantone and other ink names with spaces are accepted directly -HotPDF escapes them to "#20" sequences per PDF 1.7 7.3.5 when writing the colour-space name.
  • Refuses on documents below PDF 1.3 through RequirePDFVersion so strict-mode 1.2 / earlier output stays compliant.

2026-05-14 Version 2.44.0

  • Added CIELab colour space support per ISO 32000-1 8.6.5.3. New THotPDF.RegisterLabColorSpace(Xw, Yw, Zw, aMin, aMax, bMin, bMax) returns an auto-generated colour space name (Lab1, Lab2, ...) that drives the regular THPDFPage.SetFillColorSpace / SetStrokeColorSpace + SetFillColor([L, a, b]) / SetStrokeColor([L, a, b]) pipeline.
  • Common workflow: register one space per illuminant (D50 for ICC print, D65 for sRGB-equivalent display), then paint with L* in [0..100] and a*, b* in the chosen range (typically -128..127). PDF readers (Adobe Acrobat, Foxit, MuPDF, browser viewers) honour the WhitePoint when converting Lab to display RGB.
  • Refuses on documents below PDF 1.3 through RequirePDFVersion so strict-mode 1.2 / earlier output stays compliant.

2026-05-14 Version 2.43.1

  • Verified the existing CCITTFaxDecode encoder end-to-end on a real bilevel image: a 240x120 pf1bit alternating-band TBitmap embeds correctly through all three modes (icCCITT31 G3 1D / icCCITT32 G3 2D / icCCITT42 G4 / T.6), the encoded stream sizes follow the expected G3-1D > G3-2D > G4 compression ordering, the streams decode in Adobe Acrobat / Foxit / MuPDF with no warnings, and pixel sampling confirms the alternating black/white-band pattern round-trips on all three modes. No encoder code change - this release just locks the existing PDF 1.7 ISO 32000-1 7.4.9 coverage in the smoke suite.

2026-05-14 Version 2.43.0

  • Added end-to-end uncoloured Tiling Pattern (PaintType=2) painting per ISO 32000-1 8.6.6.1. The pattern stream contains only the tile geometry (no colour); the tint colour rides through the page content stream's scn / SCN operator at fill / stroke time.
  • New THPDFPage methods SetFillPatternRGB / SetStrokePatternRGB / SetFillPatternGray / SetStrokePatternGray / SetFillPatternCMYK / SetStrokePatternCMYK accept a registered uncoloured pattern name plus the tint components (DeviceRGB, DeviceGray, or DeviceCMYK in [0..1]). Each call auto-registers the matching [/Pattern /BaseCS] tinting colour space on the page Resources/ColorSpace dict on first use.
  • The existing SetFillPattern / SetStrokePattern still cover the coloured PaintType=1 variant - the tile carries its own colour and no tint components ride the operator.
  • Both code paths share the existing RegisterTilingPattern entry point; Colored=true emits PaintType=1 (coloured), Colored=false emits PaintType=2 (uncoloured).

2026-05-14 Version 2.42.0

  • Added PDF 1.4 soft-mask image (SMask) support. New THotPDF.AddImageWithSMask method takes a raw RGB plane and a parallel 8-bit alpha plane, emits the colour image XObject together with a DeviceGray soft-mask XObject, and wires the /SMask cross-reference per ISO 32000-1 8.9.5.4. Place the returned image index with the existing ShowImage method - no other plumbing changes are required.
  • Added the convenience helper THotPDF.AddImageWithSMask32 that pulls the colour and alpha planes out of a 32-bit BGRA TBitmap, so alpha-bearing PNG-style bitmaps can be embedded in a single call.
  • Both calls refuse on documents below PDF 1.4 through RequirePDFVersion so strict-mode 1.3 output stays compliant.

2026-05-14 Version 2.41.0

  • Added PDF 1.5 object stream output. Set THotPDF.UseObjectStreams to true together with UseXRefStream and SaveToStream packs eligible indirect objects (everything except streams, the encryption dictionary, and the trailer-pointed Catalog and Info dictionaries) into one or more /Type /ObjStm container streams.
  • File-size reduction on document-heavy PDFs (many pages, annotations, AcroForm widgets, structure-tree elements, or Optional Content layers). A 30-page graphics-only smoke test reduces from 14502 bytes to 8488 bytes (41.5% smaller) with no visible change.
  • The cross-reference stream now emits type-2 compressed-object entries whose field-2 is the host ObjStm object number and field-3 is the entry's index within that stream. UseObjectStreams silently downgrades to false when UseXRefStream is off (textual xref cannot encode type-2 entries) or when Version is below PDF 1.5.

2026-05-12 Version 2.40.1

  • Fixed rendering of generated PDFs in Adobe Acrobat, Foxit, SumatraPDF, and browser-embedded viewers on Windows 10 / 11 systems with the "Beta: Use Unicode UTF-8 for worldwide language support" region option enabled. Page content streams no longer carry a stray UTF-8 BOM preamble that strict PDF readers rejected as a syntax error.

2026-05-11 Version 2.40.0

  • Added CID-keyed CFF font subsetting for large CJK and multi-FD OpenType-CFF fonts, including Adobe CJK fonts that use FDArray and FDSelect data.
  • Improved CFF subroutine handling so large OpenType-CFF fonts can be embedded as compact PDF subsets instead of full font files.

2026-05-11 Version 2.39.1

  • Improved real-world OpenType-CFF font compatibility, including Adobe MinionPro-style fonts with larger CFF tables and multi-byte offsets.
  • Exposed HPDFExtractTTFPostScriptName so applications with custom font-loading code can read the PostScript name from TrueType and OpenType-CFF font data.

2026-05-11 Version 2.39.0

  • Added Global Subr and Local Subr subsetting for CFF fonts, further reducing embedded OpenType-CFF font size.
  • Preserved used CFF subroutine bytecode while safely replacing unused entries with compact return stubs.

2026-05-11 Version 2.38.0

  • Added automatic OpenType-CFF embedding in StoreFont. HotPDF now emits CFF-based CID fonts through /FontFile3 when a GDI-loaded font is an sfnt 'OTTO' OpenType-CFF container.
  • Added HPDFSfntIsOTF, HPDFOTFFindCFFTable, and HPDFSubsetOTFContainer helper APIs for applications that process OpenType-CFF fonts directly.

2026-05-11 Version 2.37.0

  • Added HPDFSubsetCFF for Compact Font Format and Type 1C font payloads, enabling smaller embedded CFF font programs.
  • Preserved original glyph IDs during CFF subsetting so existing Type0 / Identity-H text output remains compatible.
  • Exposed the CFF subsetter for applications that load fonts through their own pipeline before passing data to HotPDF.

2026-05-10 Version 2.36.0

  • Added optional TrueType font subsetting with THotPDF.EnableFontSubsetting, often reducing embedded font payloads by more than 90% for documents that use only a small glyph set.
  • Added PDF-standard subset font naming while preserving the source font's glyph IDs for Identity-H text.
  • Kept full-font embedding as the default for workflows that require the complete font program.

2026-05-10 Version 2.35.1

  • Fixed an Adobe Acrobat "Font Capture" crash when opening PDFs created with substituted Windows fonts. HotPDF now matches PDF font names to the PostScript name inside the embedded TrueType data.

2026-05-10 Version 2.35.0

  • Added incremental PDF update support with THotPDF.BeginIncrementalUpdate and THotPDF.SaveIncrementalUpdate. Existing source bytes are preserved and new PDF objects are appended in a compliant incremental revision.
  • Added THotPDF.MarkDirty so applications can explicitly re-save changed objects during incremental updates.
  • Enabled multi-signature workflows by preserving earlier signed byte ranges while appending later signature fields or document changes.

2026-05-10 Version 2.34.0

  • Added annotation border styles through THPDFPage.SetAnnotationBorderStyle, including solid, dashed, beveled, inset, and underline styles.
  • Added linked popup annotations. New popups can now reference their parent annotation so viewers display the expected comment connector.
  • Added THPDFPage.LastAnnotation to make annotation styling and popup chaining easier without changing existing annotation creation calls.

2026-05-10 Version 2.33.0

  • Added Public-Key Security Handler support for PDF encryption. Documents can now be encrypted for X.509 certificate recipients instead of only shared passwords.
  • Added THotPDF.EnablePubKeyEncryption and THotPDF.AddPubKeyRecipient for certificate-recipient encryption workflows.
  • Added pure-Pascal SHA-1 support in HPDFCrypt alongside the existing hash primitives.

2026-05-10 Version 2.32.0

  • Added page-level transparency groups with THPDFPage.SetTransparencyGroup and SetTransparencyGroupICC for predictable compositing of transparent PDF content.
  • Added DeviceGray, DeviceRGB, DeviceCMYK, omitted-color-space, and ICC-profile transparency group options.
  • Added THPDFPage.ClearTransparencyGroup to remove a previously assigned page transparency group.

2026-05-09 Version 2.31.0

  • Added interactive push-button actions for AcroForm buttons. Before this release, HotPDF could place button widgets that appeared correctly but had no behavior on click. Buttons can now actively submit a form, reset field values, run a JavaScript script, or navigate to a URI.
  • SubmitForm actions send the current AcroForm field values to a server URL, enabling PDF-based data collection forms that work in Adobe Reader and Foxit without a separate web viewer.
  • ResetForm clears all fields back to their default values, optionally restricted to a named subset of fields. JavaScript and URI actions follow the same PDF-version gating introduced in v2.26.0.
  • Prior to this release, AddPushButtonWithAction accepted only visual properties for the button widget; now both the appearance and the click behavior are fully controlled in a single call.

2026-05-09 Version 2.30.0

  • Added page transitions for full-screen PDF presentations with THPDFPage.SetPageTransition.
  • Added THPDFPage.SetPageDuration so presentation pages can auto-advance after a chosen delay.
  • Added version-aware handling for PDF 1.5 transition styles such as Fly, Push, Cover, Uncover, and Fade.

2026-05-09 Version 2.29.0

  • Completed the PDF 1.4 ViewerPreferences page-boundary set by adding ViewArea and ViewClip alongside the existing PrintArea and PrintClip properties.
  • ViewArea controls which page box the viewer uses as the on-screen visible area (MediaBox, CropBox, TrimBox, BleedBox, or ArtBox). ViewClip controls the on-screen clipping boundary — important for design-review PDFs that carry bleed and trim marks that should not appear in normal view.
  • These options are especially useful in preflight and print-preview workflows where the on-screen crop must differ from the print crop. Both entries require PDF 1.4 and are automatically omitted when targeting an older PDF version.

2026-05-09 Version 2.28.0

  • Added optional AcroForm appearance-stream generation with THotPDF.AutoFormAppearances. Text fields, list fields, buttons, checkboxes, and radio buttons can now render reliably in viewers that ignore /NeedAppearances.
  • Added default AcroForm resources for Helvetica and ZapfDingbats when automatic appearances are enabled.
  • Preserved the previous AcroForm behavior by keeping AutoFormAppearances disabled by default.

2026-05-09 Version 2.27.0

  • Extended PDF-version gating to ViewerPreferences and PageMode entries. Each preference value now enforces its minimum PDF version: UseAttachments and UseOC require PDF 1.5, PrintScaling requires PDF 1.6, and Duplex, NumCopies, and PickTrayByPDFSize require PDF 1.7.
  • Documents generated for older PDF targets automatically omit unsupported viewer preference entries, preventing viewer warnings or silent misinterpretation in strict PDF processors and archival validators.
  • When THotPDF.StrictVersionLock is enabled, PDF 1.3 or PDF 1.4 output never carries newer viewer preference fields, keeping files compliant for print-production and long-term archival workflows.

2026-05-09 Version 2.26.0

  • Added PDF-version gating to several older APIs that previously admitted features regardless of the target document version. Annotation types, JavaScript actions, Type 0 fonts, ToUnicode CMaps, and Catalog Additional Actions now verify that the document version supports them before writing.
  • Documents targeting PDF 1.0 or PDF 1.1 automatically promote to the required version when a newer feature is used, preventing silent generation of spec-violating output without requiring code changes in the caller.
  • When THotPDF.StrictVersionLock is enabled, feature calls that would require a version upgrade are refused instead of silently promoting the document, supporting workflows that must produce output at an exact PDF version.
  • This version-check infrastructure was extended to ViewerPreferences and PageMode in v2.27.0 and covers all major feature areas by v2.31.0.

2026-05-09 Version 2.25.0

  • Fixed PDF dictionary output to treat Name keys as case-sensitive per the PDF specification. Previously, related names differing only by case — such as /ca (fill alpha) and /CA (stroke alpha) in an ExtGState dictionary — were incorrectly merged, silently discarding one value.
  • This fix restores correct transparency compositing for documents that use per-fill and per-stroke alpha values in the same graphics state. Adobe Acrobat, Foxit, and browser PDF viewers all render the corrected output as expected.
  • Public dictionary query APIs remain case-insensitive so existing code that reads malformed input PDFs with inconsistent key casing continues to work without modification.

2026-05-09 Version 2.24.0

  • Added PDF-version gating for feature APIs introduced since v2.4.0, helping generated files stay compatible with the selected PDF version.
  • Added THotPDF.StrictVersionLock. When disabled, HotPDF auto-promotes the document version as needed; when enabled, calls that require newer PDF features are refused.
  • Updated ConfigurePDFVersion so XRef streams, XMP metadata, Tagged PDF, and AES-256 options cannot leak into older PDF targets.

2026-05-09 Version 2.23.0

  • Added a three-step digital-signature workflow for integrating HotPDF with external PKI and CMS / PKCS#7 signing libraries: create the signature field placeholder, calculate the byte range and digest to sign, then inject the completed CMS signature after external signing.
  • The byte-range strategy ensures the signed hash covers exactly the portions of the file outside the signature container, matching what PDF signature validators and PAdES conformance checkers require.
  • All cryptographic operations — certificate chain, CMS structure, timestamp tokens — are delegated to the caller’s signing library (OpenSSL, Windows CAPI, Bouncy Castle, etc.), keeping HotPDF independent of any PKI infrastructure.
  • Supports multi-signature documents: each additional signature is appended as an incremental update, preserving earlier signed byte ranges without regenerating the entire PDF.

2026-05-09 Version 2.22.0

  • Added PDF 2.0 AES-256 V=5 R=6 standard security handler support through THotPDF.UseAES256R6.
  • Added pure-Pascal SHA-384 and SHA-512 primitives in HPDFCrypt.
  • Added the PDF 2.0 hash-dance helper used by Acrobat DC and PDF/A-4 style encryption workflows.

2026-05-09 Version 2.21.0

  • Expanded Tagged PDF support from a basic marker into a structured PDF accessibility tree.
  • Added role mapping, page StructParents assignment, and ParentTree output so PDF/UA validators can navigate the structure tree.
  • Improved StructTreeRoot handling so later Tagged PDF calls update the same document structure.

2026-05-09 Version 2.20.0

  • Added page-level /UserUnit and /Tabs support through THPDFPage.SetUserUnit and THPDFPage.SetTabsOrder.
  • Added Optional Content (PDF layers) with THotPDF.RegisterOptionalContentGroup and THPDFPage.BeginOptionalContent / EndOptionalContent.
  • Added ExtGState helpers for transparency alpha and blend modes through THotPDF.RegisterExtGState and THPDFPage.SetGraphicsState.

2026-05-09 Version 2.19.0

  • Added CropBox, BleedBox, TrimBox, ArtBox, and page rotation helpers for production PDF workflows.
  • Added THotPDF.AddStructureElement for building Tagged PDF structure trees with roles, page links, and MCID references.
  • Added tiling pattern support for repeated colored or uncolored PDF pattern fills and strokes.

2026-05-09 Version 2.18.0

  • Added THPDFPage.DrawInlineImage so small raster images can be written directly into a page content stream.
  • Added multi-stop axial gradients for smoother PDF shading and color-transition effects.
  • Added OutputIntents support for PDF/A, PDF/X, and color-managed publishing workflows.

2026-05-09 Version 2.17.0

  • Added AES-256 standard security handler (PDF 1.7 Extension Level 3) for documents requiring stronger protection than the AES-128 default. AES-256 encrypted PDFs open in Adobe Acrobat 9 and later, Foxit Reader, Google Chrome, Apple Preview, and all modern PDF viewers.
  • User and owner passwords are derived using SHA-256 hashing per the Adobe Extension Level 3 specification, ensuring compatibility with any compliant PDF decryptor without custom support.
  • AES-256 encryption is opt-in in this release; the existing AES-128 path remains the default for wider backward compatibility with older Acrobat versions.
  • Later v2.22.0 adds PDF 2.0 AES-256-R6 with an improved key-derivation algorithm for maximum security with Acrobat DC and PDF/A-4 workflows.

2026-05-09 Version 2.16.0

  • Added six new annotation subtypes for multimedia, legal, and accessibility use cases: Watermark (stamp overlays for display and print), Redact (mark regions for content removal), Screen (embedded video and audio), 3D (interactive U3D and PRC model viewers), RichMedia (interactive media content), and Popup (comment bubbles linked to parent annotations).
  • Watermark annotations support separate display and print renditions, so “DRAFT” or “CONFIDENTIAL” marks can appear on-screen without printing, or vice versa.
  • Redact annotations mark content regions for removal before document distribution; the viewer’s redaction workflow applies the final erasure, keeping the original content intact until committed.
  • All six annotation types include PDF-version gating so documents targeting older PDF standards automatically omit newer annotation subtypes and stay spec-compliant.

2026-05-09 Version 2.15.0

  • Added Type 3 font support for embedding custom vector glyph definitions directly in PDF output. Type 3 fonts are well-suited to company logos, proprietary symbol sets, map icons, and any graphical notation that cannot be expressed using standard fonts.
  • Each glyph is defined as a PDF content stream using standard drawing operators, so Type 3 glyphs scale cleanly at every zoom level and print resolution without an external font file.
  • The full workflow — registering a font, defining individual glyphs, and selecting the font on a page — is handled by RegisterType3Font, AddType3Glyph, and SetType3Font.
  • Type 3 fonts are rendered correctly by Adobe Reader, Foxit, and all spec-compliant PDF viewers without requiring font installation on the viewer’s system.

2026-05-09 Version 2.14.0

  • Added pure-Pascal AES-256 and SHA-256 implementations as internal cryptographic primitives for stronger document protection. These algorithms are required by the PDF 1.7 Extension Level 3 and PDF 2.0 encryption standards.
  • The new cryptographic code requires no external DLLs or OS-provided crypto libraries, keeping HotPDF self-contained across all supported Delphi and C++Builder targets and Windows versions.
  • AES-128 remains the default document encryption format in this release; the new primitives underpin the AES-256 security handler added in v2.17.0 and the PDF 2.0 handler in v2.22.0.

2026-05-09 Version 2.13.0

  • Added smooth axial (linear) and radial gradient fills for PDF vector drawing. Vector gradients produce significantly smaller PDF files than rasterized equivalents and remain sharp at any zoom level or print resolution.
  • Both gradient types support DeviceGray, DeviceRGB, and DeviceCMYK color spaces, covering screen display, RGB print, and four-color CMYK production workflows.
  • Gradients can be applied as fills or strokes on any page path and are rendered correctly by Adobe Reader, Foxit, SumatraPDF, and all modern PDF viewers.

2026-05-09 Version 2.12.0

  • Added unsigned signature field widgets with THPDFPage.AddSignatureField. The generated PDF contains a visual signature placeholder that external PKI workflows, signing portals, and CMS / PKCS#7 signing libraries can fill with a real cryptographic signature.
  • AcroForm signature flags are set automatically whenever at least one signature field is present, keeping the document spec-compliant without extra API calls.
  • Signature field widgets are recognized as signable by Adobe Acrobat, Foxit, and other PDF signing solutions. See v2.23.0 for the full byte-range signing workflow.

2026-05-09 Version 2.11.0

  • Added Tagged PDF infrastructure required by PDF/UA and modern accessibility standards. Tagged documents carry a logical reading order and semantic structure that screen readers, assistive technologies, and text-extraction tools can follow.
  • THotPDF.EnableTaggedPDF activates the document structure tree; THotPDF.Lang sets the primary document language. Both are required for accessibility conformance validators.
  • Marked-content operators allow individual drawing operations on the page to be tagged with semantic roles (paragraph, heading, figure, etc.) and identifiers, enabling correct reflow and screen-reader extraction from generated PDFs.
  • Structure elements and marked-content identifiers are tracked per page and per document, consistent with the PDF specification for multi-page tagged documents.

2026-05-09 Version 2.10.0

  • Added PNG predictor and TIFF horizontal-differencing predictor support for FlateDecode image streams. Predictors apply adaptive per-row filtering before compression, significantly reducing output size for photographic and smooth-gradient images.
  • The PNG optimum predictor (adaptive row mode) and TIFF predictor 2 (horizontal differencing) are available as standalone encode helpers for custom image-embedding pipelines.
  • Both predictors support DeviceGray, DeviceRGB, and DeviceCMYK image data, covering screen display and four-color print workflows.

2026-05-09 Version 2.9.0

  • Added XMP metadata stream output alongside the traditional document Info dictionary. XMP is the modern standard for embedded PDF metadata and is required for PDF/A, PDF/X, and many archival workflows.
  • XMP-embedded metadata is read by desktop search engines, digital asset management systems, and PDF processing pipelines that no longer consult the older Info dictionary.
  • XMP streams are left uncompressed in the output file so external tools and command-line utilities can inspect metadata without decoding the file.
  • Applications that require exact metadata content can supply a fully custom UTF-8 XMP packet through THotPDF.CustomXMP, bypassing auto-construction.
  • When document encryption is active, the XMP stream is encrypted with the rest of the document by default, keeping metadata confidential.

2026-05-09 Version 2.8.0

  • Added optional PDF 1.5 cross-reference stream output. Cross-reference streams encode the object offset table in a compact binary format rather than the large text section used by traditional xref tables, reducing file size for every document.
  • Cross-reference streams are required for PDF 1.5 features such as object streams (introduced in v2.41.0) and are automatically disabled when the document targets a PDF version below 1.5.
  • Generated PDFs with cross-reference streams are processed more efficiently by modern viewers, archival tools, and PDF/A validators.
  • The traditional text xref table remains the default, preserving compatibility with legacy PDF readers and strict processors.

2026-05-09 Version 2.7.0

  • Added native DeviceCMYK color output for print and PDF/X workflows.
  • Added ICCBased color space registration with THotPDF.RegisterICCProfile.
  • Added generic fill and stroke color-space APIs for ICC profiles and arbitrary-component color values.

2026-05-09 Version 2.6.0

  • Added Highlight, Underline, Squiggly, and StrikeOut text-markup annotations.
  • Added Polygon, Polyline, Ink, and Caret annotations.
  • Added GoTo, GoToR, and Launch link actions for intra-document, cross-document, and external-file navigation.

2026-05-09 Version 2.5.0

  • Added AcroForm support for text fields, checkboxes, radio buttons, combo boxes, list boxes, and push buttons.
  • Added THPDFFormFieldFlags so applications can configure common form-field options without manual bit handling.
  • Generated forms now set /NeedAppearances so common PDF viewers can render fields automatically.

2026-05-08 Version 2.4.0

  • Fixed AES-128 PDF encryption. Strings and streams are now actually encrypted with AES-CBC for V=4 / R=4 security.
  • CryptKeyLength=aes128 now creates encrypted PDFs that compliant viewers can open with the configured user or owner password.
  • Added ASCIIHexDecode, ASCII85Decode, and RunLengthDecode encoder helpers.
  • Updated bundled JBIG2 and JPEG 2000 stubs to report unsupported decoding honestly instead of returning placeholder image data.

2026-05-06 Version 2.3.23

  • Tuned bundled zlib-ng, libjpeg-turbo, and libtiff object builds for current RAD Studio and Visual Studio toolchains.
  • Improved heap alignment for native compression and image libraries on Delphi and C++Builder targets.
  • Validated the Delphi and C++Builder automated regression suites across Win32, Win64, and Win64x targets.

2026-05-05 Version 2.3.22

  • Enabled zlib-ng runtime SIMD dispatch in bundled 64-bit Flate compression builds.
  • Kept Win32 zlib-ng on the stable generic object path for compatibility with the classic toolchain.
  • Hardened native object builds so a compiler success without an output object is treated as a build failure.

2026-05-05 Version 2.3.21

  • Enabled libjpeg-turbo SIMD acceleration in bundled Win32, Win64, and Win64x object builds.
  • Made SIMD support explicit through a HotPDF build flag.
  • Fixed a Win32 NASM OMF output issue that prevented Delphi Win32 from linking the SIMD JPEG object set.
  • Fixed a Win64 TIFF image regression exposed after the SIMD and zlib-ng rebuilds.

2026-05-05 Version 2.3.20

  • Replaced the bundled Flate backend with zlib-ng in zlib-compatible mode for page streams, fonts, CMaps, images, TIFF output, and FlateDecode helpers.
  • Fixed dense stream compression and TIFF small-image import issues found during the zlib-ng migration.
  • Kept JPEG workflows backed by libjpeg-turbo and TIFF workflows backed by libtiff.
  • Added broad automated regression coverage for Delphi and C++Builder PDF generation, image import, compression, encryption, hyperlinks, page setup, and copy/merge/edit workflows.

2026-05-01 Version 2.3.19

  • Fixed Win64 FlateDecode page-stream generation for dense barcode output.
  • Aligned modern Delphi page and font stream compression with the stable RTL zlib path.

2026-05-01 Version 2.3.18

  • Fixed C++Builder Win64x encrypted PDF generation by correcting pointer-sized RC4 key setup.
  • Fixed TIFF import on C++Builder Win64x builds.
  • Fixed page append workflows for PDFs opened with LoadFromFile so appended pages are saved with safe object numbers.

2026-05-01 Version 2.3.17

  • Updated the CanvasDrawing and GraphicDraw C++Builder demos to match the improved Delphi graphics samples.
  • Improved the ViewerPref C++Builder demo with clearer UI state, validation, preset save/load support, and dedicated direction-setting output.

2026-05-01 Version 2.3.16

  • Fixed page copying for merged PDFs that contain embedded CID TrueType fonts and nested width arrays.
  • Improved object mapping when copying pages from multiple source PDF documents in sequence.

2026-05-01 Version 2.3.15

  • Improved the ViewerPref Delphi demo so option-specific controls are enabled only when relevant.
  • Added ViewerPref error reporting, copy-count validation, and named preset save/load support.

2026-05-01 Version 2.3.14

  • Reworked the PDFmerge Delphi demo into a self-contained merge sample that creates source PDFs when needed.
  • Improved merge validation, output indexing, and UI success/failure messages.

2026-05-01 Version 2.3.13

  • Refined the CanvasDraw and GraphicDraw Delphi demos for clearer, repeatable visual output.
  • Improved TrueType font selection across GDI font paths, reducing embed-time failures caused by raster fallback fonts.
  • Fixed character spacing when the same font is used across multiple text runs in one PDF.
  • Improved font-embedding error messages with the requested font name and charset.

2026-05-01 Version 2.3.12

  • Improved the CopyPage demo so it can copy PDFs with non-standard /Pages trees without requiring a separate tool.
  • CopyPage now writes FlateDecode-compressed output regardless of which copy path succeeds.
  • Folded the standalone CopyPageFixed sample into the main CopyPage demo.

2026-05-01 Version 2.3.11

  • Generated PDFs now open with each page fitted to window height by default.
  • Added many no-prefix predefined page-size names for office, large-format, drawing, card, ID, photo, Japanese, Chinese, and Taiwanese paper sizes while keeping older ps-prefixed names compatible.
  • Reworked Delphi page-size demos into LargeSize, NormalSize, and SmallSize projects.

2026-04-29 Version 2.3.10

  • Fixed RAD Studio XE7 package builds in the TIFF compatibility layer.
  • Reduced warnings for RAD Studio 10 Seattle and 10.1 Berlin.
  • Corrected command-line library build mapping for RAD Studio ProductVersion 9.0 through 12.0.
  • Restored command-line package builds for RAD Studio ProductVersion 9.0.
  • Fixed RAD Studio XE2 Win64 builds by declaring the CRT vsnprintf symbol required by the Win64 compatibility stubs.
  • Improved legacy project compatibility with numeric boolean values in older Delphi and C++Builder project files.

2026-04-29 Version 2.3.9

  • Fixed RAD Studio 10.1 Berlin library package builds by compiling packages from the Lib directory.
  • Fixed Trial library builds on older Delphi compilers by removing inline variable declarations from the Trial watermark path.

2026-04-29 Version 2.3.8

  • Removed a deprecated TStream.Seek compiler warning from the TIFF stream callback path in RAD Studio 10.3 Rio builds.

2026-04-29 Version 2.3.7

  • Fixed TIFF-to-PDF conversion when using bundled libtiff 4.7.x object files.
  • Fixed RAD Studio Win32 compilation of the bundled zlib Pascal bridge under stricter compiler settings.
  • Added DUnitX regression coverage for TIFF-to-PDF conversion.

2026-04-28 Version 2.3.5

  • Fixed source-package rebuilds for bundled zlib, libjpeg-turbo, and libtiff with the classic Borland C++ Win32 compiler.
  • Rebuilt and aligned shipped Win32 and Win64 third-party object files for more reliable Delphi and C++Builder builds.
  • No public API changes. Existing Delphi, C++Builder, and RAD Studio projects can use this as a compatibility release.

2026-04-19 Version 2.3.3

  • Improved RAD Studio project reliability by preventing host INCLUDE environment settings from interfering with HotPDF resource compilation.
  • Reduced duplicate-resource warnings in package builds, especially for RAD Studio 13.1 Florence projects.

2026-04-19 Version 2.3.2

  • Package and demo project output folders now follow the active RAD Studio version automatically.
  • Improved HotPDF370 package build behavior to avoid stale resource files being linked with newly generated resources.

2026-04-19 Version 2.3.1

  • The C++Builder TextOut demo now builds without manual edits across supported RAD Studio versions.
  • Fixed Win64x rebuild behavior for the TextOut demo so the required resource file is staged before linking.

2026-04-19 Version 2.3.0

  • Added complete C++Builder 13.1 Florence support for Win32, Win64, and Win64x targets.
  • Standardized TextOut overload availability for Delphi and C++Builder while keeping older C++Builder helper names compatible.
  • Cleaned Delphi Win64 static linking by removing previous linker warnings from bundled zlib, libjpeg-turbo, and libtiff object files.
  • Improved Win64x package output so C++Builder projects can find the generated HotPDF library through normal RAD Studio search paths.

2026-04-17 Version 2.2.1

  • Eliminated Delphi Win32 linker warnings from bundled third-party compression and image libraries.
  • Improved static-link compatibility for zlib, JPEG, and TIFF support in Delphi and C++Builder builds.

2026-04-14 Version 2.2.0

  • Upgraded bundled third-party libraries to zlib 1.3.2, libjpeg-turbo 3.1.90, and libtiff 4.7.1.
  • Modernized Win32 and Win64 static linking for the updated compression, JPEG, and TIFF libraries.
  • Expanded image-format compatibility for PDF generation scenarios that use JPEG or TIFF input.
  • Updated the HTML help to match the current HotPDF API, examples, and library layout.

2026-04-13 Version 2.1.4

  • Fixed Win64 compilation for RAD Studio XE2 through XE8.
  • Improved compatibility of bundled object files used by older Delphi compilers.
  • Removed stale package resource files that could trigger duplicate-resource linker warnings.
  • Corrected Delphi XE3 project metadata for the HotPDF package.

2026-04-12 Version 2.1.3

  • Expanded the Delphi TestNumeric demo into a practical regression test for numeric PDF output.
  • Added broader validation for decimals, fractions, integers, and PDF reload behavior.
  • Made optional debug-output files opt-in through a dedicated compile-time switch.
  • Updated help content for debug logging and numeric-output testing.

2026-04-10 Version 2.1.2

  • Improved embedded-font rendering in Win32 and Win64 PDF output.
  • Fixed incomplete glyph rendering when switching between embedded fonts, including common Windows fonts such as Calibri.
  • Updated the FontTest demo and generated sample PDFs for easier visual verification.
  • Refreshed HTML help pages for font embedding, Unicode text output, and embedded-font examples.

2026-04-08 Version 2.1.1

  • Updated ViewerPreferences demos for Delphi and C++Builder to match the current HotPDF API.
  • Added coverage for newer PDF viewer preference options.
  • Improved generated demo output so it reflects only the viewer preference settings selected in the UI.
  • Updated HTML help pages for ViewerPreferences and related document properties.