Skip to content

Repository files navigation

Morph

NuGet Status NuGet Status NuGet Status NuGet Status

A .NET library that converts Microsoft Word DOCX documents or HTML content into PNG images, PDF, semantic HTML, or Markdown.

Either input can produce any of the four outputs, and a document is parsed once no matter how many formats it is exported to.

Try it live in the browser — a Blazor WebAssembly app that converts a DOCX to PNG, PDF, Markdown or plain text client-side, built on Morph.ImageSharp and Morph.Pdf.

Open Source Maintenance Fee

This project participates in the Open Source Maintenance Fee. The source code is freely available under the terms of the license. To support sustainable maintenance, use of the project's official binary releases in revenue-generating activities and all government agencies requires adherence to the Open Source Maintenance Fee EULA. The fee is paid by sponsoring Papyrine.

This project uses SponsorCheck to surface a build-time reminder in consuming projects that are not yet sponsoring.

Requirements

  • .NET 10.0 or later
  • Cross-platform support: Windows, macOS, Linux

NuGet packages

Which package is needed depends on the output format, not the input — DOCX and HTML are both handled by every package:

Output Package Notes
HTML Morph No rendering backend required
Markdown Morph No rendering backend required
PDF Morph.Pdf Vector text via PdfSharp
PNG Morph.Skia or Morph.ImageSharp Pick a rendering backend

Morph.Skia, Morph.ImageSharp and Morph.Pdf all depend on Morph, so any of them also brings the HTML and Markdown exporters.

Features

Text Formatting

  • Font families and sizes
  • Bold, italic, underline, strikethrough
  • Text colors and highlighting
  • All caps, small caps
  • Superscript, subscript
  • Character spacing

Paragraph Formatting

  • Text alignment (left, right, center, justified)
  • Indentation (first-line, hanging, left, right)
  • Spacing (before, after, line spacing)
  • Contextual spacing
  • Paragraph borders

Document Structure

  • Multiple sections with different margins/orientation
  • Page breaks (manual and automatic)
  • Section breaks (continuous, next page, odd/even)
  • Headers and footers
  • Page numbering
  • Line numbering

Tables

  • Complex table structures with merged cells
  • Cell borders and shading
  • Table styles
  • Nested tables
  • Column widths

Lists

  • Bullet lists
  • Numbered lists
  • Multi-level lists with various numbering styles
  • Custom list formatting

Graphics

  • Embedded images (JPEG, PNG)
  • Shapes (rectangles, circles, etc.)
  • Drawing objects
  • SVG content
  • Ink/handwriting annotations

Advanced Features

  • Theme support (colors, fonts)
  • Compatibility modes (Word 2007 and later)
  • Font resolution — name-table-driven, deterministic across platforms
  • Hyphenation
  • HTML content via AltChunk

Export fidelity (HTML / Markdown / PDF)

The HTML, Markdown and PDF exporters run off the same parsed model as the PNG renderers above — the DOCX parser for a .docx source, the HTML parser for an HTML source — so the same content carries across, each within the limits of its format:

  • HTML — semantic tags (<strong>, <em>, <u>, <h1><h6>, <table>, <ul>/<ol>) over one embedded stylesheet, with inline overrides only where a run or paragraph deviates from the document defaults. Theme colours (including themeShade / themeTint), per-run fonts (with generic fallbacks) and sizes, the page background, paragraph spacing / indentation / alignment / borders, and table cell widths / shading / borders / vertical alignment are preserved; background shapes, gradients and accent panels are emitted as inline SVG behind the text.
  • Markdown — CommonMark with GFM pipe tables; adjacent runs are coalesced so emphasis stays well-formed and headings stay clean.
  • PDF — vector text via PdfSharp, paginated to match the source page layout.

Export galleries — a visual index of every scenario's exporter output (PDF pages beside the Word reference render):

Rendering Backends

Morph supports two rendering backends:

Backend Package (DOCX + HTML) Pros
SkiaSharp Morph.Skia Mature, includes SVG support
ImageSharp Morph.ImageSharp Fully managed (no native dependencies)

Rendering comparison gallery — every scenario rendered by both backends side by side against the Microsoft Word reference image, with per-page error metrics.

Usage

DOCX to PNG

The examples below use the SkiaSharp backend. To use ImageSharp instead, replace SkiaDocumentConverter with ImageSharpDocumentConverter.

Basic Usage - Save to Files

var converter = new SkiaDocumentConverter();

var result = converter.ConvertToImages(
    "document.docx",
    "output-folder");

