Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

### [3.2.16] - 2026-04-24
- Added global `Utils.MuPDFLock` and synchronized MuPDF native calls for improved thread safety.
- Improved Tesseract OCR stability in the `PDF4LLM` OCR pipeline and hardened OCR helper behavior.
- Fixed a regression in Llama `LoadData` and added a new `TableExtract` demo sample.
- Updated `PDF4LLM` package metadata and NuGet project files.

### [3.2.15] - 2026-04-17
- Migrated the helper package from `MuPDF.NET4LLM` to `PDF4LLM` and refreshed the package layout, demos, and documentation.
- Added file-path overloads for `ToMarkdown`, `ToJson`, and `ToText` helpers.
- Updated `PDF4LLM` package support for the latest MuPDF bindings and metadata.

### [3.2.14] - 2026-03-23
- Fixed issue #234 in page/text utilities and added a regression test in `UtilsTest`.
- Minor `PDF4LLM` documentation and comment updates.

### [3.2.13] - 2026-03-18
- Added **MuPDF.NET4LLM** as a separate NuGet package: LLM/RAG helpers for PDF-to-Markdown conversion, layout parsing, document structure analysis, and LlamaIndex integration. Install via `dotnet add package MuPDF.NET4LLM`; depends on MuPDF.NET.
- Fixed `DocumentWriter` leak in `Story.WriteWithLinks` and `Story.WriteStabilizedWithLinks` (dispose via `using`).
Expand Down
4 changes: 3 additions & 1 deletion Demo/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Demo
using System.Threading.Tasks;

namespace Demo
{
/// <summary>
/// GitHub samples entry point. With no arguments, all samples run; see <see cref="SampleMenu"/>.
Expand Down
1 change: 1 addition & 0 deletions Demo/SampleMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ private sealed record Sample(string Category, string Name, string Description, A
new("Regression & diagnostics", "issue-213", "Repro: drawing paths / line width", _ => Program.TestIssue213()),
new("Regression & diagnostics", "issue-1880", "Repro: read Data Matrix barcodes", _ => Program.TestIssue1880()),
new("Regression & diagnostics", "issue-234", "Repro: pixmap scale + insert image", _ => Program.TestIssue234()),
new("Regression & diagnostics", "pixmap-parallel", "Repro: parallel Pixmap.ToBytes rendering", _ => Program.TestPixmapParallel()),
new("Regression & diagnostics", "jbig2", "Rewrite images with FAX recompression", _ => Program.TestRecompressJBIG2()),
};

Expand Down
35 changes: 35 additions & 0 deletions Demo/Samples/Regression/Program.Regression.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using System.Threading.Tasks;

namespace Demo
{
internal partial class Program
Expand All @@ -16,6 +18,8 @@ internal static void TestIssue234()
page.Dispose();
doc.Save("issue_234.pdf");
doc.Close();

Console.WriteLine("Saved issue_234.pdf");
}

internal static void TestRecompressJBIG2()
Expand All @@ -35,6 +39,8 @@ internal static void TestRecompressJBIG2()

doc.Save(@"e:\TestRecompressJBIG2.pdf");
doc.Close();

Console.WriteLine("Saved e:\\TestRecompressJBIG2.pdf");
}

internal static void TestIssue1880()
Expand Down Expand Up @@ -164,5 +170,34 @@ internal static void TestIssue213()
//writer.Close();
}

internal static void TestPixmapParallel()
{
const int iterations = 300;
const int degreeOfParallelism = 10;

var pdfPath = Path.Combine(@"..\..\..\TestDocuments\TestPdf1.pdf");
var pdf = File.ReadAllBytes(pdfPath);

Console.WriteLine($"MuPDF.NET parallel Pixmap.ToBytes repro");
Console.WriteLine($"PDF: {pdfPath}");
Console.WriteLine($"Iterations: {iterations}");
Console.WriteLine($"Degree of parallelism: {degreeOfParallelism}");
Console.WriteLine();

Parallel.ForEach(
Enumerable.Range(0, iterations),
new ParallelOptions { MaxDegreeOfParallelism = degreeOfParallelism },
iteration =>
{
using var document = new Document(stream: pdf, fileType: "pdf");
using var page = document[0];
using var pixmap = page.GetPixmap(new Matrix(2, 2));

var png = pixmap.ToBytes("png");
Console.WriteLine($"Iteration {iteration + 1}: rendered {png.Length} bytes");
});

Console.WriteLine("Completed without crashing.");
}
}
}
Binary file added Demo/TestDocuments/TestPdf1.pdf
Binary file not shown.
60 changes: 60 additions & 0 deletions MuPDF.NET.Test/PixmapTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,65 @@ public void InvertIrect1()
//Assert.Pass();
}

