ComiX 1.0.0-preview.3

This is a prerelease version of ComiX.
dotnet add package ComiX --version 1.0.0-preview.3
                    
NuGet\Install-Package ComiX -Version 1.0.0-preview.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ComiX" Version="1.0.0-preview.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ComiX" Version="1.0.0-preview.3" />
                    
Directory.Packages.props
<PackageReference Include="ComiX" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ComiX --version 1.0.0-preview.3
                    
#r "nuget: ComiX, 1.0.0-preview.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ComiX@1.0.0-preview.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ComiX&version=1.0.0-preview.3&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=ComiX&version=1.0.0-preview.3&prerelease
                    
Install as a Cake Tool

ComiX

A .NET library for reading, extracting and converting comic book archives.

License: MIT .NET NuGet

ComiX exposes a unified object model for comic archives that is independent of the underlying container format and of the metadata standard the archive was tagged with. It provides archive access, metadata normalisation, extraction and format conversion for applications such as readers, cataloguing tools and conversion utilities. Image rendering and decoding are outside its scope.

await using var comic = await ComicBook.OpenAsync("example.cbz");

Console.WriteLine(comic.Metadata.Series);
Console.WriteLine(comic.Pages.Count);

await using var page = await comic.Pages[0].OpenAsync();

Supported formats

Container Detection Reading Conversion to CBZ
CBZ (ZIP) ✔ ✔ ✔
CBR (RAR) ✔ ✔ ✔
CB7 (7-Zip) ✔ ✔ ✔
CBT (TAR) ✔ ✔ ✔
Metadata standard Location Reading Writing Per-page information
ComicInfo.xml archive entry ✔ ✘ ✔
MetronInfo.xml archive entry ✔ ✘ ✘
CoMet comet.xml archive entry ✔ ✘ ✘
ComicBookInfo archive comment (ZIP and RAR only) ✔ ✘ ✘

Writing metadata into an existing archive is not supported yet. Conversion copies the original metadata entries and the archive comment verbatim.

Supported page formats, identified by file extension: JPEG, PNG, GIF, WebP, BMP, TIFF and AVIF.

Installation

dotnet add package ComiX

Target frameworks: net8.0 and net10.0. The only runtime dependency is SharpCompress (MIT).

Usage

Opening an archive

await using var comic = await ComicBook.OpenAsync("example.cbz");

ComiX determines the container format from the file contents. The file extension is not used as the authoritative source for container identification.

ComicBook.OpenAsync also accepts a Stream, which must be readable and seekable. The stream is not disposed by the ComicBook unless leaveOpen: false is specified.

await using var stream = File.OpenRead("example.cbz");
await using var comic = await ComicBook.OpenAsync(stream);

Opening an archive reads the container index and resolves metadata. Page content is accessed lazily and is not extracted or decoded during this operation.

Pages

ComicBook.Pages returns the archive's images in reading order. Ordering is numeric-aware, so 2.jpg precedes 10.jpg; a file named cover at the archive root is ordered first.

foreach (var page in comic.Pages)
{
    Console.WriteLine($"{page.Index}: {page.Name} ({page.ImageFormat}, {page.SizeInBytes} bytes)");
}

ComicPage.OpenAsync returns a seekable, read-only Stream over the page content. The stream is owned by the caller, is independent of the ComicBook that produced it, and remains readable after that instance has been disposed.

await using var content = await comic.Pages[0].OpenAsync();

Page attributes derived from metadata — DeclaredWidth, DeclaredHeight, Type, DeclaredSpread and Bookmark — are populated only when the source metadata declares them. ComiX does not decode image content.

Resources

Entries that are not images, including the metadata file itself, are exposed through ComicBook.Resources with the same content access API as pages.

var comicInfo = comic.Resources.FirstOrDefault(resource => resource.FileName == "ComicInfo.xml");

Metadata

Metadata from any supported standard is normalised into a single ComicMetadata instance. Values that ComiX does not map to a canonical property are retained in ComicMetadata.Extensions under their original field name.

var metadata = comic.Metadata;

Console.WriteLine(metadata.Title);
Console.WriteLine(metadata.Series);
Console.WriteLine(metadata.PublicationDate);
Console.WriteLine(metadata.Extensions["StoryArc"]);

foreach (var credit in metadata.Credits)
{
    Console.WriteLine($"{credit.Role}: {credit.Name}");
}

Credits are modelled as one entry per person and role. A person credited for two roles produces two entries; group by ComicCredit.Name to obtain one entry per person.

ComicCreditRole normalises equivalent role names across standards: Script, Story and Author all resolve to ComicCreditRole.Writer. Comparison is case-insensitive, and unrecognised roles are preserved verbatim.