Console.WriteLine($"Generated {result.PageCount} pages");
foreach (var path in result.ImagePaths)
{
    Console.WriteLine($"Created: {path}");
}

snippet source | anchor

In-Memory Conversion

var converter = new SkiaDocumentConverter();

var imageData = converter.ConvertToImageData("document.docx");

foreach (var pngBytes in imageData)
{
    // Use the PNG byte array as needed
}

snippet source | anchor

Stream-Based Conversion

var converter = new SkiaDocumentConverter();

using var stream = File.OpenRead("document.docx");

// From stream to files
var result = converter.ConvertToImages(stream, "output-folder");

// Or from stream to memory
var imageData = converter.ConvertToImageData(stream);

snippet source | anchor

With Custom Options

var converter = new SkiaDocumentConverter();

var options = new ImageExportOptions
{
    Dpi = 300,
    FontWidthScale = 1.08
};

var result = converter.ConvertToImages(
    "document.docx",
    "output-folder",
    options);

snippet source | anchor

DOCX to HTML / Markdown / PDF

Morph also serializes a DOCX directly to semantic HTML, Markdown, or a vector-text PDF — no backend choice needed for HTML / Markdown. Per-format options classes (HtmlExportOptions, MarkdownExportOptions, PdfExportOptions) carry the knobs relevant to each output.

The HTML output renders background shapes, gradients and accent panels from the source as inline SVG behind the text, so coloured backgrounds and decorative artwork survive the conversion.

Basic — HTML

var html = DocumentConverter.ConvertToHtml("document.docx");
File.WriteAllText("document.html", html);

snippet source | anchor

Basic — Markdown

var markdown = DocumentConverter.ConvertToMarkdown("document.docx");
File.WriteAllText("document.md", markdown);

snippet source | anchor

Basic — PDF

var outputPath = "document.pdf";
PdfDocumentConverter.ConvertToPdf("document.docx", outputPath);

snippet source | anchor

Parse once, export many

For multi-format export, WordDocument parses the source a single time and supports calling as many ExportToXxx methods as needed. The PDF extension method comes from Morph.Pdf; HTML and Markdown are built in.

// Parse once with WordDocument, then export to as many formats as you like — the source
// .docx is only opened and parsed a single time.
var document = new WordDocument("document.docx");

File.WriteAllText("document.html", document.ExportToHtml());
File.WriteAllText("document.md",   document.ExportToMarkdown());
document.ExportToPdf("document.pdf");   // extension method from Morph.Pdf

snippet source | anchor

Custom image handling

By default Morph inlines images as base64 data URIs. Pass an ImageHandler to write images to disk, upload them to a CDN, or reference them however suits the calling pipeline.

// Write images to a media folder and reference them relatively, instead of base64-inlining.
Directory.CreateDirectory("media");
var html = DocumentConverter.ConvertToHtml(
    "document.docx",
    new()
    {
        ImageHandler = image =>
        {
            var extension = image.ContentType switch
            {
                "image/svg+xml" => "svg",
                "image/jpeg"    => "jpg",
                _               => "png"
            };
            var path = $"media/image-{image.Index}.{extension}";
            File.WriteAllBytes(path, image.Data);
            return path;
        }
    });

snippet source | anchor

Warning callback

Some source features can't always be represented in the chosen output (ink strokes in HTML, foreground vector shapes in PDF, etc.). Pass an OnWarning callback to discover what was dropped.

// Discover features in the source that couldn't be fully represented in the output —
// unsupported elements (ink strokes, vector shapes), missing fonts, etc.
var warnings = new List<ExportWarning>();
var html = DocumentConverter.ConvertToHtml(
    "document.docx",
    new()
    {
        OnWarning = warnings.Add
    });

foreach (var warning in warnings)
{
    Console.WriteLine($"[{warning.Kind}] {warning.Message}");
}

snippet source | anchor

PDF page range

Render only specific pages — useful for previews / thumbnails.

// Render only the first three pages of the document.
var firstThreePages = PdfDocumentConverter.ConvertToPdf(
    "document.docx",
    new()
    {
        Pages = new(Start: 1, End: 3)
    });

File.WriteAllBytes("document-preview.pdf", firstThreePages);

snippet source | anchor

HTML to PNG

To use ImageSharp instead, replace SkiaHtmlConverter with ImageSharpHtmlConverter. HTML parsing is asynchronous (AngleSharp), so these APIs return a Task.

Basic Usage - Save to Files

var converter = new SkiaHtmlConverter();

var result = await converter.ConvertToImages(
    "<h1>Hello</h1><p>World</p>",
    "output-folder");

