Release notes

The last 15 DocToolkit releases, newest first, as published on GitHub.

v0.59.0

On GitHub

Changed

  • Extensions: if you set DocToolkitOptions.Fonts and convert XLSX or PPTX to PDF, this changes your output. IXlsxToPdfConverter and IPptxToPdfConverter ignored the setting until now, because the core converters took no fonts before 0.58.0. They now use yours, read on every call, as IDocxToPdfConverter and IHtmlToPdfConverter already did. Supplied fonts replace the host's fallbacks rather than adding to them, so a list that covers less than your workbooks and presentations use can refuse text the host's fonts used to cover. Supplying fonts also turns on Hebrew and Arabic shaping, with the limits listed under 0.58.0. Clear Fonts if you relied on the host's fonts.
  • Extensions: the package now requires Ank.DocToolkit 0.58.0 or later.

v0.58.0

On GitHub

Added

  • Core: XLSX and PPTX to PDF take fonts. XlsxToPdfConverter and PptxToPdfConverter gain Convert(byte[], PdfFontOptions?) and ConvertAsync(Stream, Stream, PdfFontOptions?, CancellationToken), matching DOCX and HTML. Until now a workbook or presentation holding text the renderer's built-in fonts cannot draw — Cyrillic, Hebrew, Arabic, CJK — could be refused with an exception, depending on the host's fonts, and there was no way to supply one. Passing null gives exactly the old render.

Changed

  • Core: if you already pass PdfFontOptions to DOCX or HTML to PDF, Hebrew and Arabic output changes. Supplying fonts now also turns on the renderer's text shaping, on every path that takes fonts. Each Hebrew or Arabic word now prints in the right letter order, and Arabic letters join; before, the letters of every word were reversed and Arabic did not join. Text with no right-to-left script renders byte-identical to 0.57.3 — measured over 83 real documents.
  • The README, the NuGet package page and the documentation site now name WillowAnk Solution, the company that maintains DocToolkit. PackageProjectUrl points to https://wanksolution.com/; RepositoryUrl still points to GitHub, where issues and source live.

Known limits of right-to-left text, stated here because "shapes Hebrew and Arabic" promises more

  • Words are laid out, lines are not. A line of several right-to-left words prints its words in reverse order, and a number lands on the wrong side. This is the renderer's behaviour, measured from glyph positions; a w:bidi paragraph behaves the same.
  • Persian and Urdu letters are not shaped. A word containing پ چ ژ گ ک ی still renders reversed and unjoined. Arabic-only words in the same text are shaped.
  • A right-to-left sheet is not mirrored. <sheetView rightToLeft="1"> is ignored, so column A prints on the left. Merged cells are honoured.
  • Markdown to PDF takes no fonts, so it gets no shaping.

v0.57.3

On GitHub

Fixed

  • lift System.Security.Cryptography.Xml to 10.0.13 past five advisories (C43)

v0.57.2

On GitHub

Fixed

  • Neither package's code changed since 0.57.1. This release carries repository tooling only.
  • The Linux container test job now has a .dockerignore, so it can no longer pass by reusing build output copied in from the host.
  • Build and test tooling updated: docfx 2.81.0, Stryker 5.0.0, coverlet 10.1.0 and Microsoft.NET.Test.Sdk 18.10.1. None of these ships inside either package.

v0.57.1

On GitHub

Fixed

  • core: count drawing ids in the comments part when inserting an image

v0.57.0

On GitHub

Added

  • extensions: mirror document comparison on IDocxCompare (A118-DI)

v0.56.0

On GitHub

Added

  • Core: DocxCompare compares two versions of a document and returns the later one with the differences marked as tracked changes. The result is an ordinary .docx carrying revisions, not a report, so Word shows it the way it shows any tracked-changes document and DocxReview reads it back without knowing where it came from — DocxReview.Inspect lists the revisions on the report it returns, and DocxReview.AcceptRevisions / RejectRevisions resolve them.
  • Core: DocxCompare.CompareWithReport returns the same document plus a warning naming every construct that was present but not compared. Read it — HasLoss is the signal that the comparison covers less than the document does.