var writers = metadata.Credits.Where(credit => credit.Role == ComicCreditRole.Writer);

Metadata resolution

An archive may contain more than one metadata source. Sources are resolved in the following precedence order: ComicInfo.xml, MetronInfo.xml, CoMet, then ComicBookInfo. The first source that parses successfully supplies the canonical values; the remaining sources are reported but not merged.

Console.WriteLine(metadata.Standard);                 // ComicInfo
Console.WriteLine(metadata.PrimarySource?.Location);  // ComicInfo.xml

foreach (var source in metadata.Sources)
{
    Console.WriteLine($"{source.Standard} at {source.Location} (primary: {source.IsPrimary})");
}

Metadata parsing failures

A metadata source that cannot be parsed does not prevent an archive from being opened. Resolution continues with the next source in precedence order. If no source can be parsed, canonical metadata is empty and every source located remains listed in ComicMetadata.Sources. ComicValidator reports the failure and its cause.

Archive format detection

ComicDetector identifies an archive without opening it as a ComicBook.

var info = await ComicDetector.DetectAsync("example.cbz");

Console.WriteLine(info.Format);
Console.WriteLine(info.ExtensionMatchesFormat);
Console.WriteLine(info.PageCount);

Detection reads the file header and the container index. Page content is never decompressed, so the cost is proportional to the number of entries rather than to the size of the archive.

ComicDetectionDepth.Header limits detection to the archive header. Page count, metadata presence and encryption are then not reported, and the operation cost is independent of archive size.

var options = new ComicDetectionOptions { Depth = ComicDetectionDepth.Header };
var result = await ComicDetector.DetectAsync(path, options);

Validation

ComicValidator reports defects as diagnostics rather than exceptions. Findings are classified as errors, which indicate that the archive cannot be used as a comic, or warnings, which indicate an anomaly that does not prevent use. Metadata defects are reported as warnings.

var result = await ComicValidator.ValidateAsync("example.cbz");

foreach (var issue in result.Issues)
{
    Console.WriteLine($"{issue.Severity} [{issue.Code}]: {issue.Message}");
}

ComicValidationOptions.DeepScan additionally decompresses every page and verifies that its content begins with a signature consistent with the image format implied by its file extension.

Extraction

await comic.Pages[0].ExtractAsync("cover.jpg");
await comic.Pages[0].ExtractAsync(destinationStream);
await comic.ExtractAllAsync("./output");

ComicPage.ExtractAsync writes content directly to the destination without buffering the page in memory. ComicBook.ExtractAllAsync recreates the archive's directory structure under the destination directory; ComicExtractionOptions controls overwriting, flattening and the inclusion of non-image resources.

Entry names are sanitised and the resolved destination is verified to be contained within the destination directory, so an entry cannot be written outside it.

Conversion

await using var comic = await ComicBook.OpenAsync("example.cbr");
await comic.ExportAsync("example.cbz", ComicExportFormat.Cbz);

Conversion repackages the archive. Page content is copied without re-encoding, entry names and page order are preserved, and the original metadata file is copied verbatim rather than regenerated from the canonical model. Archive comments are preserved where the target container supports them. Entries are processed one at a time, so memory consumption does not scale with archive size.

CBZ is the only supported export target. RAR compression is proprietary and cannot be produced by a managed implementation.

Caching

Caching is optional and disabled by default. ComicCache.InMemory provides a size-bounded, least-recently-used cache of extracted entry content.

var cache = ComicCache.InMemory(maxSizeInBytes: 128 * 1024 * 1024);

await using var comic = await ComicBook.OpenAsync("example.cbz", new ComicOpenOptions { Cache = cache });

A ComicCache instance may be shared by multiple ComicBook instances, in which case they share a single budget. Cache entries are keyed by archive path, file size and last write time, so a modified file does not return previously cached content.

Resource limits

Archives are treated as untrusted input. Reads are bounded by default and the limits are configurable.

var options = new ComicOpenOptions
{
    MaxEntrySizeInBytes = 256L * 1024 * 1024,  // per entry; default 256 MiB
    MaxEntryCount = 50_000,                    // per archive; default 50,000
};

Limits are enforced against the number of bytes read, not against the sizes declared by the archive.

Behaviour and guarantees

Thread safety

A ComicBook instance may be accessed concurrently. Content reads are serialised per instance for every container format, including those with random access, so concurrent reads are safe but are executed one at a time. Streams returned by ComicPage.OpenAsync and ComicResource.OpenAsync are independent of the instance that produced them and remain valid after it has been disposed.

Disposal

Disposal lets the read in progress complete. ComicBook.DisposeAsync waits for that read before releasing the archive; ComicBook.Dispose returns immediately and the archive is released when the read finishes. Reads requested after either call, including reads already queued, throw ObjectDisposedException.

