Hytone-PDF 0.8.1-beta

This is a prerelease version of Hytone-PDF.
dotnet add package Hytone-PDF --version 0.8.1-beta
                    
NuGet\Install-Package Hytone-PDF -Version 0.8.1-beta
                    
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="Hytone-PDF" Version="0.8.1-beta" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Hytone-PDF" Version="0.8.1-beta" />
                    
Directory.Packages.props
<PackageReference Include="Hytone-PDF" />
                    
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 Hytone-PDF --version 0.8.1-beta
                    
#r "nuget: Hytone-PDF, 0.8.1-beta"
                    
#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 Hytone-PDF@0.8.1-beta
                    
#: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=Hytone-PDF&version=0.8.1-beta&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Hytone-PDF&version=0.8.1-beta&prerelease
                    
Install as a Cake Tool

Hytone-PDF

alternate text is missing from this package README image

Licensed NuGet library for DOCX → PDF conversion, plus legacy DOC → DOCX conversion, on .NET.

Breaking changes in v0.8.0-beta

The conversion API was redesigned around an Aspose-style Save/SaveAsync shape, and PDF merging's appendToDocument option was folded into the existing MergePdfs utility instead of being a separate mode on conversion itself. There's no compatibility shim — update call sites directly:

Old (pre-0.8.0) New (0.8.0+)
doc.ConvertToPDF("out.pdf") doc.Save("out.pdf")
doc.ConvertToPDF("out.pdf", deleteOriginalFile: true) doc.Save("out.pdf", new PdfSaveOptions { DeleteOriginalFile = true })
HytonePdf.Convert(docxStream, pdfStream) HytonePdf.LoadDocument(docxStream).Save(pdfStream)
doc.ConvertToPDF(appendToDocument: "report.pdf") (grow in place) doc.Save("new.pdf"); HytonePdf.MergePdfs(["report.pdf", "new.pdf"], "report.pdf");
doc.ConvertToPDF(outputPath: "v2.pdf", appendToDocument: "report.pdf") (non-destructive) doc.Save("new.pdf"); HytonePdf.MergePdfs(["report.pdf", "new.pdf"], "v2.pdf");

Save/SaveAsync also gained stream overloads (doc.Save(Stream), doc.SaveAsync(Stream, ...)), and HytonePdf.LoadDocument/LoadDocumentAsync now accept a stream directly — see Convert below.

  • Package id: Hytone-PDF
  • No Microsoft Word / Office required
  • License key activated in code (RSA-signed, offline validation)

This is a commercial library (like Aspose.Words), not a hosted service. Customers install the package and call C# methods directly in their own process.

Install

dotnet add package Hytone-PDF

On install, NuGet will prompt to accept the license (LICENSE.txt in the package).

Activate a license

using Hytone.Pdf;

// Once at application startup — key issued by Hytone
HytonePdf.SetLicense("HYT1....");

// Or from a .lic file
// License.SetLicenseFromFile("hytone-pdf.lic");

Without a key, the library runs in evaluation mode (production use requires a valid key).

using Hytone.Pdf.Licensing;

if (License.IsLicensed)
{
    Console.WriteLine($"Licensed to {License.Current.LicensedTo} ({License.Current.Type})");
}

Pricing & tiers

Trial Standard
Price Free £25/month or £175/year
Output Capped at 1 page Unlimited
PDF merging (MergePdfs) Not available — throws LicenseException Included
Watermark Yes None