Known limits of DocxCompare, stated here because the name promises more

  • Paragraph text is what gets compared. Tables are reported, never diffed — a row inserted mid-table is not the same edit as its text appearing elsewhere, and marking it as one would be a wrong answer rather than a coarse one.
  • A formatting-only change is not detected at all, and is not reported as a text change either. A paragraph whose text is unchanged but whose styling differs produces no revision.
  • Paragraphs are paired by position, so a paragraph inserted or removed in the middle shifts every pair after it and its neighbours read as heavily rewritten. That case is reported.
  • Per-run formatting inside a REWRITTEN paragraph is not preserved — the first run's properties are carried onto the rebuilt runs. An unchanged paragraph is left completely alone, so a document compared with itself produces no revisions and keeps every run as it was.

v0.55.0

On GitHub

Added

  • Core: DocxFormField now reports a content control's alias. Alias is the display name Word itself shows a human (w:alias). It is not Key, which is whichever name DocxFormKey resolved for matching and is usually the tag - an identifier the template's author chose for code. An application rendering a form wants the alias. Empty when the control has no alias, and also when the key is ambiguous. Word does not require either name to be unique; where one key reaches two controls with different aliases this reports nothing rather than picking one and being silently wrong half the time. DocxForm.Validate already reports that case as DuplicateKey. Two controls sharing a key *and* an alias are not ambiguous about the alias, and it is still reported. Whether a control is LOCKED is still not reported, and that is a deliberate limit rather than an oversight - the underlying library exposes the alias but neither the lock nor the element to read one from. Since 0.54.0 DocxForm.Fill refuses to write a locked control, so a lock is at least safe to discover by attempting the fill.
  • Extensions: IDocxEditor mirrors the Regex overloads of ReplaceText added to the core in 0.54.0 - the byte[] form and its Stream form - so pattern-based find-and-replace is reachable through dependency injection rather than only through the static class. Three things a caller gets wrong without being told, now stated on the interface: the replacement is a template in which $1 expands to a captured group and a literal $ is $$, zero-width matches are skipped, and a Regex built without a match timeout is refused rather than accepted.

v0.54.0

On GitHub

⚠ BREAKING CHANGES

  • Core: DocxForm.Fill refuses to fill a content control the document locks. A fill that would change a locked control now throws InvalidOperationException and writes nothing; the document you pass in is untouched, and a Stream overload writes zero bytes to its destination. It previously succeeded - and left the w:lock in place, so the result declared the control protected while its content had been replaced. That file contradicted itself, no reader could see it, and the caller had no way to detect it afterwards. Measured against sdtContentLocked, sdtLocked and contentLocked. What to do: remove the lock in Word, or leave that control out of the values you pass. Filling other controls in a document that also contains a locked one is unaffected and still works - which is the ordinary case for a template that locks its headings. This is why 0.54.0 is a minor bump rather than a patch. See *Migrating* in the README. Locks are not read, reported or removed by this change. DocxFormField still exposes Key and Value alone; a caller cannot yet tell a locked control from an editable one without attempting the fill.

v0.53.0

On GitHub

Added

  • Core: DocxEditor.ReplaceText can match a regular expression, not just literal keys. A new overload takes a Regex and a replacement template, with a Stream form beside it, and applies across the body, headers, footers, footnotes and endnotes exactly as the literal overload does. Anything whose shape is known but whose text is not - dates, reference numbers, whitespace - now has an entry point. The replacement is a TEMPLATE, not a literal. $1 and friends expand to captured groups the way Regex.Replace expands them, so a literal $ must be written $$. The pattern must carry a match timeout, and one without is refused with an ArgumentException rather than accepted. A catastrophically backtracking expression has no upper bound on its running time, and a hang is worse than a failure because the caller cannot catch it. Construct it as new Regex(text, RegexOptions.None, TimeSpan.FromSeconds(1)). Formatting behaves exactly as it already did for literal keys: where a match spans several runs, the replacement is written into the run holding its first character and inherits that run's formatting. Zero-width matches are skipped, because one consumes no characters. Nothing existing changed. The literal overload is untouched, and both now share one implementation of the run-splicing step rather than two that could drift.
  • Extensions: IPdfEditor mirrors ExtractWords and ExtractImages from core 0.52.0, with their Stream forms - four members, so the PDF word-position and image extraction added last release is now reachable through dependency injection. Two things a caller gets wrong without being told, and which the interface documentation now states: coordinates are measured from the page's bottom-left, so a word near the top of A4 has a Bottom near 842 rather than near 0; and PdfImage.Png is null when the pixels could not be re-encoded, which is a normal outcome rather than an error.