[Test]
public void TestPixmapToBytes()
{
// Test single pixmap creation and PNG conversion with matrix scaling
Document doc = new Document("../../../resources/cython.pdf");
Page page = doc[0];
Pixmap pixmap = page.GetPixmap(new Matrix(2, 2));

byte[] png = pixmap.ToBytes("png");

// Verify PNG bytes are generated and valid
Assert.That(png.Length, Is.GreaterThan(0));
// PNG magic bytes: 137, 80, 78, 71
Assert.That(png[0], Is.EqualTo(137));
Assert.That(png[1], Is.EqualTo(80));
Assert.That(png[2], Is.EqualTo(78));
Assert.That(png[3], Is.EqualTo(71));

pixmap.Dispose();
page.Dispose();
doc.Close();
}

[Test]
public void TestPixmapParallel()
{
// Test parallel pixmap rendering with PNG conversion (simulating TestPixmap())
const int iterations = 50; // Reduced from 500 for unit test performance
const int degreeOfParallelism = 4; // Reduced from 10 for unit test performance

using (var document = new Document("../../../resources/cython.pdf"))
{
var renderResults = new System.Collections.Concurrent.ConcurrentBag<int>();
var errors = new System.Collections.Concurrent.ConcurrentBag<Exception>();

Parallel.ForEach(
Enumerable.Range(0, iterations),
new ParallelOptions { MaxDegreeOfParallelism = degreeOfParallelism },
iteration =>
{
try
{
using var page = document[0];
using var pixmap = page.GetPixmap(new Matrix(2, 2));
var png = pixmap.ToBytes("png");
renderResults.Add(png.Length);
}
catch (Exception ex)
{
errors.Add(ex);
}
});

// Verify all iterations completed successfully
Assert.That(errors.Count, Is.EqualTo(0), $"Parallel rendering encountered errors: {string.Join(", ", errors.Select(e => e.Message))}");
Assert.That(renderResults.Count, Is.EqualTo(iterations), "Not all iterations completed");
Assert.That(renderResults.All(size => size > 0), Is.True, "All PNG bytes should be valid");
}
}

}
}
34 changes: 24 additions & 10 deletions MuPDF.NET/DisplayList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,38 +33,52 @@ public Rect Rect
/// <param name="rect">The page's rectangle.</param>
public DisplayList(Rect rect)
{
_nativeDisplayList = new FzDisplayList(rect.ToFzRect());
lock (Utils.MuPDFLock)
{
_nativeDisplayList = new FzDisplayList(rect.ToFzRect());
}
ThisOwn = true;
}

public DisplayList(DisplayList displayList)
{
_nativeDisplayList = displayList.ToFzDisplayList();
lock (Utils.MuPDFLock)
{
_nativeDisplayList = displayList.ToFzDisplayList();
}
}

public DisplayList(PdfPage pdfPage, int annots = 1)
{
_fzPage = new FzPage(pdfPage);
if (annots != 0)
_nativeDisplayList = mupdf.mupdf.fz_new_display_list_from_page(_fzPage);
else
_nativeDisplayList = mupdf.mupdf.fz_new_display_list_from_page_contents(_fzPage);
lock (Utils.MuPDFLock)
{
_fzPage = new FzPage(pdfPage);
if (annots != 0)
_nativeDisplayList = mupdf.mupdf.fz_new_display_list_from_page(_fzPage);
else
_nativeDisplayList = mupdf.mupdf.fz_new_display_list_from_page_contents(_fzPage);
}
}

public void Dispose()
{
if (_nativeDisplayList != null)
{
_nativeDisplayList.Dispose();
lock (Utils.MuPDFLock)
{
_nativeDisplayList.Dispose();
}
_nativeDisplayList = null;
ThisOwn = false;
}

if (_fzPage != null)
{
_fzPage.Dispose();
lock (Utils.MuPDFLock)
{
_fzPage.Dispose();
}
_fzPage = null;
ThisOwn = false;
}
}

Expand Down
Loading