Cancellation

All potentially long-running operations accept a CancellationToken. A cancelled export deletes the partially written destination file. A cancelled extraction leaves files already written in place.

Image processing

ComiX does not decode image data. Page dimensions are therefore available only when declared by the source metadata, and image formats are identified from file extensions. ComicValidationOptions.DeepScan verifies image signatures but does not decode image content.

Double-page spreads

ComicPage.DeclaredSpread reports the spread type declared by the source metadata. ComicInfo.xml is the only supported standard that describes pages individually; CoMet, ComicBookInfo and MetronInfo define a page count only. DeclaredSpread is therefore Unknown for archives tagged with those standards, for untagged archives, and for ComicInfo documents that omit the attribute.

var layout = page.DeclaredSpread switch
{
    ComicPageSpread.Double => Layout.TwoUp,
    ComicPageSpread.Single => Layout.OneUp,
    // Determine the layout from the aspect ratio when no spread type is declared.
    _ => DetermineLayoutFromAspectRatio(page),
};

Solid archives

7-Zip archives, and RAR archives created in solid mode, store entries in shared compression blocks. Accessing an individual entry may require decompressing preceding data. ComiX does not eliminate this constraint imposed by the archive format, and reports it through ComicCapabilities.RandomAccess.

if (!comic.Capabilities.HasFlag(ComicCapabilities.RandomAccess))
{
    // Sequential access is more efficient for this archive.
}

Caching is disabled by default, so every read of a page in a solid archive repeats that decompression, including reads of pages already visited. Applications that revisit pages, such as readers, should supply ComicCache.InMemory through ComicOpenOptions.Cache.

Multi-volume archives

An archive that forms part of a multi-volume set can be opened and enumerated, but content stored in the remaining volumes is unavailable. ComicValidator reports this condition as an error.

Entry names

Entry names use UTF-8 when the archive declares the UTF-8 flag. For entries without that flag, the configured entry-name encoding is used. Names consisting only of ASCII characters, as is the case for most archives, decode identically under all relevant encodings and require no configuration.

An archive whose non-ASCII names were written using a legacy code page without declaring the encoding may decode those names with replacement characters. This can affect both the reported names and the resulting page order. ComicValidator reports such entries as EntryNameEncodingSuspect, and ComicOpenOptions.EntryNameEncoding allows the consumer to specify the correct encoding:

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

var options = new ComicOpenOptions { EntryNameEncoding = Encoding.GetEncoding(1252) };
await using var comic = await ComicBook.OpenAsync("example.cbz", options);

Registering the code pages provider is the consuming application's responsibility; ComiX does not register it or take a dependency on it. The setting applies only to entry names for which the archive does not declare UTF-8.

Limitations

  • Metadata cannot be written into an existing archive.
  • CBZ is the only supported export target.
  • EPUB and PDF are not supported as containers.
  • Image dimensions and spread information are limited to what the source metadata declares.
  • Encrypted archives require a password supplied through ComicOpenOptions.Password; encrypted entries are otherwise reported by detection and validation and cannot be read.

Building from source

The .NET 10 SDK is required. The test suite targets both supported frameworks, so the .NET 8 runtime must also be installed.

dotnet build
dotnet test

License

Released under the MIT License.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on ComiX:

Package Downloads
ComicViewer.WinUI

A WinUI 3 comic reading control over ComiX: gesture-driven page transitions, zoom and pan, single and double page layouts, and bounded page caching, rendered through Direct3D 11 on a composition swap chain, with page turns that curl like paper.

ComicViewer.Core

The document abstraction of ComicViewer over ComiX and the types shared with its viewer control, together with the reading engine the control uses.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0-preview.3 71 9/24/2026
1.0.0-preview.2 87 9/16/2026
1.0.0-preview.1 60 9/16/2026

### Added

- `ComicOpenOptions.EntryNameEncoding` and `ComicValidationOptions.EntryNameEncoding`, allowing
 consumers to specify the encoding used for entry names when an archive does not declare UTF-8.
 This provides a way to correctly decode names written using legacy code pages that would otherwise
 produce replacement characters.
- `ComicValidationCode.EntryNameEncodingSuspect`, reported when a decoded entry name contains
 replacement or control characters, which is the observable signature of an encoding mismatch.
- A CI workflow building and testing on Linux, Windows and macOS.

### Changed

- Improved reading performance for solid archives by reusing sequential traversal between reads.
- Pages requested near their stored order can now share a single traversal instead of restarting
- decompression for each entry.

### Fixed

- [#12](https://github.com/lzippilli/ComiX/issues/12).