v0.51.0

On GitHub

Added

  • Extensions: the dependency-injection interfaces caught up with everything 0.50.0 added to the core. IDocxEditor gains the eight watermark and bookmark members, IPresentationEditor the four speaker-notes members, and IDocToDocxConverter gains ConvertWithReportAsync — thirteen in all, each a thin wrapper over the core static method of the same name. This release contains no core change. The extensions package references the *published* core, so a mirror can never ship in the same release as the member it wraps; it is structurally one release behind. If you use the static classes directly, there is nothing here for you. Worth knowing if you tracked the gap: ConvertWithReportAsync was the one mirror nobody filed a ticket for. It was found by the interface-completeness test the moment the package floor was bumped, not by anybody remembering.

v0.50.0

On GitHub

Added

  • Core: DOCX watermarks and bookmarks. DocxEditor.AddWatermark, RemoveWatermarks, AddBookmark and ReadBookmarks, with Stream and file-path forms. One known limitation, measured rather than assumed: AddWatermark marks one section per section the document *reports*, which is not the same as one per w:sectPr. A document produced by DocxEditor.Merge carries two w:sectPr elements but reports a single section, so it receives one watermark and pages belonging to the later part may be unmarked. Watermark each document before merging if every page must carry one.
  • Core: PPTX speaker notes round-trip. PresentationEditor.ReadNotes and SetNotes, with Stream forms. Notes written by this package are read back by PowerPoint and vice versa.
  • Core: DocToDocxConverter.ConvertWithReportAsync. The converted bytes come back in the result rather than through a destination stream, unlike ConvertAsync — the report and the document are one value, so a caller inspecting a loss report does not also have to manage a stream it may decide not to use.
  • XLSX: sparklines via XlsxFormat.WithSparkline. The in-cell mini-charts, round-tripped through ClosedXML. Slicers and timelines are not reachable from this dependency graph and are not planned — that was measured, not assumed, and it is recorded so nobody re-derives it.
  • Extensions: IDocxEditor mirrors Merge/MergeAsync from 0.49.0.

v0.49.0

On GitHub

Added

  • Core: DOCX documents can be joined with DocxEditor.Merge. PdfEditor.Merge had shipped and the DOCX equivalent had not, though the underlying library appends documents in one call. Takes the documents in the order given, with Stream and file-path forms beside the byte[] one. If you also stamp watermarks, watermark each document *before* merging. A merged document carries two section-properties elements but reports a single section, so AddWatermark applied afterwards marks it once and later pages may be unmarked.
  • Extensions: the metadata, protection and formula Stream overloads from 0.48.0 are now on the four editor interfaces, so a Stream-shaped caller reaches them through dependency injection rather than only through the static classes.

v0.48.0

On GitHub

Added

  • Core: Stream overloads for the metadata, protection and formula members. ReadMetadata, WithMetadata, InspectFormulas and EvaluateFormulas were byte[]-only, while every other capability in this package already offered a Stream form. A caller holding a stream had to materialise the whole document into an array first, purely to call one of these four. Nothing existing changed. The byte[] overloads are untouched; these sit beside them, with the same source / destination / CancellationToken shape used everywhere else. Caller-owned streams are never disposed, closed or sought.
  • Extensions: IWorkbookEditor mirrors AddDefinedName and AddImage from 0.47.0.

v0.47.0

On GitHub

Added

  • XLSX: worksheet furniture — six gaps, each measured before it was built. Page setup, merged cells, hyperlinks, cell comments, defined names and images can now be applied to a worksheet. These are the things a spreadsheet needs before it is presentable rather than merely correct, and none of them had an entry point here.
  • XLSX: real Excel tables (ListObjects) via XlsxFormat.WithTable. A styled range with a header row, sortable and filterable in Excel — not a range that merely looks like one.
  • Extensions: a large batch of interface mirrors — signatures, metadata, formulas, charts, pivot tables and SmartArt now reachable through dependency injection rather than only through the static classes.

Older releases

Every release, with its SBOM, is on GitHub.

All releases on GitHub