GroupDocs.Conversion for .NET 26.6 Release Notes
There are 9 features, improvements, and bug fixes in this release.
This release centers on richer conversion event support—adding font‑substitution notifications, a unified events aggregator, and customizable image handling for PDF‑to‑Markdown—while strengthening conversion accuracy and stability with RTL auto‑detection, form‑control handling, and fixes for PDF, image, and spreadsheet outputs.
Full list of changes in this release
| Key | Category | Summary |
|---|---|---|
| CONVERSIONNET-7934 | Feature | PDF to Markdown: allow custom image extraction and placeholder insertion |
| CONVERSIONNET-8286 | Feature | Introduce ConversionEvents aggregator with per-call/global handler precedence |
| CONVERSIONNET-8314 | Feature | Add OnFontSubstituted conversion event |
| CONVERSIONNET-8263 | Improvement | NuGet Package Split |
| CONVERSIONNET-8280 | Improvement | Auto-detect RTL direction for DOCX with missing/incorrect bidi markup |
| CONVERSIONNET-8325 | Improvement | Spreadsheet to PDF conversions with SkipEmptyRowsAndColumns overlap text when the sheet has form controls |
| CONVERSIONNET-7912 | Bug | Corrupted characters in JPEG/TIFF output |
| CONVERSIONNET-8281 | Bug | Converting a particular XFA PDF to image hangs and does not produce any result |
| CONVERSIONNET-8321 | Bug | Problem converting publisher - Unable to load Aspose.PDF |
Major Features
- OnFontSubstituted Event: Receive real‑time notifications whenever a source document font is replaced, enabling applications to log, replace, or adjust rendering for Word, Excel, PowerPoint, PDF, PCL, TeX, and Diagram files.
- ConversionEvents Aggregator: Manage all conversion‑related events through a single, typed aggregator with per‑call‑wins precedence, simplifying configuration and reducing scattered event handling code.
- Custom Image Extraction for PDF → Markdown: Hook into the conversion process to extract images and insert custom placeholders, eliminating the need for post‑conversion parsing.
- RTL Auto‑Detection for DOCX: Documents lacking proper bidi markup are automatically detected as right‑to‑left, ensuring correct text flow and layout without manual intervention.
- Form‑Control & Overlap Fixes + Stability Improvements: Spreadsheet‑to‑PDF conversions now honor form controls and prevent overlapping text when skipping empty rows/columns; additional fixes resolve corrupted characters in JPEG/TIFF outputs and hanging conversions of dynamic XFA PDFs.
Public API and backward incompatible changes
⚠️ Breaking changes
- Event names have been renamed and the event aggregation model has changed. Existing per‑result event properties and fluent chain methods are obsolete and will be removed in v26.9.
ConverterSettings.Listenerand theIConverterListenerinterface are obsolete; they are replaced by lifecycle events onConversionEvents.- The old
OnConversionCompletedper‑document event has been renamed toOnDocumentConverted. The same name is now used for the pipeline‑lifecycle event that fires once at the end of a conversion run.
1. New font‑substitution event
| API | Description |
|---|---|
ConversionEvents.OnFontSubstituted | Fires when a font required by the source document is missing and a substitute is used (either automatically or via a user‑defined rule). |
FontSubstitutionContext | Provides details about the substitution: SourceFileName, OriginalFontName, SubstituteFontName, Reason. |
FontSubstitute | Represents a user‑supplied substitution rule (e.g., FontSubstitute.Create("MissingFont", "Arial")). |
Reference:
Classic API example
using GroupDocs.Conversion;
using GroupDocs.Conversion.Contracts;
using GroupDocs.Conversion.Options.Convert;
var events = new ConversionEvents
{
OnFontSubstituted = ctx =>
{
var detail = ctx.OriginalFontName != null
? $"{ctx.OriginalFontName} -> {ctx.SubstituteFontName}"
: ctx.Reason;
Console.WriteLine($"Font substituted in '{ctx.SourceFileName}': {detail}");
}
};
using var converter = new Converter(
"source.docx",
() => new ConverterSettings(),
() => events);
converter.Convert("output.pdf", new PdfConvertOptions());
Fluent API example
FluentConverter
.WithEvents(e => e.OnFontSubstituted = ctx =>
Console.WriteLine($"Font substituted: {ctx.Reason ?? ctx.OriginalFontName}"))
.Load("source.docx")
.ConvertTo("output.pdf")
.WithOptions(new PdfConvertOptions())
.Convert();
Substitution rule example (Word‑processing / Spreadsheet / PDF)
using GroupDocs.Conversion.Options.Load;
using var converter = new Converter(
"source.docx",
_ => new WordProcessingLoadOptions
{
FontSubstitutes = new List<FontSubstitute>
{
FontSubstitute.Create("MissingFont", "Arial")
}
},
() => new ConverterSettings(),
() => events);
converter.Convert("output.pdf", new PdfConvertOptions());
2. Unified event aggregation (ConversionEvents)
A new ConversionEvents class aggregates pipeline‑lifecycle and per‑result events.
Constructor overloads for Converter and the fluent entry method FluentConverter.WithEvents now accept a delegate that configures a ConversionEvents instance.
Reference:
Classic API migration
// Before (v26.5)
var settings = new ConverterSettings();
settings.OnConversionFailed = (ctx, ex) => Log(ex);
settings.OnConversionByPageFailed = (ctx, ex) => Log(ex);
using var converter = new Converter("input.docx", () => settings);
converter.Convert("output.pdf", new PdfConvertOptions());
// After (v26.6)
var events = new ConversionEvents
{
OnDocumentFailed = (ctx, ex) => Log(ex),
OnPageFailed = (ctx, ex) => Log(ex)
};
using var converter = new Converter(
"input.docx",
() => new ConverterSettings(),
() => events);
converter.Convert("output.pdf", new PdfConvertOptions());
Fluent API migration
// Before (v26.5)
FluentConverter
.Load("input.docx")
.ConvertTo("output.pdf")
.WithOptions(new PdfConvertOptions())
.OnConversionCompleted(ctx => Console.WriteLine($"Done: {ctx.SourceFileName}"))
.OnConversionFailed((ctx, ex) => Log(ex))
.Convert();
// After (v26.6)
FluentConverter
.WithEvents(e =>
{
e.OnDocumentConverted = ctx => Console.WriteLine($"Done: {ctx.SourceFileName}");
e.OnDocumentFailed = (ctx, ex) => Log(ex);
})
.Load("input.docx")
.ConvertTo("output.pdf")
.WithOptions(new PdfConvertOptions())
.Convert();
Compression result‑delivery migration (fluent)
// Before (v26.5)
FluentConverter
.Load("input.docx")
.ConvertTo((SaveContext _) => new MemoryStream())
.WithOptions(new PdfConvertOptions())
.Compress(new CompressionConvertOptions { Format = CompressionFileType.Zip })
.OnCompressionCompleted(stream => HandleArchive(stream))
.Convert();
// After (v26.6)
FluentConverter
.WithEvents(e => e.OnCompressionCompleted = stream => HandleArchive(stream))
.Load("input.docx")
.ConvertTo((SaveContext _) => new MemoryStream())
.WithOptions(new PdfConvertOptions())
.Compress(new CompressionConvertOptions { Format = CompressionFileType.Zip })
.Convert();
3. Event name changes (per‑result events)
| Old name | New name |
|---|---|
OnConversionCompleted (per‑document) | OnDocumentConverted |
OnConversionFailed (per‑document) | OnDocumentFailed |
OnConversionByPageCompleted | OnPageConverted |
OnConversionByPageFailed | OnPageFailed |
OnCompressionCompleted | (unchanged) |
Reference:
The new pipeline‑lifecycle events are:
| Property | Signature | Fires |
|---|---|---|
OnConversionStarted | Action | When a conversion run begins |
OnConversionProgress | Action<int> (percent) | Periodically during conversion |
OnConversionCompleted | Action | Once at the end of the run (regardless of success) |
Reference:
4. Obsolete APIs (scheduled removal in v26.9)
| Obsolete member | Replacement |
|---|---|
ConverterSettings.OnConversionFailed, OnConversionByPageFailed, OnCompressionCompleted | Set the corresponding handler in ConversionEvents via the events: constructor argument or FluentConverter.WithEvents. |
Fluent chain methods .OnConversionCompleted(...), .OnConversionFailed(...), .OnCompressionCompleted(...) | Use FluentConverter.WithEvents(e => …) before Load. |
ConverterSettings.Listener / IConverterListener | Use ConversionEvents.OnConversionStarted, OnConversionProgress, OnConversionCompleted. |
| Staged interfaces that expose the old per‑result methods | Register all handlers through ConversionEvents. |
All obsolete members remain functional in v26.6 but emit compiler warnings.
5. Markdown image‑saving callback
A new callback interface allows custom handling of images generated while converting to Markdown.
| API | Description |
|---|---|
IMarkdownImageSavingCallback | Implemented by the caller and assigned to MarkdownOptions.ImageSavingCallback. Invoked once per extracted image. |
MarkdownImageSavingArgs | Argument passed to the callback; mutable properties: ImageFileName, ImageStream, KeepImageStreamOpen. |
MarkdownOptions.ImageSavingCallback | Property on conversion options that accepts an IMarkdownImageSavingCallback implementation. |
Reference:
Scenario 1 – Capture images in memory
class CaptureImagesCallback : IMarkdownImageSavingCallback
{
private int _index;
private readonly Dictionary<string, MemoryStream> _images;
public CaptureImagesCallback(Dictionary<string, MemoryStream> images) => _images = images;
public void ImageSaving(MarkdownImageSavingArgs args)
{
var id = $"image{_index++}";
var buffer = new MemoryStream();
_images[id] = buffer;
args.ImageStream = buffer; // redirect bytes into our buffer
args.ImageFileName = id; // placeholder URI in the .md file
args.KeepImageStreamOpen = true; // caller will read after Convert()
}
}
var captured = new Dictionary<string, MemoryStream>();
var options = new WordProcessingConvertOptions { Format = WordProcessingFileType.Md };
options.MarkdownOptions.ImageSavingCallback = new CaptureImagesCallback(captured);
using var converter = new Converter("source.pdf");
converter.Convert("output.md", options);
// captured["image0"], captured["image1"], … now contain the image bytes
Scenario 2 – Persist images to disk
class FileImagesCallback : IMarkdownImageSavingCallback
{
private readonly string _outputFolder;
private int _index;
public FileImagesCallback(string outputFolder) => _outputFolder = outputFolder;
public void ImageSaving(MarkdownImageSavingArgs args)
{
var fileName = $"image{_index++}.png";
args.ImageStream = new FileStream(Path.Combine(_outputFolder, fileName), FileMode.Create);
args.ImageFileName = fileName; // written into the .md as 
// KeepImageStreamOpen defaults to false → converter closes the file
}
}
var options = new WordProcessingConvertOptions { Format = WordProcessingFileType.Md };
options.MarkdownOptions.ImageSavingCallback = new FileImagesCallback("./out");
using var converter = new Converter("source.pdf");
converter.Convert("./out/output.md", options);
// ./out/image0.png, ./out/image1.png, … are created and closed by the converter