Console.WriteLine($"Generated {result.PageCount} pages");
foreach (var path in result.ImagePaths)
{
    Console.WriteLine($"Created: {path}");
}

snippet source | anchor

In-Memory Conversion

var converter = new SkiaHtmlConverter();

var imageData = await converter.ConvertToImageData("<h1>Hello</h1><p>World</p>");

foreach (var pngBytes in imageData)
{
    // Use the PNG byte array as needed
}

snippet source | anchor

HTML to HTML / Markdown / PDF

The same exporters are available for an HTML source: HtmlConverter.ConvertToHtml normalizes the input into the same semantic HTML the DOCX path emits, HtmlConverter.ConvertToMarkdown converts it to Markdown, and PdfHtmlConverter.ConvertToPdf (from Morph.Pdf) paginates it into a vector-text PDF.

Basic — Markdown

var markdown = await HtmlConverter.ConvertToMarkdown("<h1>Hello</h1><p>World</p>");
File.WriteAllText("page.md", markdown);

snippet source | anchor

Basic — PDF

var pdf = await PdfHtmlConverter.ConvertToPdf("<h1>Hello</h1><p>World</p>");
File.WriteAllBytes("page.pdf", pdf);

snippet source | anchor

Parse once, export many

HtmlDocument is the HTML-source counterpart to WordDocument — construct it with LoadAsync, then export as many times as needed off the single parse.

// Parse once with HtmlDocument, then export to as many formats as you like — the
// source HTML is only parsed a single time.
var document = await HtmlDocument.LoadAsync("<h1>Hello</h1><p>World</p>");

File.WriteAllText("page.html", document.ExportToHtml());
File.WriteAllText("page.md",   document.ExportToMarkdown());
document.ExportToPdf("page.pdf");   // extension method from Morph.Pdf

snippet source | anchor

Shrinking a DOCX

A DOCX authored in Word carries parts that hold no rendering information. DocumentCleaner removes them. Applied to this repository's own 328-document test corpus it recovered 9.5 MB, 14% of the corpus on disk.

The biggest single contributor is the Explorer preview picture Word writes when "Save Thumbnails" is on: 46 documents carried one, totalling 7.1 MB, and in the worst case a 3.85 MB card template was 91% preview picture, leaving 350 KB once stripped.

DocumentParts Package location What it is
Thumbnail docProps/thumbnail.* The preview picture Explorer shows. Usually the largest part in a template.
Glossary word/glossary/ Building blocks and Quick Parts, used only by Word's insert UI.
CustomXml customXml/ Custom XML data islands and their properties.
RevisionAuthors word/people.xml Display names for tracked-change author IDs.

None of these are reachable from word/document.xml content, so removing them cannot change what any of Morph's exporters produce. Parts that survive are copied across verbatim — only the package relationships and [Content_Types].xml are rewritten, and only to drop entries that would otherwise dangle.

// Strips every part that carries no rendering information. Returns what was
// actually removed, or DocumentParts.None if there was nothing to strip — in
// which case the file is left byte-for-byte untouched.
var removed = DocumentCleaner.Remove("document.docx");

Console.WriteLine($"Removed: {removed}");

snippet source | anchor

DocumentParts is a [Flags] enum, so a subset can be selected, and Find reports what a package holds without touching it:

// Drop only the Explorer preview picture, keeping building blocks and custom XML.
DocumentCleaner.Remove("document.docx", DocumentParts.Thumbnail);

// Or report what a package is carrying without modifying it.
var present = DocumentCleaner.Find("document.docx");
if (present.HasFlag(DocumentParts.Thumbnail))
{
    Console.WriteLine("This document has a preview picture");
}

// Stream overloads write the cleaned package to a destination of your choosing.
using var source = File.OpenRead("document.docx");
using var target = File.Create("document-clean.docx");
DocumentCleaner.Remove(source, target, DocumentParts.Thumbnail | DocumentParts.Glossary);

snippet source | anchor

One caveat on CustomXml: a content control can carry a w:dataBinding into a data island. The bound value is also cached inline in word/document.xml — which is what Morph, and Word until it refreshes, actually reads — but if the island and the cache have drifted apart, removing the island changes what Word eventually shows.

Configuration Options

Option Type Default Description
Dpi int 150 Image resolution in dots per inch

Font-related options (FontDirectory, FontFallback, DefaultFont, FontWidthScale, DeterministicRendering) are documented in docs/fonts.md.

Icon

Impossible Star designed by Rflor from The Noun Project.

About

Converts Microsoft Word DOCX documents into PNG images.

Resources

Contributing

Stars

Watchers

Forks

Releases

Sponsor this project

Used by

Contributors

Languages