No key at all, an expired key, or an active Trial-type key all count as the Trial tier — the relevant check is License.Current.IsRestrictedTier, not IsLicensed (a valid, non-expired Trial key genuinely activates — IsLicensed is true — but still keeps the tier's limits, since IsLicensed is about key validity, not tier).

The trial watermark is deliberately not a single text object: it's tiled as many small, independently-positioned fragments whose draw calls are interleaved with the real page content's own draw calls, so there's no single object a PDF-editing tool (including PDFsharp itself) can find and delete to produce a clean file — removing it means correctly identifying and stripping dozens of scattered fragments throughout the whole content stream. Position, size, opacity, and draw order are randomized per document, so there's no fixed pattern to script against either. No purely visual watermark defeats a sufficiently determined, PDF-format-expert attacker with custom tooling — the realistic goal is raising the bar past casual/scripted removal, not claiming unbreakability.

Convert

using Hytone.Pdf;

var doc = HytonePdf.LoadDocument("report.docx");
doc.Save("report.pdf");

LoadDocument returns a HytoneDocument — hold onto the reference and call Save whenever you're ready. Each instance is independent (no shared state with any other loaded document), so this is safe to use from a script, a CLI tool, or a server loading and converting many documents at once from multiple threads.

For streams, or any source that isn't a file on disk, LoadDocument/Save both have stream overloads — the same object model, just a different source/destination:

using var docxStream = File.OpenRead("report.docx");
using var pdfStream = File.Create("report.pdf");
HytonePdf.LoadDocument(docxStream).Save(pdfStream);

For a server/web-app caller that doesn't want to block a thread pool thread on an I/O-heavy conversion, LoadDocumentAsync/SaveAsync are available (path and stream overloads, both with CancellationToken support):

using Hytone.Pdf.Conversion.Pdf;

var doc = await HytonePdf.LoadDocumentAsync("report.docx");
await doc.SaveAsync("report.pdf", new PdfSaveOptions { DeleteOriginalFile = true });

This is honest async, not a fabricated one: reading a file's bytes uses real asynchronous file I/O, but the DOCX parsing/PDF layout/rendering itself (via OpenXml SDK and PDFsharp, neither of which expose async APIs) runs via a thread-pool Task.Run under the hood rather than a true non-blocking pipeline — see the XML doc comments on SaveAsync for the full explanation.

Merge PDFs

The recommended way to combine documents: build up in-memory "parts" — HytoneDocument.ToPdfDocument() for a freshly-converted DOCX, HytonePdf.LoadPdf(...) for an existing PDF — then combine and Save once, no intermediate file or stream needed in between:

HytonePdfDocument cover = HytonePdf.LoadPdf("cover.pdf");
HytoneDocument chapter1 = HytonePdf.LoadDocument("chapter1.docx");
HytoneDocument chapter2 = HytonePdf.LoadDocument("chapter2.docx");

cover.Append(chapter1).Append(chapter2).Save("book.pdf");

Append chains fluently and accepts either a HytoneDocument (converts and appends in one call) or another HytonePdfDocument. Prefer a flat ordered list instead of chaining? MergePdfs has an overload for that too:

HytonePdf.MergePdfs(
    [HytonePdf.LoadPdf("cover.pdf"), chapter1.ToPdfDocument(), chapter2.ToPdfDocument()],
    "book.pdf");

For merging PDFs that are already files on disk with no conversion involved at all, the plain path/stream overloads are still there and remain the simplest option:

HytonePdf.MergePdfs(["part1.pdf", "part2.pdf"], "combined.pdf");
using var a = File.OpenRead("part1.pdf");
using var b = File.OpenRead("part2.pdf");
using var merged = File.Create("combined.pdf");
HytonePdf.MergePdfs([a, b], merged);

MergePdfs's path overload also handles the output path being one of its own inputs safely (a temp file under the hood, swapped in atomically once the merge succeeds) — the "grow an existing PDF in place" pattern:

HytonePdf.MergePdfs(["report.pdf", "chapter2_only.pdf"], "report.pdf");

Convert legacy .doc

Word 97-2003 binary (.doc) documents convert to .docx first — chain into LoadDocument to go straight to PDF:

HytonePdf.ConvertDocToDocx("legacy.doc", "legacy.docx");
HytonePdf.LoadDocument("legacy.docx").Save("legacy.pdf");

For streams, or any non-file source, use the stream overload instead:

using var docStream = File.OpenRead("legacy.doc");
using var docxStream = File.Create("legacy.docx");
HytonePdf.ConvertDocToDocx(docStream, docxStream);

License key format

Keys are signed strings:

HYT1.<base64url-json-payload>.<base64url-rsa-sha256-signature>

The NuGet package embeds only the public key (verify). Keys are issued with the vendor tool that holds the private key.

Field Meaning
Product Always Hytone-PDF
Type Trial, Standard, Enterprise, OEM
Customer Licensee name
Expires Optional yyyy-MM-dd (omit = perpetual)

Issue keys (vendor only)

Keep keys/hytone-pdf-private.pem secret. Never publish it or the issuer tool as a public package.

# Perpetual Standard key
dotnet run --project tools/Hytone.Pdf.LicenseIssuer -- issue ^
  -c "Acme Ltd" -t Standard -o acme.lic

# 30-day trial
dotnet run --project tools/Hytone.Pdf.LicenseIssuer -- issue ^
  -c "Prospect Inc" -t Trial -e 2026-09-01

# Verify
dotnet run --project tools/Hytone.Pdf.LicenseIssuer -- verify acme.lic

Build & pack the licensed NuGet

cd source/Hytone-PDF
dotnet test
dotnet pack src/Hytone-PDF/Hytone-PDF.csproj -c Release -o artifacts

Output: artifacts/Hytone-PDF.<version>.nupkg

Publish to nuget.org or a private feed when ready:

dotnet nuget push artifacts/Hytone-PDF.*.nupkg --api-key <KEY> --source https://api.nuget.org/v3/index.json

Status

Area Status
Licensed NuGet packaging Done
RSA license keys + evaluation mode Done
Vendor license issuer tool Done
Public convert methods (instance-based document, path + stream + async overloads, all thread-safe) Done
PDF merging (standalone, or append-after-conversion via Save + MergePdfs) Working
DOCX → PDF engine Section-aware two-pass layout
Style inheritance / bullets / fonts Working
Sections (portrait/landscape pgSz) Working
Multi-column sections (w:cols, incl. column breaks) Working — sequential-fill (see Known limitations)
Hard page breaks Working
Headers & footers (incl. PAGE/NUMPAGES, first-page, odd/even) Working
Table grid widths (gridCol / tcW) Working
Table vertical cell merge (vMerge) Working
Nested tables (a table inside a table cell) Working (see Known limitations)
Cell borders (incl. single-edge accents) Working
Floating/anchored images (wp:anchor, incl. behindDoc z-order) Working (see Known limitations)
Tracked changes (w:ins/w:del) Rendered as accepted (insertions kept, deletions dropped)
Hyperlinks Clickable PDF link annotations
List restart (w:startOverride) Working
Character formatting (subscript/superscript, strikethrough, caps/small caps, highlight, character spacing) Working
Images / charts Working
Font resolution Windows only — matches installed TTF/TTC fonts by parsing their actual name table, with an open-license substitute for a common decorative font that isn't installed (see Known limitations)
Pixel-perfect Word layout Improving (see Known limitations)
DOC → DOCX (legacy Word 97-2003 binary) Working (see Known limitations)

Known limitations

Deliberately out of scope for now — each is a substantial subsystem on its own rather than a quick addition, and none of the internal validation documents used to harden this engine exercise them:

  • Footnotes / endnotes are not rendered.
  • Multi-column sections use sequential-fill, not Word's end-of-section column balancing — a column fills top-to-bottom before the next one starts, but the last page of a section isn't re-balanced to even out column heights the way Word's own renderer does.
  • Floating/anchored images (wp:anchor) render at their authored absolute/relative position and respect behindDoc z-order, but surrounding text is never reflowed around them — no wrap-type simulation (Square/Tight/Through), and the image is fixed to whichever page its anchoring paragraph starts on rather than independently floating across a page break. Keyword alignment (wp:align, e.g. "center"/"right" relative to a reference frame) falls back to a zero offset rather than being resolved.
  • Nested tables don't split across a page break independently of their parent row — the whole row (nested content included) moves to the next page together if it doesn't fit, the same behavior an oversized single cell already had.
  • Field computation beyond PAGE/NUMPAGESTOC, REF, STYLEREF, DATE, etc. are not evaluated.
  • Comments are not rendered (matches Word's own default print/PDF behavior).
  • Small caps and character spacing are visual approximations (uppercase transform; per-span advance) rather than true sub-glyph resizing / per-glyph tracking.
  • Font resolution is Windows-only — it reads the Windows Fonts folder directly, so conversion currently requires a Windows host (not yet validated in Linux/Docker deployments).
  • A document-requested font that isn't installed on the host falls back to Arial by default — except for a curated list of well-known decorative fonts (mostly Microsoft Office/Publisher fonts like Tempus Sans ITC or Matisse ITC, which aren't part of a bare Windows install and can't be redistributed by us), where the closest-category open-license font bundled in the assembly is used instead: a handwriting style, a bold marker/brush style, or an elegant script style. This is a best-effort visual approximation, not a literal match, and only kicks in for the specific fonts and keyword patterns WindowsFontResolver recognizes as decorative — an unrecognized plain font still falls back to Arial as before. Installing the actual requested font on the host (e.g. by having Office/Publisher installed) always takes priority over any substitute.
  • DOC → DOCX is built in part using components from b2xtranslator (BSD-3-Clause; see THIRD-PARTY-NOTICES.md — used under license, not affiliated with or endorsed by its authors), a small, niche project rather than a widely field-tested one — verified here against a real .doc fixture with headings, character formatting, lists, and a page break, but not yet exercised against a broad, adversarial .doc corpus the way the DOCX → PDF engine has been. Report any conversion gaps found in real-world files.

Solution layout

Hytone-PDF/
├── LICENSE.txt                 # EULA packed into the NuGet
├── keys/
│   ├── hytone-pdf-public.pem   # OK to keep with source
│   └── hytone-pdf-private.pem  # SECRET — gitignored
├── src/Hytone-PDF/             # PackageId: Hytone-PDF
├── tools/Hytone.Pdf.LicenseIssuer/
└── tests/Hytone-PDF.Tests/

License

Proprietary. See LICENSE.txt. A signed license key is required for production use.

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 was computed.  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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.8.1-beta 72 8/5/2026
0.8.0-beta 61 8/5/2026
0.7.1-beta 67 8/4/2026
0.7.0-beta 57 8/4/2026
0.6.0-beta 58 8/4/2026
0.5.0-beta 68 8/3/2026