onediff
Text diff and document comparison for .NET, in pure C#. Diff, patch and merge text the way git does; compare PDFs and scans into a checked list of changes, marked on the original pages or as a redline.
Open source, MIT or Apache-2.0, made by Recursive AI. Version 1.0.0, the first stable release, is on NuGet.
A quick look
The text core is one line per job. For documents, inputs are recognized by their content and the output form follows the extension of the output path.
Getting started// Text: changed words, a unified patch, a three-way merge string marked = Diff.ToMarkdownInline(Diff.Words(oldText, newText)); string patch = Diff.Unified(oldText, newText); TextMerge both = Diff.Merge(baseText, ours, theirs); // PDF: marks on v2’s own pages, or a rendered redline PdfRedline.Register(); // once at startup Redline.Create("v1.pdf", "v2.pdf", "markup.pdf"); var cmp = Comparison.Of("v1.pdf", "v2.pdf"); cmp.SaveAs("redline.pdf", PdfRedline.Rendered);
Edits, a move, and the renumbering it caused
The repository’s own sample: page 2 of blackline-inline.pdf, made by OneDiff 1.0.0 from two short versions of a services agreement. Shown here is the top of the page: the renumbering the move caused, grouped as one cascade, a replaced word, and the moved clause marked at its new place. Further down, the same clause is struck through where it was, and a quote-mark difference the settings hide is counted under “Differences not shown” instead of dropped.
What it does
Text diff
Myers, patience and histogram over any sequence; line, word, character and grapheme tokens; semantic and efficiency cleanup; a work cap for untrusted input. No dependency beyond the .NET base class library.
Diff algorithmsPatches and merge
Unified and diff-match-patch patches with fuzzy apply and conflict reporting, and three-way merge in git’s merge, diff3 and zdiff3 conflict styles.
A checked change list
Every comparison is proved complete: old plus the changes gives new, and new minus the changes gives old. Differences a setting hides, such as quotes or page numbers, are counted, never dropped. Saved as versioned JSON and checked again when read back.
The compare modelChanges as a reader sees them
Alignment that follows moves, splits and merges; typed values (30 days → 60 days); renumbering and renamed defined terms as single changes; a filter for what changed in substance.
PDF redlines
Highlights and notes on the new PDF’s own pages, a rendered and tagged redline PDF, or HTML inline or side by side. A PDF with no text layer is refused with a message naming the OCR package, never compared as empty. From the results page: “one 29.7 MB pair exceeds the renderer’s display-list limit; the rendered redline gives a clear error for it.”
PDF comparisonScans
Scanned PDFs and images read by a local OCR engine, every word with its confidence. Changes that may be recognition errors are flagged, never hidden. Latin-script only; in the results page’s words, boundary and move scores “are limited by recognition errors and by paragraphs the recognition engine merges”. Scanned PDFs aren’t yet read in the browser: the project’s test source says rasterizing them through OneImage.Pdf “fails to load an internal OneImage type under the browser runtime (TypeLoadException for OneImage.DeterministicMath, still with OneImage 1.10.5)”, so those tests are skipped there.
Scans and imagesReviewers and versions
Combine several reviewers’ copies of one original, with each change attributed and conflicts listed, or compare a chain of versions step by step, as a report in HTML or JSON.
Combine and version chainsWord, spreadsheets, slides
Not in 1.0. In the README’s words: “Coming in later releases: Word documents in and tracked-changes DOCX out (RecursiveAI.OneDiff.Word, in this repository and not in the first release), spreadsheets, slides, and the OneNLP value recognizer (RecursiveAI.OneDiff.Nlp). Until then a Word file is refused with a message that names the package.”
The onediff tool
diff, patch, merge, compare, combine and chain from a shell. onediff compare v1.pdf v2.pdf -o markup.pdf does what the library does.
Correctness and speed
The project’s own measurements at the release (not an external audit), with their conditions and every losing row on the results page.
- Against the reference tools. Myers’ edit length equals GNU diffutils 3.12’s minimum on 9,483 of 9,483 code and prose pairs. Three-way merge equals
git merge-filebyte for byte on 2,599 of 2,599 real triples in all three conflict styles. Unified patches are accepted by GNU patch andgit applyon 9,481 pairs, 2 skipped, 0 failed. - Text speed against DiffPlex 1.9.0 and DiffMatchPatch 5.0.0. Single-threaded, inputs of about 2 KB, 40 KB and 300–400 KB. Each ratio is OneDiff’s time over the comparator’s, so below 1 is faster. Each mean is the geometric mean of one area’s rows against one comparator, on x64 (AMD Ryzen 9 7950X3D, Linux): lines 0.19 against DiffPlex and 0.52 against diff-match-patch’s line mode; words 0.29 against DiffPlex, which cuts words differently and so does not do equal work; characters 0.01 against DiffPlex and 0.22 against diff-match-patch; patch apply 0.61; patch make 0.85.
- The slowest rows. Patch make on the medium input, 1.13 on x64 and 1.02 on arm64 (Apple M2, macOS); efficiency cleanup 0.89 and words against DiffPlex 0.81, both on x64. Not covered: word rows against diff-match-patch, large character, cleanup and patch rows, and native tools such as git and GNU diff.
- Documents. No managed comparator exists for PDF or scan comparison, so these are budgets: a 100-page PDF pair with about 300 changes is read, compared and marked in 841 ms on x64 and 726 ms on arm64 (budget 2 s), and rendered as an inline redline in 1,178 ms and 1,197 ms.
Status and packages
- Status
1.0.0, the first stable release. From the changelog: “The first stable release of
RecursiveAI.OneDiff,RecursiveAI.OneDiff.Documents,RecursiveAI.OneDiff.Pdf,RecursiveAI.OneDiff.Ocrand theonedifftool (RecursiveAI.OneDiff.Tool).” - Text core
RecursiveAI.OneDiff: diff, patches and three-way merge, with no dependency beyond the base class library. 1.0.0. - Documents
RecursiveAI.OneDiff.Documents: the comparison engine, combine and chains, HTML redlines and the JSON change list, on OneFont..Pdf: PDF in, marked and rendered PDF out, on OnePDF and OneRender..Ocr: scans, on OneOCR. 1.0.0. - Tool
RecursiveAI.OneDiff.Tool, theonediffcommand:dotnet tool install --global RecursiveAI.OneDiff.Tool. 1.0.0. - .NET
.NET 10, C#, no native code, NativeAOT and trimming. The same inputs and settings give byte-identical output, and nothing touches the network. The test suites of the core,
.Documents,.Pdfand.Ocralso run as browser WebAssembly apps (x64 only); tests that need files, threads, processes or AES are skipped there, 10 of 18 for.Ocr. MIT or Apache-2.0.
Go further
OneDiff is unaffiliated with any other project named OneDiff, including the Python diffusion-model accelerator of that name.