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

KeyCategorySummary
CONVERSIONNET-7934FeaturePDF to Markdown: allow custom image extraction and placeholder insertion
CONVERSIONNET-8286FeatureIntroduce ConversionEvents aggregator with per-call/global handler precedence
CONVERSIONNET-8314FeatureAdd OnFontSubstituted conversion event
CONVERSIONNET-8263ImprovementNuGet Package Split
CONVERSIONNET-8280ImprovementAuto-detect RTL direction for DOCX with missing/incorrect bidi markup
CONVERSIONNET-8325ImprovementSpreadsheet to PDF conversions with SkipEmptyRowsAndColumns overlap text when the sheet has form controls
CONVERSIONNET-7912BugCorrupted characters in JPEG/TIFF output
CONVERSIONNET-8281BugConverting a particular XFA PDF to image hangs and does not produce any result
CONVERSIONNET-8321BugProblem 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.Listener and the IConverterListener interface are obsolete; they are replaced by lifecycle events on ConversionEvents.
  • The old OnConversionCompleted per‑document event has been renamed to OnDocumentConverted. 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

APIDescription
ConversionEvents.OnFontSubstitutedFires when a font required by the source document is missing and a substitute is used (either automatically or via a user‑defined rule).
FontSubstitutionContextProvides details about the substitution: SourceFileName, OriginalFontName, SubstituteFontName, Reason.
FontSubstituteRepresents 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 nameNew name
OnConversionCompleted (per‑document)OnDocumentConverted
OnConversionFailed (per‑document)OnDocumentFailed
OnConversionByPageCompletedOnPageConverted
OnConversionByPageFailedOnPageFailed
OnCompressionCompleted(unchanged)

Reference:

The new pipeline‑lifecycle events are:

PropertySignatureFires
OnConversionStartedActionWhen a conversion run begins
OnConversionProgressAction<int> (percent)Periodically during conversion
OnConversionCompletedActionOnce at the end of the run (regardless of success)

Reference:


4. Obsolete APIs (scheduled removal in v26.9)

Obsolete memberReplacement
ConverterSettings.OnConversionFailed, OnConversionByPageFailed, OnCompressionCompletedSet 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 / IConverterListenerUse ConversionEvents.OnConversionStarted, OnConversionProgress, OnConversionCompleted.
Staged interfaces that expose the old per‑result methodsRegister 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.

APIDescription
IMarkdownImageSavingCallbackImplemented by the caller and assigned to MarkdownOptions.ImageSavingCallback. Invoked once per extracted image.
MarkdownImageSavingArgsArgument passed to the callback; mutable properties: ImageFileName, ImageStream, KeepImageStreamOpen.
MarkdownOptions.ImageSavingCallbackProperty 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 ![](image0.png)
        // 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