diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f526ab7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,35 @@ +# Auto detect text files and perform LF normalization +* text=auto eol=lf + +# Keep Windows scripts CRLF-friendly when checked out on Windows +*.ps1 text eol=crlf +*.cmd text eol=crlf +*.bat text eol=crlf + +# C# / project files +*.cs text diff=csharp +*.csproj text +*.sln text +*.props text +*.targets text +*.config text +*.json text +*.md text +*.txt text + +# Binary fixtures +*.pdf binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.docx binary +*.hwpx binary +*.odt binary +*.xlsx binary +*.pptx binary +*.dll binary +*.so binary +*.dylib binary +*.nupkg binary +*.snupkg binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2084173 --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +## Build outputs +[Bb]in/ +[Oo]bj/ +[Dd]ebug/ +[Rr]elease/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +artifacts/ +*.dll +*.exe +*.pdb +*.cache +project.lock.json +project.fragment.lock.json + +## Example run outputs (keep folder via Output/.gitignore) +Output/** +!Output/.gitignore + +## Local NuGet overrides (do not commit machine-specific feeds) +NuGet.Config.local +nuget.config.user + +## IDE / OS +.vs/ +.idea/ +*.user +*.userosscache +*.suo +*.sln.docstates +*.rsuser +*.DotSettings.user +Thumbs.db +Desktop.ini +.DS_Store + +## Test / logs +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* +*.log +*.trx + +## NuGet +*.nupkg +*.snupkg +**/packages/* +!**/packages/build/ + +## Rider / VS Code (optional local) +*.DotSettings +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json.example diff --git a/Common/ConsoleEx.cs b/Common/ConsoleEx.cs new file mode 100644 index 0000000..bab6eed --- /dev/null +++ b/Common/ConsoleEx.cs @@ -0,0 +1,69 @@ +using System; +using System.Linq; +using System.Reflection; +using MuPDF.NET; + +namespace MuPDF.NET.Examples.Common +{ + public static class ConsoleEx + { + public static void Title(string text) + { + PrintPackageVersions(); + Console.WriteLine(); + Console.WriteLine("=== " + text + " ==="); + } + + /// + /// Print Artifex package versions used by this process. + /// Always prints MuPDF.NET + MuPDF; also Office / PDF4LLM when referenced. + /// + public static void PrintPackageVersions() + { + var v = Constants.Version; + Console.WriteLine($"MuPDF {v.MuPdfVersion}"); + Console.WriteLine($"MuPDF.NET {v.MuPdfNetVersion}"); + + string? office = TryGetPackageVersion("MuPDF.NET.Office"); + if (office != null) + Console.WriteLine($"MuPDF.NET.Office {office}"); + + string? pdf4llm = TryGetPackageVersion("MuPDF.NET.PDF4LLM"); + if (pdf4llm != null) + Console.WriteLine($"MuPDF.NET.PDF4LLM {pdf4llm}"); + } + + public static void Info(string text) => Console.WriteLine(text); + + public static void Done(string? path = null) + { + if (!string.IsNullOrEmpty(path)) + Console.WriteLine("Wrote: " + path); + Console.WriteLine("Done."); + } + + static string? TryGetPackageVersion(string assemblyName) + { + try + { + Assembly? asm = AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => string.Equals( + a.GetName().Name, assemblyName, StringComparison.OrdinalIgnoreCase)); + if (asm == null) + asm = Assembly.Load(assemblyName); + + string? informational = asm + .GetCustomAttribute() + ?.InformationalVersion; + if (!string.IsNullOrWhiteSpace(informational)) + return informational; + + return asm.GetName().Version?.ToString(); + } + catch + { + return null; + } + } + } +} diff --git a/Common/ExampleArgs.cs b/Common/ExampleArgs.cs new file mode 100644 index 0000000..90b7eaa --- /dev/null +++ b/Common/ExampleArgs.cs @@ -0,0 +1,34 @@ +using System; + +namespace MuPDF.NET.Examples.Common +{ + /// Command-line flags shared by all examples. + public static class ExampleArgs + { + /// + /// When true, write current results into Expected/ instead of comparing. + /// Pass --update-expected after a trusted NuGet upgrade to refresh baselines. + /// + public static bool UpdateExpected { get; private set; } + + public static void Parse(string[] args) + { + foreach (string a in args) + { + if (string.Equals(a, "--update-expected", StringComparison.OrdinalIgnoreCase) + || string.Equals(a, "-u", StringComparison.OrdinalIgnoreCase)) + { + UpdateExpected = true; + } + else if (string.Equals(a, "--help", StringComparison.OrdinalIgnoreCase) + || string.Equals(a, "-h", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine("Options:"); + Console.WriteLine(" --update-expected, -u Refresh Expected/ baselines from this run"); + Console.WriteLine(" --help, -h Show help"); + Environment.Exit(0); + } + } + } + } +} diff --git a/Common/ExamplePaths.cs b/Common/ExamplePaths.cs new file mode 100644 index 0000000..50d7f84 --- /dev/null +++ b/Common/ExamplePaths.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; + +namespace MuPDF.NET.Examples.Common +{ + /// + /// Resolves shared Input/ and Output/ folders for example projects. + /// + public static class ExamplePaths + { + static readonly Lazy RootLazy = new(FindRoot); + + /// Solution root (folder that contains Input/ and Output/). + public static string Root => RootLazy.Value; + + public static string InputRoot => Path.Combine(Root, "Input"); + public static string OutputRoot => Path.Combine(Root, "Output"); + + public static string MuPdfNetInput(string fileName) => + Require(Path.Combine(InputRoot, "MuPDF.NET", fileName)); + + public static string Pdf4LlmInput(string fileName) => + Require(Path.Combine(InputRoot, "MuPDF.NET.PDF4LLM", fileName)); + + public static string OfficeInput(string fileName) => + Require(Path.Combine(InputRoot, "MuPDF.NET.Office", fileName)); + + /// + /// Output path under Output/{product}/{exampleName}/. Creates the directory. + /// + public static string Output(string product, string exampleName, string fileName) + { + string dir = Path.Combine(OutputRoot, product, exampleName); + Directory.CreateDirectory(dir); + return Path.Combine(dir, fileName); + } + + /// Directory {product}/{exampleName}/Expected/ under the solution root. + public static string ExpectedDir(string product, string exampleName) => + Path.Combine(Root, product, exampleName, "Expected"); + + public static string ExpectedFile(string product, string exampleName, string fileName) => + Path.Combine(ExpectedDir(product, exampleName), fileName); + + static string Require(string path) + { + if (!File.Exists(path)) + throw new FileNotFoundException($"Example input not found: {path}", path); + return path; + } + + static string FindRoot() + { + string? dir = AppContext.BaseDirectory; + for (int i = 0; i < 10 && !string.IsNullOrEmpty(dir); i++) + { + if (Directory.Exists(Path.Combine(dir, "Input")) + && Directory.Exists(Path.Combine(dir, "Output"))) + { + return dir; + } + dir = Directory.GetParent(dir)?.FullName; + } + + throw new DirectoryNotFoundException( + "Could not locate MuPDF.NET.Examples root (expected Input/ and Output/). " + + "Run examples from the built project under this solution."); + } + } +} diff --git a/Common/Examples.Common.csproj b/Common/Examples.Common.csproj new file mode 100644 index 0000000..e0e6b63 --- /dev/null +++ b/Common/Examples.Common.csproj @@ -0,0 +1,14 @@ + + + + MuPDF.NET.Examples.Common + MuPDF.NET.Examples.Common + false + + + + + + + + diff --git a/Common/OfficeJsonFingerprint.cs b/Common/OfficeJsonFingerprint.cs new file mode 100644 index 0000000..d603428 --- /dev/null +++ b/Common/OfficeJsonFingerprint.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text; +using System.Text.Json; + +namespace MuPDF.NET.Examples.Common +{ + /// + /// Portable checks for MuPDFOffice.ToJson output. Font names and + /// glyph metrics differ by OS (e.g. Arial on Windows vs Liberation Serif on + /// Linux), so baselines compare page size and extracted text only. + /// + public static class OfficeJsonFingerprint + { + public static Dictionary FromJson(string json) + { + using JsonDocument doc = JsonDocument.Parse(json); + JsonElement root = doc.RootElement; + if (!root.TryGetProperty("pages", out JsonElement pages) || + pages.ValueKind != JsonValueKind.Array) + { + return new Dictionary + { + ["pageCount"] = "0", + ["pageWidth"] = "", + ["pageHeight"] = "", + ["texts"] = "", + }; + } + + var texts = new StringBuilder(); + string width = ""; + string height = ""; + int pageIndex = 0; + + foreach (JsonElement page in pages.EnumerateArray()) + { + if (string.IsNullOrEmpty(width) && + page.TryGetProperty("width", out JsonElement w)) + width = FormatNumber(w); + + if (string.IsNullOrEmpty(height) && + page.TryGetProperty("height", out JsonElement h)) + height = FormatNumber(h); + + if (!page.TryGetProperty("blocks", out JsonElement blocks) || + blocks.ValueKind != JsonValueKind.Array) + continue; + + foreach (JsonElement block in blocks.EnumerateArray()) + { + if (!block.TryGetProperty("type", out JsonElement type) || + !string.Equals(type.GetString(), "text", StringComparison.Ordinal)) + continue; + if (!block.TryGetProperty("text", out JsonElement textEl)) + continue; + + if (texts.Length > 0) + texts.Append('|'); + texts.Append(pageIndex + 1).Append(':').Append(textEl.GetString() ?? ""); + } + + pageIndex++; + } + + return new Dictionary + { + ["pageCount"] = pages.GetArrayLength().ToString(CultureInfo.InvariantCulture), + ["pageWidth"] = width, + ["pageHeight"] = height, + ["texts"] = texts.ToString(), + }; + } + + static string FormatNumber(JsonElement el) + { + if (el.ValueKind == JsonValueKind.Number && el.TryGetDouble(out double d)) + return d.ToString("0.0", CultureInfo.InvariantCulture); + return el.ToString(); + } + } +} diff --git a/Common/OfficeLicense.cs b/Common/OfficeLicense.cs new file mode 100644 index 0000000..972963b --- /dev/null +++ b/Common/OfficeLicense.cs @@ -0,0 +1,16 @@ +using System; + +namespace MuPDF.NET.Examples.Common +{ + /// + /// Office license helpers. Prefer MUPDF_OFFICE_KEY; empty = restricted mode. + /// + public static class OfficeLicense + { + public static string? KeyFromEnvironment() + { + string? key = Environment.GetEnvironmentVariable("MUPDF_OFFICE_KEY"); + return string.IsNullOrWhiteSpace(key) ? null : key.Trim(); + } + } +} diff --git a/Common/OfficeTextShape.cs b/Common/OfficeTextShape.cs new file mode 100644 index 0000000..0053d21 --- /dev/null +++ b/Common/OfficeTextShape.cs @@ -0,0 +1,44 @@ +using System.Text; + +namespace MuPDF.NET.Examples.Common +{ + /// + /// Portable checks for CJK text that SmartOffice may fail to map to Unicode + /// on some hosts (e.g. Windows HWPX → Arial/Liberation without Hangul glyphs, + /// producing U+FFFD or NUL). Linux with Hangul-capable fonts extracts real + /// syllables. Comparing character shape keeps baselines cross-platform. + /// + public static class OfficeTextShape + { + public static bool ContainsHangul(string text) + { + if (string.IsNullOrEmpty(text)) + return false; + foreach (char c in text) + { + if (c >= '\uAC00' && c <= '\uD7A3') + return true; + } + return false; + } + + /// + /// Map Hangul syllables, U+FFFD, and NUL to H; keep spaces/punctuation. + /// + public static string FromText(string text) + { + if (string.IsNullOrEmpty(text)) + return ""; + + var sb = new StringBuilder(text.Length); + foreach (char c in text) + { + if ((c >= '\uAC00' && c <= '\uD7A3') || c == '\uFFFD' || c == '\0') + sb.Append('H'); + else + sb.Append(c); + } + return ResultCheck.NormalizeText(sb.ToString()); + } + } +} diff --git a/Common/PdfFingerprint.cs b/Common/PdfFingerprint.cs new file mode 100644 index 0000000..2ee941d --- /dev/null +++ b/Common/PdfFingerprint.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using MuPDF.NET; + +namespace MuPDF.NET.Examples.Common +{ + /// + /// Stable PDF fingerprints (page count + extracted text hash). + /// Avoids brittle byte compares when MuPDF rewrites IDs/dates on Save. + /// + public static class PdfFingerprint + { + public static Dictionary FromFile(string pdfPath) + { + using var doc = Document.Open(pdfPath); + return FromDocument(doc); + } + + public static Dictionary FromDocument(Document doc) + { + var text = new StringBuilder(); + for (int i = 0; i < doc.PageCount; i++) + { + using Page page = doc[i]; + text.Append(page.GetText("text") ?? ""); + text.Append('\n'); + } + + string normalized = ResultCheck.NormalizeText(text.ToString()); + byte[] bytes = Encoding.UTF8.GetBytes(normalized); + string hash = Convert.ToHexString(SHA256.HashData(bytes)).ToLowerInvariant(); + + return new Dictionary + { + ["pageCount"] = doc.PageCount.ToString(), + ["textSha256"] = hash, + }; + } + } +} diff --git a/Common/ResultCheck.cs b/Common/ResultCheck.cs new file mode 100644 index 0000000..b311239 --- /dev/null +++ b/Common/ResultCheck.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace MuPDF.NET.Examples.Common +{ + /// + /// Compare example outputs to golden files under {product}/{example}/Expected/. + /// Prints PASS / FAIL. Prefer run-all.ps1 for batch runs (Office + /// natives may AV during process teardown after a successful PASS). + /// + public sealed class ResultCheck + { + readonly string _product; + readonly string _example; + readonly List _failures = new(); + int _checks; + + public ResultCheck(string product, string exampleName) + { + _product = product; + _example = exampleName; + Directory.CreateDirectory(ExamplePaths.ExpectedDir(product, exampleName)); + } + + public string ExpectedPath(string fileName) => + ExamplePaths.ExpectedFile(_product, _example, fileName); + + /// UTF-8 text compare (normalizes line endings to LF). + public void Text(string actual, string expectedFileName) + { + _checks++; + string path = ExpectedPath(expectedFileName); + string normalized = NormalizeText(actual); + + if (ExampleArgs.UpdateExpected) + { + File.WriteAllText(path, normalized, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + ConsoleEx.Info($"Updated expected: {path}"); + return; + } + + if (!File.Exists(path)) + { + _failures.Add($"Missing expected file: {path} (run with --update-expected)"); + return; + } + + string expected = NormalizeText(File.ReadAllText(path)); + if (!string.Equals(normalized, expected, StringComparison.Ordinal)) + { + _failures.Add( + $"{expectedFileName}: text mismatch (actual {normalized.Length} chars, expected {expected.Length} chars)"); + } + } + + /// SHA-256 of a binary file vs {name}.sha256 contents. + public void FileSha256(string actualFilePath, string expectedShaFileName) + { + _checks++; + string sha = Sha256Hex(actualFilePath); + string path = ExpectedPath(expectedShaFileName); + + if (ExampleArgs.UpdateExpected) + { + File.WriteAllText(path, sha + "\n", new UTF8Encoding(false)); + ConsoleEx.Info($"Updated expected: {path}"); + return; + } + + if (!File.Exists(path)) + { + _failures.Add($"Missing expected file: {path} (run with --update-expected)"); + return; + } + + string expected = NormalizeText(File.ReadAllText(path)).Trim(); + if (!string.Equals(sha, expected, StringComparison.OrdinalIgnoreCase)) + { + _failures.Add($"{expectedShaFileName}: SHA-256 mismatch\n actual: {sha}\n expected: {expected}"); + } + } + + /// Key=value lines (order-insensitive). + public void Properties(IDictionary actual, string expectedFileName) + { + var sb = new StringBuilder(); + foreach (var kv in new SortedDictionary(actual, StringComparer.Ordinal)) + sb.Append(kv.Key).Append('=').Append(kv.Value).Append('\n'); + Text(sb.ToString(), expectedFileName); + } + + public void Equal(T actual, T expected, string label) + { + _checks++; + if (ExampleArgs.UpdateExpected) + return; + + if (!EqualityComparer.Default.Equals(actual, expected)) + _failures.Add($"{label}: expected {expected}, got {actual}"); + } + + /// Print summary. Sets to 0 or 1. + public void Finish() + { + if (ExampleArgs.UpdateExpected) + { + ConsoleEx.Info($"Baselines updated for {_product}/{_example} ({_checks} file(s))."); + ConsoleEx.Done(); + Environment.ExitCode = 0; + return; + } + + if (_failures.Count == 0) + { + ConsoleEx.Info($"PASS — {_checks} check(s) matched Expected/ for {_product}/{_example}"); + ConsoleEx.Done(); + Environment.ExitCode = 0; + return; + } + + Console.WriteLine(); + Console.WriteLine($"FAIL — {_failures.Count} check(s) failed for {_product}/{_example}:"); + foreach (string f in _failures) + Console.WriteLine(" - " + f); + Console.WriteLine("Refresh baselines after intentional changes: dotnet run --project ... -- --update-expected"); + Environment.ExitCode = 1; + } + + public static string NormalizeText(string text) + { + if (text == null) + return ""; + return text.Replace("\r\n", "\n").Replace('\r', '\n'); + } + + public static string Sha256Hex(string filePath) + { + using var stream = File.OpenRead(filePath); + byte[] hash = SHA256.HashData(stream); + var sb = new StringBuilder(hash.Length * 2); + foreach (byte b in hash) + sb.Append(b.ToString("x2")); + return sb.ToString(); + } + + public static string Sha256HexBytes(byte[] data) + { + byte[] hash = SHA256.HashData(data); + var sb = new StringBuilder(hash.Length * 2); + foreach (byte b in hash) + sb.Append(b.ToString("x2")); + return sb.ToString(); + } + } +} diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..f6a8231 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,11 @@ + + + + + net8.0 + enable + enable + latest + true + + diff --git a/Input/MuPDF.NET.Office/pages.docx b/Input/MuPDF.NET.Office/pages.docx new file mode 100644 index 0000000..0a460bc Binary files /dev/null and b/Input/MuPDF.NET.Office/pages.docx differ diff --git a/Input/MuPDF.NET.Office/sample.hwpx b/Input/MuPDF.NET.Office/sample.hwpx new file mode 100644 index 0000000..f1e0fb1 Binary files /dev/null and b/Input/MuPDF.NET.Office/sample.hwpx differ diff --git a/Input/MuPDF.NET.PDF4LLM/Ocr.pdf b/Input/MuPDF.NET.PDF4LLM/Ocr.pdf new file mode 100644 index 0000000..3f28b99 Binary files /dev/null and b/Input/MuPDF.NET.PDF4LLM/Ocr.pdf differ diff --git a/Input/MuPDF.NET.PDF4LLM/Widget.pdf b/Input/MuPDF.NET.PDF4LLM/Widget.pdf new file mode 100644 index 0000000..f903200 Binary files /dev/null and b/Input/MuPDF.NET.PDF4LLM/Widget.pdf differ diff --git a/Input/MuPDF.NET.PDF4LLM/columns.pdf b/Input/MuPDF.NET.PDF4LLM/columns.pdf new file mode 100644 index 0000000..18f5f15 Binary files /dev/null and b/Input/MuPDF.NET.PDF4LLM/columns.pdf differ diff --git a/Input/MuPDF.NET.PDF4LLM/national-capitals.pdf b/Input/MuPDF.NET.PDF4LLM/national-capitals.pdf new file mode 100644 index 0000000..d2b4721 Binary files /dev/null and b/Input/MuPDF.NET.PDF4LLM/national-capitals.pdf differ diff --git a/Input/MuPDF.NET.PDF4LLM/sample.md b/Input/MuPDF.NET.PDF4LLM/sample.md new file mode 100644 index 0000000..97cea0a --- /dev/null +++ b/Input/MuPDF.NET.PDF4LLM/sample.md @@ -0,0 +1,15 @@ +# MuPDF.NET.Examples + +Sample Markdown used by **08-MarkdownToPdf**. + +## Features + +- Headings +- Paragraphs +- A short list + +1. Open a PDF +2. Extract text +3. Convert Markdown back to PDF + +This file is intentionally small so the PDF fingerprint stays stable across runs. diff --git a/Input/MuPDF.NET/Annot.pdf b/Input/MuPDF.NET/Annot.pdf new file mode 100644 index 0000000..f3897e5 Binary files /dev/null and b/Input/MuPDF.NET/Annot.pdf differ diff --git a/Input/MuPDF.NET/Blank.pdf b/Input/MuPDF.NET/Blank.pdf new file mode 100644 index 0000000..6baef93 Binary files /dev/null and b/Input/MuPDF.NET/Blank.pdf differ diff --git a/Input/MuPDF.NET/Color.pdf b/Input/MuPDF.NET/Color.pdf new file mode 100644 index 0000000..6086ec9 Binary files /dev/null and b/Input/MuPDF.NET/Color.pdf differ diff --git a/Input/MuPDF.NET/NewAnnots.pdf b/Input/MuPDF.NET/NewAnnots.pdf new file mode 100644 index 0000000..5a055ff Binary files /dev/null and b/Input/MuPDF.NET/NewAnnots.pdf differ diff --git a/Input/MuPDF.NET/Widget.pdf b/Input/MuPDF.NET/Widget.pdf new file mode 100644 index 0000000..f903200 Binary files /dev/null and b/Input/MuPDF.NET/Widget.pdf differ diff --git a/Input/MuPDF.NET/apple.png b/Input/MuPDF.NET/apple.png new file mode 100644 index 0000000..6d81042 Binary files /dev/null and b/Input/MuPDF.NET/apple.png differ diff --git a/Input/MuPDF.NET/color/NULL.icc b/Input/MuPDF.NET/color/NULL.icc new file mode 100644 index 0000000..c27a4b3 Binary files /dev/null and b/Input/MuPDF.NET/color/NULL.icc differ diff --git a/Input/MuPDF.NET/color/Proof.icc b/Input/MuPDF.NET/color/Proof.icc new file mode 100644 index 0000000..d8f2833 Binary files /dev/null and b/Input/MuPDF.NET/color/Proof.icc differ diff --git a/Input/MuPDF.NET/color/test.pdf b/Input/MuPDF.NET/color/test.pdf new file mode 100644 index 0000000..5678919 Binary files /dev/null and b/Input/MuPDF.NET/color/test.pdf differ diff --git a/Input/MuPDF.NET/datamatrix.pdf b/Input/MuPDF.NET/datamatrix.pdf new file mode 100644 index 0000000..9ddfadc Binary files /dev/null and b/Input/MuPDF.NET/datamatrix.pdf differ diff --git a/Input/MuPDF.NET/err_table.pdf b/Input/MuPDF.NET/err_table.pdf new file mode 100644 index 0000000..f7f19b3 Binary files /dev/null and b/Input/MuPDF.NET/err_table.pdf differ diff --git a/Input/MuPDF.NET/logo.png b/Input/MuPDF.NET/logo.png new file mode 100644 index 0000000..03784e0 Binary files /dev/null and b/Input/MuPDF.NET/logo.png differ diff --git a/Input/MuPDF.NET/note.txt b/Input/MuPDF.NET/note.txt new file mode 100644 index 0000000..0eb209a --- /dev/null +++ b/Input/MuPDF.NET/note.txt @@ -0,0 +1 @@ +MuPDF.NET.Examples embedded file payload. diff --git a/Input/MuPDF.NET/sample.pdf b/Input/MuPDF.NET/sample.pdf new file mode 100644 index 0000000..ae0f872 Binary files /dev/null and b/Input/MuPDF.NET/sample.pdf differ diff --git a/Input/MuPDF.NET/test-rewrite-images.pdf b/Input/MuPDF.NET/test-rewrite-images.pdf new file mode 100644 index 0000000..d1d7423 Binary files /dev/null and b/Input/MuPDF.NET/test-rewrite-images.pdf differ diff --git a/Input/MuPDF.NET/zugferd-muster-rechnung.pdf b/Input/MuPDF.NET/zugferd-muster-rechnung.pdf new file mode 100644 index 0000000..66ff690 Binary files /dev/null and b/Input/MuPDF.NET/zugferd-muster-rechnung.pdf differ diff --git a/Input/MuPDF.NET/zugferd-muster-rechnung.xml b/Input/MuPDF.NET/zugferd-muster-rechnung.xml new file mode 100644 index 0000000..9858583 --- /dev/null +++ b/Input/MuPDF.NET/zugferd-muster-rechnung.xml @@ -0,0 +1,150 @@ + + + + + urn:cen.eu:en16931:2017 + + + + MUSTER-2026-0042 + 380 + + 20260215 + + + Beispielrechnung (ZUGFeRD 2.x, Profil EN 16931). Alle Parteien und Betraege sind frei erfunden und dienen nur zu Demonstrationszwecken. + + + + + + 1 + + + Beratungsleistung Rechnungsdigitalisierung + + + + 90.00 + + + 90.00 + + + + 4.00 + + + + VAT + S + 19.00 + + + 360.00 + + + + + + 2 + + + Projektlizenz Software (Jahreslizenz) + + + + 240.00 + + + 240.00 + + + + 1.00 + + + + VAT + S + 19.00 + + + 240.00 + + + + + + Muster GmbH + + Sabine Muster + + +49 30 1234567 + + + rechnung@muster-gmbh.example + + + + 10115 + Musterstrasse 12 + Berlin + DE + + + DE123456789 + + + + Beispiel Handel AG + + 80331 + Beispielweg 5 + Muenchen + DE + + + DE987654321 + + + + + + + 20260215 + + + + + EUR + + 58 + + DE21500500009876543210 + + + + 114.00 + VAT + 600.00 + S + 19.00 + + + Zahlbar bis 01.03.2026 ohne Abzug. + + 20260301 + + + + 600.00 + 600.00 + 114.00 + 714.00 + 0.00 + 714.00 + + + + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..99e9525 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,58 @@ +# ARTIFEX COMMUNITY LICENSE + +**Version 1, 11th December 2024** + +This **Artifex Community License** ("License") governs the use, reproduction, and distribution of **MuPDF.NET.Examples** ("Software") for non-commercial use. By using, modifying, or distributing this Software, you accept the terms of this License. + +You may not use the software without an appropriate license, so if you cannot abide by all the terms of this license, you must [contact the Licensor](https://artifex.com/contact) to discuss obtaining a **Commercial Use License**. + +## 1. Definitions + +- **Commercial Use**: Any use of the Software for commercial purposes, including, but not limited to, use as part of, or in any workflow in support of, a product, service, or platform that directly or indirectly generates revenue. +- **Non-Commercial Use**: Any use of the software that does not fall into the **Commercial Use** category, such as for personal, or educational purposes. +- **Licensor**: Artifex Software, Inc. +- **You**: Any individual or entity using the Software. + +## 2. Grant of License + +1. **Non-Commercial Use**: + - You are granted a royalty-free, perpetual, non-exclusive license to use, modify, and distribute the Software for Non-Commercial Use, subject to the terms of this License. +2. **Commercial Use**: + - This license does not cover Commercial Use. + - If you intend to use the Software for Commercial Use you are required to obtain a Commercial Use License - [contact the Licensor](https://artifex.com/contact) to negotiate a Commercial Use License. + +## 3. Permitted Uses + +1. You may: + - Access and modify the Software’s source code. + - Use the Software for any lawful purpose, subject to the limitations outlined in Section 2. + - Combine the Software with other works, provided that any distribution complies with this License. + - Distribute unmodified source code. + +## 4. Restrictions + +1. You may not: + - Use the Software for any Commercial Use. If Commercial Use is required [contact the Licensor](https://artifex.com/contact) to negotiate a Commercial Use License. + - Remove or obscure any copyright, trademark, or attribution notices included in the Software. + - Distribute modified versions of the source code. + +2. Derivative works must include prominent attribution to the original Software and a copy of this License. + +## 5. Attribution + +All distributions of the Software, whether modified or unmodified, must include: +- A prominent notice stating: *"This software is based on MuPDF.NET.Examples, developed by Artifex Software, Inc. and licensed under the Artifex Community License."* +- A copy of this License. + +## 6. Disclaimer of Warranty + +THE SOFTWARE IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +## 7. Limitation of Liability + +IN NO EVENT SHALL THE LICENSOR BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE SOFTWARE, EVEN IF ADVISED OF SUCH DAMAGES. + +## 8. Termination + +1. This License is effective until terminated. +2. Your rights under this License will terminate automatically if you breach any of its terms. Upon termination, you must cease all use of the Software and destroy all copies in your possession. diff --git a/LocalNuget/.gitignore b/LocalNuget/.gitignore new file mode 100644 index 0000000..e71cc3c --- /dev/null +++ b/LocalNuget/.gitignore @@ -0,0 +1,4 @@ +# Drop or symlink locally packed Artifex .nupkg files here (optional). +# NuGet.Config uses this folder as LocalArtifex ahead of nuget.org. +* +!.gitignore diff --git a/MuPDF.NET.Examples.sln b/MuPDF.NET.Examples.sln new file mode 100644 index 0000000..973d163 --- /dev/null +++ b/MuPDF.NET.Examples.sln @@ -0,0 +1,690 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{881589EE-639B-0A35-DBCE-5D94E4FC90C0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples.Common", "Common\Examples.Common.csproj", "{E62A732B-740E-479A-918C-17BE95374A7A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MuPDF.NET", "MuPDF.NET", "{C9700E2F-F6F1-A264-19EE-D9EB5C62093D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "01-OpenSave", "MuPDF.NET\01-OpenSave\01-OpenSave.csproj", "{9E9073C7-139D-4AFC-808D-C2E39AFD04E4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "03-RenderPixmap", "MuPDF.NET\03-RenderPixmap\03-RenderPixmap.csproj", "{45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "04-TextExtractSearch", "MuPDF.NET\04-TextExtractSearch\04-TextExtractSearch.csproj", "{9D259784-603E-4F96-A2E4-360E291D1D6C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MuPDF.NET.PDF4LLM", "MuPDF.NET.PDF4LLM", "{EEFA252F-3CE0-C131-3F8C-AF1C5E35DF91}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "01-ToMarkdown", "MuPDF.NET.PDF4LLM\01-ToMarkdown\01-ToMarkdown.csproj", "{A37158B5-8404-4E04-B63F-EA26B237FD42}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MuPDF.NET.Office", "MuPDF.NET.Office", "{188FE78A-9273-6FA7-91AA-4C340983034C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "01-UnlockFonts", "MuPDF.NET.Office\01-UnlockFonts\01-UnlockFonts.csproj", "{37DF7393-B6F0-45CA-B436-ED84B6D6B107}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "03-ExportToPdf", "MuPDF.NET.Office\03-ExportToPdf\03-ExportToPdf.csproj", "{0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "05-WithPdf4Llm", "MuPDF.NET.Office\05-WithPdf4Llm\05-WithPdf4Llm.csproj", "{6A17E833-F897-4075-A9E6-CFED5AED1858}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "02-PagesMergeSplit", "MuPDF.NET\02-PagesMergeSplit\02-PagesMergeSplit.csproj", "{FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "07-AnnotationsRedact", "MuPDF.NET\07-AnnotationsRedact\07-AnnotationsRedact.csproj", "{6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "02-ToJsonLayout", "MuPDF.NET.PDF4LLM\02-ToJsonLayout\02-ToJsonLayout.csproj", "{E86B329B-216B-4261-A730-2F310059ADCC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "04-Ocr", "MuPDF.NET.PDF4LLM\04-Ocr\04-Ocr.csproj", "{AE3BB780-FD58-476F-B1BE-3CA147773E28}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "02-OpenHwpxDocx", "MuPDF.NET.Office\02-OpenHwpxDocx\02-OpenHwpxDocx.csproj", "{98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "04-ExportToMarkdownJson", "MuPDF.NET.Office\04-ExportToMarkdownJson\04-ExportToMarkdownJson.csproj", "{4C49A93B-86F9-4064-AE84-E205A3290640}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "08-FormWidgets", "MuPDF.NET\08-FormWidgets\08-FormWidgets.csproj", "{F36EF875-752F-446E-9A03-9711B262F220}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "09-InsertImage", "MuPDF.NET\09-InsertImage\09-InsertImage.csproj", "{A06BB5B9-E130-4870-9A6E-B222EA900A30}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "11-Tables", "MuPDF.NET\11-Tables\11-Tables.csproj", "{BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "12-Barcodes", "MuPDF.NET\12-Barcodes\12-Barcodes.csproj", "{D34E826E-F2C3-4D92-AC89-A36EB1F5F801}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "03-ToText", "MuPDF.NET.PDF4LLM\03-ToText\03-ToText.csproj", "{2FF073D1-2089-4BA2-9CA9-918BE6AC6970}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "05-TablesCsv", "MuPDF.NET.PDF4LLM\05-TablesCsv\05-TablesCsv.csproj", "{53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "05-Recolor", "MuPDF.NET\05-Recolor\05-Recolor.csproj", "{2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "06-StoryHtmlBox", "MuPDF.NET\06-StoryHtmlBox\06-StoryHtmlBox.csproj", "{063E4EC8-05EE-4134-AB10-2320B88EB0A7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "10-OutlineLinks", "MuPDF.NET\10-OutlineLinks\10-OutlineLinks.csproj", "{1D2E550F-BCF0-4FC2-B74B-A50988D04229}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "13-EmbeddedFiles", "MuPDF.NET\13-EmbeddedFiles\13-EmbeddedFiles.csproj", "{319870ED-F42D-4089-BE2B-C6138D67E531}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "06-LlamaMarkdownReader", "MuPDF.NET.PDF4LLM\06-LlamaMarkdownReader\06-LlamaMarkdownReader.csproj", "{CF033B84-95AC-4250-BF8A-4D615429F3EB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "14-Metadata", "MuPDF.NET\14-Metadata\14-Metadata.csproj", "{CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "15-TextWriter", "MuPDF.NET\15-TextWriter\15-TextWriter.csproj", "{495D6005-C265-4D3B-8E90-DFDE60D2EDAA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "16-DrawShapes", "MuPDF.NET\16-DrawShapes\16-DrawShapes.csproj", "{57D00261-2BA7-467B-A890-5F78E32122DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "17-ReplaceImage", "MuPDF.NET\17-ReplaceImage\17-ReplaceImage.csproj", "{9BBCA112-32B2-4515-9752-0146F251A377}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "18-ZugferdEmbedded", "MuPDF.NET\18-ZugferdEmbedded\18-ZugferdEmbedded.csproj", "{BAC4EBFB-4F0F-4770-9408-3748EF27AC17}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "07-GetKeyValues", "MuPDF.NET.PDF4LLM\07-GetKeyValues\07-GetKeyValues.csproj", "{70BFCFDD-7DB4-4875-9933-21229B4D8D8D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "19-ColorManagement", "MuPDF.NET\19-ColorManagement\19-ColorManagement.csproj", "{83B6F2DC-0358-4720-9CBD-36027D988228}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "08-MarkdownToPdf", "MuPDF.NET.PDF4LLM\08-MarkdownToPdf\08-MarkdownToPdf.csproj", "{86ED3702-826C-4D74-99F4-830C8F66923A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "20-RewriteImages", "MuPDF.NET\20-RewriteImages\20-RewriteImages.csproj", "{DF2CCA78-AEA9-4425-9160-A4487C8AB70E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "21-ExtractImages", "MuPDF.NET\21-ExtractImages\21-ExtractImages.csproj", "{1DBE7E05-5408-4B05-8275-5290FF643978}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "22-GetDrawings", "MuPDF.NET\22-GetDrawings\22-GetDrawings.csproj", "{A0EC6A50-92AF-4422-9A4B-10020E479FAF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "23-RotateCrop", "MuPDF.NET\23-RotateCrop\23-RotateCrop.csproj", "{03C44C89-7BBE-4D90-951D-CE413EAC767B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "24-EncryptDecrypt", "MuPDF.NET\24-EncryptDecrypt\24-EncryptDecrypt.csproj", "{4FB4B780-6D71-4AB7-A531-CF808F2549CB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "25-CompressSave", "MuPDF.NET\25-CompressSave\25-CompressSave.csproj", "{B72B80C9-FFBE-401F-8393-7558731705F2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "26-Watermark", "MuPDF.NET\26-Watermark\26-Watermark.csproj", "{8832643C-A4D4-4F7A-8051-E36D04372932}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "27-ImageToPdf", "MuPDF.NET\27-ImageToPdf\27-ImageToPdf.csproj", "{C4E98E11-EFAC-44B9-8942-591FBB09922B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "28-ImageFilters", "MuPDF.NET\28-ImageFilters\28-ImageFilters.csproj", "{FDE5B67A-607D-4789-A667-BC79D7F0762B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "29-PageOps", "MuPDF.NET\29-PageOps\29-PageOps.csproj", "{45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "30-FileAnnot", "MuPDF.NET\30-FileAnnot\30-FileAnnot.csproj", "{0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E62A732B-740E-479A-918C-17BE95374A7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E62A732B-740E-479A-918C-17BE95374A7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E62A732B-740E-479A-918C-17BE95374A7A}.Debug|x64.ActiveCfg = Debug|Any CPU + {E62A732B-740E-479A-918C-17BE95374A7A}.Debug|x64.Build.0 = Debug|Any CPU + {E62A732B-740E-479A-918C-17BE95374A7A}.Debug|x86.ActiveCfg = Debug|Any CPU + {E62A732B-740E-479A-918C-17BE95374A7A}.Debug|x86.Build.0 = Debug|Any CPU + {E62A732B-740E-479A-918C-17BE95374A7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E62A732B-740E-479A-918C-17BE95374A7A}.Release|Any CPU.Build.0 = Release|Any CPU + {E62A732B-740E-479A-918C-17BE95374A7A}.Release|x64.ActiveCfg = Release|Any CPU + {E62A732B-740E-479A-918C-17BE95374A7A}.Release|x64.Build.0 = Release|Any CPU + {E62A732B-740E-479A-918C-17BE95374A7A}.Release|x86.ActiveCfg = Release|Any CPU + {E62A732B-740E-479A-918C-17BE95374A7A}.Release|x86.Build.0 = Release|Any CPU + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4}.Debug|x64.ActiveCfg = Debug|Any CPU + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4}.Debug|x64.Build.0 = Debug|Any CPU + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4}.Debug|x86.ActiveCfg = Debug|Any CPU + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4}.Debug|x86.Build.0 = Debug|Any CPU + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4}.Release|Any CPU.Build.0 = Release|Any CPU + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4}.Release|x64.ActiveCfg = Release|Any CPU + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4}.Release|x64.Build.0 = Release|Any CPU + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4}.Release|x86.ActiveCfg = Release|Any CPU + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4}.Release|x86.Build.0 = Release|Any CPU + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}.Debug|x64.ActiveCfg = Debug|Any CPU + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}.Debug|x64.Build.0 = Debug|Any CPU + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}.Debug|x86.ActiveCfg = Debug|Any CPU + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}.Debug|x86.Build.0 = Debug|Any CPU + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}.Release|Any CPU.Build.0 = Release|Any CPU + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}.Release|x64.ActiveCfg = Release|Any CPU + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}.Release|x64.Build.0 = Release|Any CPU + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}.Release|x86.ActiveCfg = Release|Any CPU + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF}.Release|x86.Build.0 = Release|Any CPU + {9D259784-603E-4F96-A2E4-360E291D1D6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D259784-603E-4F96-A2E4-360E291D1D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D259784-603E-4F96-A2E4-360E291D1D6C}.Debug|x64.ActiveCfg = Debug|Any CPU + {9D259784-603E-4F96-A2E4-360E291D1D6C}.Debug|x64.Build.0 = Debug|Any CPU + {9D259784-603E-4F96-A2E4-360E291D1D6C}.Debug|x86.ActiveCfg = Debug|Any CPU + {9D259784-603E-4F96-A2E4-360E291D1D6C}.Debug|x86.Build.0 = Debug|Any CPU + {9D259784-603E-4F96-A2E4-360E291D1D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D259784-603E-4F96-A2E4-360E291D1D6C}.Release|Any CPU.Build.0 = Release|Any CPU + {9D259784-603E-4F96-A2E4-360E291D1D6C}.Release|x64.ActiveCfg = Release|Any CPU + {9D259784-603E-4F96-A2E4-360E291D1D6C}.Release|x64.Build.0 = Release|Any CPU + {9D259784-603E-4F96-A2E4-360E291D1D6C}.Release|x86.ActiveCfg = Release|Any CPU + {9D259784-603E-4F96-A2E4-360E291D1D6C}.Release|x86.Build.0 = Release|Any CPU + {A37158B5-8404-4E04-B63F-EA26B237FD42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A37158B5-8404-4E04-B63F-EA26B237FD42}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A37158B5-8404-4E04-B63F-EA26B237FD42}.Debug|x64.ActiveCfg = Debug|Any CPU + {A37158B5-8404-4E04-B63F-EA26B237FD42}.Debug|x64.Build.0 = Debug|Any CPU + {A37158B5-8404-4E04-B63F-EA26B237FD42}.Debug|x86.ActiveCfg = Debug|Any CPU + {A37158B5-8404-4E04-B63F-EA26B237FD42}.Debug|x86.Build.0 = Debug|Any CPU + {A37158B5-8404-4E04-B63F-EA26B237FD42}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A37158B5-8404-4E04-B63F-EA26B237FD42}.Release|Any CPU.Build.0 = Release|Any CPU + {A37158B5-8404-4E04-B63F-EA26B237FD42}.Release|x64.ActiveCfg = Release|Any CPU + {A37158B5-8404-4E04-B63F-EA26B237FD42}.Release|x64.Build.0 = Release|Any CPU + {A37158B5-8404-4E04-B63F-EA26B237FD42}.Release|x86.ActiveCfg = Release|Any CPU + {A37158B5-8404-4E04-B63F-EA26B237FD42}.Release|x86.Build.0 = Release|Any CPU + {37DF7393-B6F0-45CA-B436-ED84B6D6B107}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37DF7393-B6F0-45CA-B436-ED84B6D6B107}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37DF7393-B6F0-45CA-B436-ED84B6D6B107}.Debug|x64.ActiveCfg = Debug|Any CPU + {37DF7393-B6F0-45CA-B436-ED84B6D6B107}.Debug|x64.Build.0 = Debug|Any CPU + {37DF7393-B6F0-45CA-B436-ED84B6D6B107}.Debug|x86.ActiveCfg = Debug|Any CPU + {37DF7393-B6F0-45CA-B436-ED84B6D6B107}.Debug|x86.Build.0 = Debug|Any CPU + {37DF7393-B6F0-45CA-B436-ED84B6D6B107}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37DF7393-B6F0-45CA-B436-ED84B6D6B107}.Release|Any CPU.Build.0 = Release|Any CPU + {37DF7393-B6F0-45CA-B436-ED84B6D6B107}.Release|x64.ActiveCfg = Release|Any CPU + {37DF7393-B6F0-45CA-B436-ED84B6D6B107}.Release|x64.Build.0 = Release|Any CPU + {37DF7393-B6F0-45CA-B436-ED84B6D6B107}.Release|x86.ActiveCfg = Release|Any CPU + {37DF7393-B6F0-45CA-B436-ED84B6D6B107}.Release|x86.Build.0 = Release|Any CPU + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}.Debug|x64.ActiveCfg = Debug|Any CPU + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}.Debug|x64.Build.0 = Debug|Any CPU + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}.Debug|x86.ActiveCfg = Debug|Any CPU + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}.Debug|x86.Build.0 = Debug|Any CPU + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}.Release|Any CPU.Build.0 = Release|Any CPU + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}.Release|x64.ActiveCfg = Release|Any CPU + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}.Release|x64.Build.0 = Release|Any CPU + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}.Release|x86.ActiveCfg = Release|Any CPU + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF}.Release|x86.Build.0 = Release|Any CPU + {6A17E833-F897-4075-A9E6-CFED5AED1858}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A17E833-F897-4075-A9E6-CFED5AED1858}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A17E833-F897-4075-A9E6-CFED5AED1858}.Debug|x64.ActiveCfg = Debug|Any CPU + {6A17E833-F897-4075-A9E6-CFED5AED1858}.Debug|x64.Build.0 = Debug|Any CPU + {6A17E833-F897-4075-A9E6-CFED5AED1858}.Debug|x86.ActiveCfg = Debug|Any CPU + {6A17E833-F897-4075-A9E6-CFED5AED1858}.Debug|x86.Build.0 = Debug|Any CPU + {6A17E833-F897-4075-A9E6-CFED5AED1858}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A17E833-F897-4075-A9E6-CFED5AED1858}.Release|Any CPU.Build.0 = Release|Any CPU + {6A17E833-F897-4075-A9E6-CFED5AED1858}.Release|x64.ActiveCfg = Release|Any CPU + {6A17E833-F897-4075-A9E6-CFED5AED1858}.Release|x64.Build.0 = Release|Any CPU + {6A17E833-F897-4075-A9E6-CFED5AED1858}.Release|x86.ActiveCfg = Release|Any CPU + {6A17E833-F897-4075-A9E6-CFED5AED1858}.Release|x86.Build.0 = Release|Any CPU + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}.Debug|x64.ActiveCfg = Debug|Any CPU + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}.Debug|x64.Build.0 = Debug|Any CPU + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}.Debug|x86.ActiveCfg = Debug|Any CPU + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}.Debug|x86.Build.0 = Debug|Any CPU + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}.Release|Any CPU.Build.0 = Release|Any CPU + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}.Release|x64.ActiveCfg = Release|Any CPU + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}.Release|x64.Build.0 = Release|Any CPU + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}.Release|x86.ActiveCfg = Release|Any CPU + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5}.Release|x86.Build.0 = Release|Any CPU + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}.Debug|x64.ActiveCfg = Debug|Any CPU + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}.Debug|x64.Build.0 = Debug|Any CPU + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}.Debug|x86.ActiveCfg = Debug|Any CPU + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}.Debug|x86.Build.0 = Debug|Any CPU + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}.Release|Any CPU.Build.0 = Release|Any CPU + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}.Release|x64.ActiveCfg = Release|Any CPU + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}.Release|x64.Build.0 = Release|Any CPU + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}.Release|x86.ActiveCfg = Release|Any CPU + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10}.Release|x86.Build.0 = Release|Any CPU + {E86B329B-216B-4261-A730-2F310059ADCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E86B329B-216B-4261-A730-2F310059ADCC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E86B329B-216B-4261-A730-2F310059ADCC}.Debug|x64.ActiveCfg = Debug|Any CPU + {E86B329B-216B-4261-A730-2F310059ADCC}.Debug|x64.Build.0 = Debug|Any CPU + {E86B329B-216B-4261-A730-2F310059ADCC}.Debug|x86.ActiveCfg = Debug|Any CPU + {E86B329B-216B-4261-A730-2F310059ADCC}.Debug|x86.Build.0 = Debug|Any CPU + {E86B329B-216B-4261-A730-2F310059ADCC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E86B329B-216B-4261-A730-2F310059ADCC}.Release|Any CPU.Build.0 = Release|Any CPU + {E86B329B-216B-4261-A730-2F310059ADCC}.Release|x64.ActiveCfg = Release|Any CPU + {E86B329B-216B-4261-A730-2F310059ADCC}.Release|x64.Build.0 = Release|Any CPU + {E86B329B-216B-4261-A730-2F310059ADCC}.Release|x86.ActiveCfg = Release|Any CPU + {E86B329B-216B-4261-A730-2F310059ADCC}.Release|x86.Build.0 = Release|Any CPU + {AE3BB780-FD58-476F-B1BE-3CA147773E28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE3BB780-FD58-476F-B1BE-3CA147773E28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE3BB780-FD58-476F-B1BE-3CA147773E28}.Debug|x64.ActiveCfg = Debug|Any CPU + {AE3BB780-FD58-476F-B1BE-3CA147773E28}.Debug|x64.Build.0 = Debug|Any CPU + {AE3BB780-FD58-476F-B1BE-3CA147773E28}.Debug|x86.ActiveCfg = Debug|Any CPU + {AE3BB780-FD58-476F-B1BE-3CA147773E28}.Debug|x86.Build.0 = Debug|Any CPU + {AE3BB780-FD58-476F-B1BE-3CA147773E28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE3BB780-FD58-476F-B1BE-3CA147773E28}.Release|Any CPU.Build.0 = Release|Any CPU + {AE3BB780-FD58-476F-B1BE-3CA147773E28}.Release|x64.ActiveCfg = Release|Any CPU + {AE3BB780-FD58-476F-B1BE-3CA147773E28}.Release|x64.Build.0 = Release|Any CPU + {AE3BB780-FD58-476F-B1BE-3CA147773E28}.Release|x86.ActiveCfg = Release|Any CPU + {AE3BB780-FD58-476F-B1BE-3CA147773E28}.Release|x86.Build.0 = Release|Any CPU + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}.Debug|x64.ActiveCfg = Debug|Any CPU + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}.Debug|x64.Build.0 = Debug|Any CPU + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}.Debug|x86.ActiveCfg = Debug|Any CPU + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}.Debug|x86.Build.0 = Debug|Any CPU + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}.Release|Any CPU.Build.0 = Release|Any CPU + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}.Release|x64.ActiveCfg = Release|Any CPU + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}.Release|x64.Build.0 = Release|Any CPU + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}.Release|x86.ActiveCfg = Release|Any CPU + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24}.Release|x86.Build.0 = Release|Any CPU + {4C49A93B-86F9-4064-AE84-E205A3290640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C49A93B-86F9-4064-AE84-E205A3290640}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C49A93B-86F9-4064-AE84-E205A3290640}.Debug|x64.ActiveCfg = Debug|Any CPU + {4C49A93B-86F9-4064-AE84-E205A3290640}.Debug|x64.Build.0 = Debug|Any CPU + {4C49A93B-86F9-4064-AE84-E205A3290640}.Debug|x86.ActiveCfg = Debug|Any CPU + {4C49A93B-86F9-4064-AE84-E205A3290640}.Debug|x86.Build.0 = Debug|Any CPU + {4C49A93B-86F9-4064-AE84-E205A3290640}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C49A93B-86F9-4064-AE84-E205A3290640}.Release|Any CPU.Build.0 = Release|Any CPU + {4C49A93B-86F9-4064-AE84-E205A3290640}.Release|x64.ActiveCfg = Release|Any CPU + {4C49A93B-86F9-4064-AE84-E205A3290640}.Release|x64.Build.0 = Release|Any CPU + {4C49A93B-86F9-4064-AE84-E205A3290640}.Release|x86.ActiveCfg = Release|Any CPU + {4C49A93B-86F9-4064-AE84-E205A3290640}.Release|x86.Build.0 = Release|Any CPU + {F36EF875-752F-446E-9A03-9711B262F220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F36EF875-752F-446E-9A03-9711B262F220}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F36EF875-752F-446E-9A03-9711B262F220}.Debug|x64.ActiveCfg = Debug|Any CPU + {F36EF875-752F-446E-9A03-9711B262F220}.Debug|x64.Build.0 = Debug|Any CPU + {F36EF875-752F-446E-9A03-9711B262F220}.Debug|x86.ActiveCfg = Debug|Any CPU + {F36EF875-752F-446E-9A03-9711B262F220}.Debug|x86.Build.0 = Debug|Any CPU + {F36EF875-752F-446E-9A03-9711B262F220}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F36EF875-752F-446E-9A03-9711B262F220}.Release|Any CPU.Build.0 = Release|Any CPU + {F36EF875-752F-446E-9A03-9711B262F220}.Release|x64.ActiveCfg = Release|Any CPU + {F36EF875-752F-446E-9A03-9711B262F220}.Release|x64.Build.0 = Release|Any CPU + {F36EF875-752F-446E-9A03-9711B262F220}.Release|x86.ActiveCfg = Release|Any CPU + {F36EF875-752F-446E-9A03-9711B262F220}.Release|x86.Build.0 = Release|Any CPU + {A06BB5B9-E130-4870-9A6E-B222EA900A30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A06BB5B9-E130-4870-9A6E-B222EA900A30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A06BB5B9-E130-4870-9A6E-B222EA900A30}.Debug|x64.ActiveCfg = Debug|Any CPU + {A06BB5B9-E130-4870-9A6E-B222EA900A30}.Debug|x64.Build.0 = Debug|Any CPU + {A06BB5B9-E130-4870-9A6E-B222EA900A30}.Debug|x86.ActiveCfg = Debug|Any CPU + {A06BB5B9-E130-4870-9A6E-B222EA900A30}.Debug|x86.Build.0 = Debug|Any CPU + {A06BB5B9-E130-4870-9A6E-B222EA900A30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A06BB5B9-E130-4870-9A6E-B222EA900A30}.Release|Any CPU.Build.0 = Release|Any CPU + {A06BB5B9-E130-4870-9A6E-B222EA900A30}.Release|x64.ActiveCfg = Release|Any CPU + {A06BB5B9-E130-4870-9A6E-B222EA900A30}.Release|x64.Build.0 = Release|Any CPU + {A06BB5B9-E130-4870-9A6E-B222EA900A30}.Release|x86.ActiveCfg = Release|Any CPU + {A06BB5B9-E130-4870-9A6E-B222EA900A30}.Release|x86.Build.0 = Release|Any CPU + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}.Debug|x64.ActiveCfg = Debug|Any CPU + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}.Debug|x64.Build.0 = Debug|Any CPU + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}.Debug|x86.ActiveCfg = Debug|Any CPU + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}.Debug|x86.Build.0 = Debug|Any CPU + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}.Release|Any CPU.Build.0 = Release|Any CPU + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}.Release|x64.ActiveCfg = Release|Any CPU + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}.Release|x64.Build.0 = Release|Any CPU + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}.Release|x86.ActiveCfg = Release|Any CPU + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A}.Release|x86.Build.0 = Release|Any CPU + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801}.Debug|x64.ActiveCfg = Debug|Any CPU + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801}.Debug|x64.Build.0 = Debug|Any CPU + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801}.Debug|x86.ActiveCfg = Debug|Any CPU + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801}.Debug|x86.Build.0 = Debug|Any CPU + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801}.Release|Any CPU.Build.0 = Release|Any CPU + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801}.Release|x64.ActiveCfg = Release|Any CPU + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801}.Release|x64.Build.0 = Release|Any CPU + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801}.Release|x86.ActiveCfg = Release|Any CPU + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801}.Release|x86.Build.0 = Release|Any CPU + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970}.Debug|x64.ActiveCfg = Debug|Any CPU + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970}.Debug|x64.Build.0 = Debug|Any CPU + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970}.Debug|x86.ActiveCfg = Debug|Any CPU + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970}.Debug|x86.Build.0 = Debug|Any CPU + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970}.Release|Any CPU.Build.0 = Release|Any CPU + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970}.Release|x64.ActiveCfg = Release|Any CPU + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970}.Release|x64.Build.0 = Release|Any CPU + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970}.Release|x86.ActiveCfg = Release|Any CPU + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970}.Release|x86.Build.0 = Release|Any CPU + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}.Debug|x64.ActiveCfg = Debug|Any CPU + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}.Debug|x64.Build.0 = Debug|Any CPU + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}.Debug|x86.ActiveCfg = Debug|Any CPU + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}.Debug|x86.Build.0 = Debug|Any CPU + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}.Release|Any CPU.Build.0 = Release|Any CPU + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}.Release|x64.ActiveCfg = Release|Any CPU + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}.Release|x64.Build.0 = Release|Any CPU + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}.Release|x86.ActiveCfg = Release|Any CPU + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1}.Release|x86.Build.0 = Release|Any CPU + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}.Debug|x64.ActiveCfg = Debug|Any CPU + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}.Debug|x64.Build.0 = Debug|Any CPU + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}.Debug|x86.ActiveCfg = Debug|Any CPU + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}.Debug|x86.Build.0 = Debug|Any CPU + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}.Release|Any CPU.Build.0 = Release|Any CPU + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}.Release|x64.ActiveCfg = Release|Any CPU + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}.Release|x64.Build.0 = Release|Any CPU + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}.Release|x86.ActiveCfg = Release|Any CPU + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A}.Release|x86.Build.0 = Release|Any CPU + {063E4EC8-05EE-4134-AB10-2320B88EB0A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {063E4EC8-05EE-4134-AB10-2320B88EB0A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {063E4EC8-05EE-4134-AB10-2320B88EB0A7}.Debug|x64.ActiveCfg = Debug|Any CPU + {063E4EC8-05EE-4134-AB10-2320B88EB0A7}.Debug|x64.Build.0 = Debug|Any CPU + {063E4EC8-05EE-4134-AB10-2320B88EB0A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {063E4EC8-05EE-4134-AB10-2320B88EB0A7}.Debug|x86.Build.0 = Debug|Any CPU + {063E4EC8-05EE-4134-AB10-2320B88EB0A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {063E4EC8-05EE-4134-AB10-2320B88EB0A7}.Release|Any CPU.Build.0 = Release|Any CPU + {063E4EC8-05EE-4134-AB10-2320B88EB0A7}.Release|x64.ActiveCfg = Release|Any CPU + {063E4EC8-05EE-4134-AB10-2320B88EB0A7}.Release|x64.Build.0 = Release|Any CPU + {063E4EC8-05EE-4134-AB10-2320B88EB0A7}.Release|x86.ActiveCfg = Release|Any CPU + {063E4EC8-05EE-4134-AB10-2320B88EB0A7}.Release|x86.Build.0 = Release|Any CPU + {1D2E550F-BCF0-4FC2-B74B-A50988D04229}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D2E550F-BCF0-4FC2-B74B-A50988D04229}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D2E550F-BCF0-4FC2-B74B-A50988D04229}.Debug|x64.ActiveCfg = Debug|Any CPU + {1D2E550F-BCF0-4FC2-B74B-A50988D04229}.Debug|x64.Build.0 = Debug|Any CPU + {1D2E550F-BCF0-4FC2-B74B-A50988D04229}.Debug|x86.ActiveCfg = Debug|Any CPU + {1D2E550F-BCF0-4FC2-B74B-A50988D04229}.Debug|x86.Build.0 = Debug|Any CPU + {1D2E550F-BCF0-4FC2-B74B-A50988D04229}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D2E550F-BCF0-4FC2-B74B-A50988D04229}.Release|Any CPU.Build.0 = Release|Any CPU + {1D2E550F-BCF0-4FC2-B74B-A50988D04229}.Release|x64.ActiveCfg = Release|Any CPU + {1D2E550F-BCF0-4FC2-B74B-A50988D04229}.Release|x64.Build.0 = Release|Any CPU + {1D2E550F-BCF0-4FC2-B74B-A50988D04229}.Release|x86.ActiveCfg = Release|Any CPU + {1D2E550F-BCF0-4FC2-B74B-A50988D04229}.Release|x86.Build.0 = Release|Any CPU + {319870ED-F42D-4089-BE2B-C6138D67E531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {319870ED-F42D-4089-BE2B-C6138D67E531}.Debug|Any CPU.Build.0 = Debug|Any CPU + {319870ED-F42D-4089-BE2B-C6138D67E531}.Debug|x64.ActiveCfg = Debug|Any CPU + {319870ED-F42D-4089-BE2B-C6138D67E531}.Debug|x64.Build.0 = Debug|Any CPU + {319870ED-F42D-4089-BE2B-C6138D67E531}.Debug|x86.ActiveCfg = Debug|Any CPU + {319870ED-F42D-4089-BE2B-C6138D67E531}.Debug|x86.Build.0 = Debug|Any CPU + {319870ED-F42D-4089-BE2B-C6138D67E531}.Release|Any CPU.ActiveCfg = Release|Any CPU + {319870ED-F42D-4089-BE2B-C6138D67E531}.Release|Any CPU.Build.0 = Release|Any CPU + {319870ED-F42D-4089-BE2B-C6138D67E531}.Release|x64.ActiveCfg = Release|Any CPU + {319870ED-F42D-4089-BE2B-C6138D67E531}.Release|x64.Build.0 = Release|Any CPU + {319870ED-F42D-4089-BE2B-C6138D67E531}.Release|x86.ActiveCfg = Release|Any CPU + {319870ED-F42D-4089-BE2B-C6138D67E531}.Release|x86.Build.0 = Release|Any CPU + {CF033B84-95AC-4250-BF8A-4D615429F3EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF033B84-95AC-4250-BF8A-4D615429F3EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF033B84-95AC-4250-BF8A-4D615429F3EB}.Debug|x64.ActiveCfg = Debug|Any CPU + {CF033B84-95AC-4250-BF8A-4D615429F3EB}.Debug|x64.Build.0 = Debug|Any CPU + {CF033B84-95AC-4250-BF8A-4D615429F3EB}.Debug|x86.ActiveCfg = Debug|Any CPU + {CF033B84-95AC-4250-BF8A-4D615429F3EB}.Debug|x86.Build.0 = Debug|Any CPU + {CF033B84-95AC-4250-BF8A-4D615429F3EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF033B84-95AC-4250-BF8A-4D615429F3EB}.Release|Any CPU.Build.0 = Release|Any CPU + {CF033B84-95AC-4250-BF8A-4D615429F3EB}.Release|x64.ActiveCfg = Release|Any CPU + {CF033B84-95AC-4250-BF8A-4D615429F3EB}.Release|x64.Build.0 = Release|Any CPU + {CF033B84-95AC-4250-BF8A-4D615429F3EB}.Release|x86.ActiveCfg = Release|Any CPU + {CF033B84-95AC-4250-BF8A-4D615429F3EB}.Release|x86.Build.0 = Release|Any CPU + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}.Debug|x64.ActiveCfg = Debug|Any CPU + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}.Debug|x64.Build.0 = Debug|Any CPU + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}.Debug|x86.ActiveCfg = Debug|Any CPU + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}.Debug|x86.Build.0 = Debug|Any CPU + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}.Release|Any CPU.Build.0 = Release|Any CPU + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}.Release|x64.ActiveCfg = Release|Any CPU + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}.Release|x64.Build.0 = Release|Any CPU + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}.Release|x86.ActiveCfg = Release|Any CPU + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B}.Release|x86.Build.0 = Release|Any CPU + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA}.Debug|x64.ActiveCfg = Debug|Any CPU + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA}.Debug|x64.Build.0 = Debug|Any CPU + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA}.Debug|x86.ActiveCfg = Debug|Any CPU + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA}.Debug|x86.Build.0 = Debug|Any CPU + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA}.Release|Any CPU.Build.0 = Release|Any CPU + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA}.Release|x64.ActiveCfg = Release|Any CPU + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA}.Release|x64.Build.0 = Release|Any CPU + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA}.Release|x86.ActiveCfg = Release|Any CPU + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA}.Release|x86.Build.0 = Release|Any CPU + {57D00261-2BA7-467B-A890-5F78E32122DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57D00261-2BA7-467B-A890-5F78E32122DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57D00261-2BA7-467B-A890-5F78E32122DC}.Debug|x64.ActiveCfg = Debug|Any CPU + {57D00261-2BA7-467B-A890-5F78E32122DC}.Debug|x64.Build.0 = Debug|Any CPU + {57D00261-2BA7-467B-A890-5F78E32122DC}.Debug|x86.ActiveCfg = Debug|Any CPU + {57D00261-2BA7-467B-A890-5F78E32122DC}.Debug|x86.Build.0 = Debug|Any CPU + {57D00261-2BA7-467B-A890-5F78E32122DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57D00261-2BA7-467B-A890-5F78E32122DC}.Release|Any CPU.Build.0 = Release|Any CPU + {57D00261-2BA7-467B-A890-5F78E32122DC}.Release|x64.ActiveCfg = Release|Any CPU + {57D00261-2BA7-467B-A890-5F78E32122DC}.Release|x64.Build.0 = Release|Any CPU + {57D00261-2BA7-467B-A890-5F78E32122DC}.Release|x86.ActiveCfg = Release|Any CPU + {57D00261-2BA7-467B-A890-5F78E32122DC}.Release|x86.Build.0 = Release|Any CPU + {9BBCA112-32B2-4515-9752-0146F251A377}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9BBCA112-32B2-4515-9752-0146F251A377}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BBCA112-32B2-4515-9752-0146F251A377}.Debug|x64.ActiveCfg = Debug|Any CPU + {9BBCA112-32B2-4515-9752-0146F251A377}.Debug|x64.Build.0 = Debug|Any CPU + {9BBCA112-32B2-4515-9752-0146F251A377}.Debug|x86.ActiveCfg = Debug|Any CPU + {9BBCA112-32B2-4515-9752-0146F251A377}.Debug|x86.Build.0 = Debug|Any CPU + {9BBCA112-32B2-4515-9752-0146F251A377}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9BBCA112-32B2-4515-9752-0146F251A377}.Release|Any CPU.Build.0 = Release|Any CPU + {9BBCA112-32B2-4515-9752-0146F251A377}.Release|x64.ActiveCfg = Release|Any CPU + {9BBCA112-32B2-4515-9752-0146F251A377}.Release|x64.Build.0 = Release|Any CPU + {9BBCA112-32B2-4515-9752-0146F251A377}.Release|x86.ActiveCfg = Release|Any CPU + {9BBCA112-32B2-4515-9752-0146F251A377}.Release|x86.Build.0 = Release|Any CPU + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17}.Debug|x64.ActiveCfg = Debug|Any CPU + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17}.Debug|x64.Build.0 = Debug|Any CPU + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17}.Debug|x86.ActiveCfg = Debug|Any CPU + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17}.Debug|x86.Build.0 = Debug|Any CPU + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17}.Release|Any CPU.Build.0 = Release|Any CPU + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17}.Release|x64.ActiveCfg = Release|Any CPU + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17}.Release|x64.Build.0 = Release|Any CPU + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17}.Release|x86.ActiveCfg = Release|Any CPU + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17}.Release|x86.Build.0 = Release|Any CPU + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D}.Debug|x64.ActiveCfg = Debug|Any CPU + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D}.Debug|x64.Build.0 = Debug|Any CPU + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D}.Debug|x86.ActiveCfg = Debug|Any CPU + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D}.Debug|x86.Build.0 = Debug|Any CPU + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D}.Release|Any CPU.Build.0 = Release|Any CPU + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D}.Release|x64.ActiveCfg = Release|Any CPU + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D}.Release|x64.Build.0 = Release|Any CPU + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D}.Release|x86.ActiveCfg = Release|Any CPU + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D}.Release|x86.Build.0 = Release|Any CPU + {83B6F2DC-0358-4720-9CBD-36027D988228}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83B6F2DC-0358-4720-9CBD-36027D988228}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83B6F2DC-0358-4720-9CBD-36027D988228}.Debug|x64.ActiveCfg = Debug|Any CPU + {83B6F2DC-0358-4720-9CBD-36027D988228}.Debug|x64.Build.0 = Debug|Any CPU + {83B6F2DC-0358-4720-9CBD-36027D988228}.Debug|x86.ActiveCfg = Debug|Any CPU + {83B6F2DC-0358-4720-9CBD-36027D988228}.Debug|x86.Build.0 = Debug|Any CPU + {83B6F2DC-0358-4720-9CBD-36027D988228}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83B6F2DC-0358-4720-9CBD-36027D988228}.Release|Any CPU.Build.0 = Release|Any CPU + {83B6F2DC-0358-4720-9CBD-36027D988228}.Release|x64.ActiveCfg = Release|Any CPU + {83B6F2DC-0358-4720-9CBD-36027D988228}.Release|x64.Build.0 = Release|Any CPU + {83B6F2DC-0358-4720-9CBD-36027D988228}.Release|x86.ActiveCfg = Release|Any CPU + {83B6F2DC-0358-4720-9CBD-36027D988228}.Release|x86.Build.0 = Release|Any CPU + {86ED3702-826C-4D74-99F4-830C8F66923A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86ED3702-826C-4D74-99F4-830C8F66923A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86ED3702-826C-4D74-99F4-830C8F66923A}.Debug|x64.ActiveCfg = Debug|Any CPU + {86ED3702-826C-4D74-99F4-830C8F66923A}.Debug|x64.Build.0 = Debug|Any CPU + {86ED3702-826C-4D74-99F4-830C8F66923A}.Debug|x86.ActiveCfg = Debug|Any CPU + {86ED3702-826C-4D74-99F4-830C8F66923A}.Debug|x86.Build.0 = Debug|Any CPU + {86ED3702-826C-4D74-99F4-830C8F66923A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86ED3702-826C-4D74-99F4-830C8F66923A}.Release|Any CPU.Build.0 = Release|Any CPU + {86ED3702-826C-4D74-99F4-830C8F66923A}.Release|x64.ActiveCfg = Release|Any CPU + {86ED3702-826C-4D74-99F4-830C8F66923A}.Release|x64.Build.0 = Release|Any CPU + {86ED3702-826C-4D74-99F4-830C8F66923A}.Release|x86.ActiveCfg = Release|Any CPU + {86ED3702-826C-4D74-99F4-830C8F66923A}.Release|x86.Build.0 = Release|Any CPU + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E}.Debug|x64.Build.0 = Debug|Any CPU + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E}.Debug|x86.ActiveCfg = Debug|Any CPU + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E}.Debug|x86.Build.0 = Debug|Any CPU + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E}.Release|Any CPU.Build.0 = Release|Any CPU + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E}.Release|x64.ActiveCfg = Release|Any CPU + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E}.Release|x64.Build.0 = Release|Any CPU + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E}.Release|x86.ActiveCfg = Release|Any CPU + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E}.Release|x86.Build.0 = Release|Any CPU + {1DBE7E05-5408-4B05-8275-5290FF643978}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DBE7E05-5408-4B05-8275-5290FF643978}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DBE7E05-5408-4B05-8275-5290FF643978}.Debug|x64.ActiveCfg = Debug|Any CPU + {1DBE7E05-5408-4B05-8275-5290FF643978}.Debug|x64.Build.0 = Debug|Any CPU + {1DBE7E05-5408-4B05-8275-5290FF643978}.Debug|x86.ActiveCfg = Debug|Any CPU + {1DBE7E05-5408-4B05-8275-5290FF643978}.Debug|x86.Build.0 = Debug|Any CPU + {1DBE7E05-5408-4B05-8275-5290FF643978}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DBE7E05-5408-4B05-8275-5290FF643978}.Release|Any CPU.Build.0 = Release|Any CPU + {1DBE7E05-5408-4B05-8275-5290FF643978}.Release|x64.ActiveCfg = Release|Any CPU + {1DBE7E05-5408-4B05-8275-5290FF643978}.Release|x64.Build.0 = Release|Any CPU + {1DBE7E05-5408-4B05-8275-5290FF643978}.Release|x86.ActiveCfg = Release|Any CPU + {1DBE7E05-5408-4B05-8275-5290FF643978}.Release|x86.Build.0 = Release|Any CPU + {A0EC6A50-92AF-4422-9A4B-10020E479FAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0EC6A50-92AF-4422-9A4B-10020E479FAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0EC6A50-92AF-4422-9A4B-10020E479FAF}.Debug|x64.ActiveCfg = Debug|Any CPU + {A0EC6A50-92AF-4422-9A4B-10020E479FAF}.Debug|x64.Build.0 = Debug|Any CPU + {A0EC6A50-92AF-4422-9A4B-10020E479FAF}.Debug|x86.ActiveCfg = Debug|Any CPU + {A0EC6A50-92AF-4422-9A4B-10020E479FAF}.Debug|x86.Build.0 = Debug|Any CPU + {A0EC6A50-92AF-4422-9A4B-10020E479FAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0EC6A50-92AF-4422-9A4B-10020E479FAF}.Release|Any CPU.Build.0 = Release|Any CPU + {A0EC6A50-92AF-4422-9A4B-10020E479FAF}.Release|x64.ActiveCfg = Release|Any CPU + {A0EC6A50-92AF-4422-9A4B-10020E479FAF}.Release|x64.Build.0 = Release|Any CPU + {A0EC6A50-92AF-4422-9A4B-10020E479FAF}.Release|x86.ActiveCfg = Release|Any CPU + {A0EC6A50-92AF-4422-9A4B-10020E479FAF}.Release|x86.Build.0 = Release|Any CPU + {03C44C89-7BBE-4D90-951D-CE413EAC767B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03C44C89-7BBE-4D90-951D-CE413EAC767B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03C44C89-7BBE-4D90-951D-CE413EAC767B}.Debug|x64.ActiveCfg = Debug|Any CPU + {03C44C89-7BBE-4D90-951D-CE413EAC767B}.Debug|x64.Build.0 = Debug|Any CPU + {03C44C89-7BBE-4D90-951D-CE413EAC767B}.Debug|x86.ActiveCfg = Debug|Any CPU + {03C44C89-7BBE-4D90-951D-CE413EAC767B}.Debug|x86.Build.0 = Debug|Any CPU + {03C44C89-7BBE-4D90-951D-CE413EAC767B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03C44C89-7BBE-4D90-951D-CE413EAC767B}.Release|Any CPU.Build.0 = Release|Any CPU + {03C44C89-7BBE-4D90-951D-CE413EAC767B}.Release|x64.ActiveCfg = Release|Any CPU + {03C44C89-7BBE-4D90-951D-CE413EAC767B}.Release|x64.Build.0 = Release|Any CPU + {03C44C89-7BBE-4D90-951D-CE413EAC767B}.Release|x86.ActiveCfg = Release|Any CPU + {03C44C89-7BBE-4D90-951D-CE413EAC767B}.Release|x86.Build.0 = Release|Any CPU + {4FB4B780-6D71-4AB7-A531-CF808F2549CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FB4B780-6D71-4AB7-A531-CF808F2549CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FB4B780-6D71-4AB7-A531-CF808F2549CB}.Debug|x64.ActiveCfg = Debug|Any CPU + {4FB4B780-6D71-4AB7-A531-CF808F2549CB}.Debug|x64.Build.0 = Debug|Any CPU + {4FB4B780-6D71-4AB7-A531-CF808F2549CB}.Debug|x86.ActiveCfg = Debug|Any CPU + {4FB4B780-6D71-4AB7-A531-CF808F2549CB}.Debug|x86.Build.0 = Debug|Any CPU + {4FB4B780-6D71-4AB7-A531-CF808F2549CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FB4B780-6D71-4AB7-A531-CF808F2549CB}.Release|Any CPU.Build.0 = Release|Any CPU + {4FB4B780-6D71-4AB7-A531-CF808F2549CB}.Release|x64.ActiveCfg = Release|Any CPU + {4FB4B780-6D71-4AB7-A531-CF808F2549CB}.Release|x64.Build.0 = Release|Any CPU + {4FB4B780-6D71-4AB7-A531-CF808F2549CB}.Release|x86.ActiveCfg = Release|Any CPU + {4FB4B780-6D71-4AB7-A531-CF808F2549CB}.Release|x86.Build.0 = Release|Any CPU + {B72B80C9-FFBE-401F-8393-7558731705F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B72B80C9-FFBE-401F-8393-7558731705F2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B72B80C9-FFBE-401F-8393-7558731705F2}.Debug|x64.ActiveCfg = Debug|Any CPU + {B72B80C9-FFBE-401F-8393-7558731705F2}.Debug|x64.Build.0 = Debug|Any CPU + {B72B80C9-FFBE-401F-8393-7558731705F2}.Debug|x86.ActiveCfg = Debug|Any CPU + {B72B80C9-FFBE-401F-8393-7558731705F2}.Debug|x86.Build.0 = Debug|Any CPU + {B72B80C9-FFBE-401F-8393-7558731705F2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B72B80C9-FFBE-401F-8393-7558731705F2}.Release|Any CPU.Build.0 = Release|Any CPU + {B72B80C9-FFBE-401F-8393-7558731705F2}.Release|x64.ActiveCfg = Release|Any CPU + {B72B80C9-FFBE-401F-8393-7558731705F2}.Release|x64.Build.0 = Release|Any CPU + {B72B80C9-FFBE-401F-8393-7558731705F2}.Release|x86.ActiveCfg = Release|Any CPU + {B72B80C9-FFBE-401F-8393-7558731705F2}.Release|x86.Build.0 = Release|Any CPU + {8832643C-A4D4-4F7A-8051-E36D04372932}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8832643C-A4D4-4F7A-8051-E36D04372932}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8832643C-A4D4-4F7A-8051-E36D04372932}.Debug|x64.ActiveCfg = Debug|Any CPU + {8832643C-A4D4-4F7A-8051-E36D04372932}.Debug|x64.Build.0 = Debug|Any CPU + {8832643C-A4D4-4F7A-8051-E36D04372932}.Debug|x86.ActiveCfg = Debug|Any CPU + {8832643C-A4D4-4F7A-8051-E36D04372932}.Debug|x86.Build.0 = Debug|Any CPU + {8832643C-A4D4-4F7A-8051-E36D04372932}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8832643C-A4D4-4F7A-8051-E36D04372932}.Release|Any CPU.Build.0 = Release|Any CPU + {8832643C-A4D4-4F7A-8051-E36D04372932}.Release|x64.ActiveCfg = Release|Any CPU + {8832643C-A4D4-4F7A-8051-E36D04372932}.Release|x64.Build.0 = Release|Any CPU + {8832643C-A4D4-4F7A-8051-E36D04372932}.Release|x86.ActiveCfg = Release|Any CPU + {8832643C-A4D4-4F7A-8051-E36D04372932}.Release|x86.Build.0 = Release|Any CPU + {C4E98E11-EFAC-44B9-8942-591FBB09922B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4E98E11-EFAC-44B9-8942-591FBB09922B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4E98E11-EFAC-44B9-8942-591FBB09922B}.Debug|x64.ActiveCfg = Debug|Any CPU + {C4E98E11-EFAC-44B9-8942-591FBB09922B}.Debug|x64.Build.0 = Debug|Any CPU + {C4E98E11-EFAC-44B9-8942-591FBB09922B}.Debug|x86.ActiveCfg = Debug|Any CPU + {C4E98E11-EFAC-44B9-8942-591FBB09922B}.Debug|x86.Build.0 = Debug|Any CPU + {C4E98E11-EFAC-44B9-8942-591FBB09922B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4E98E11-EFAC-44B9-8942-591FBB09922B}.Release|Any CPU.Build.0 = Release|Any CPU + {C4E98E11-EFAC-44B9-8942-591FBB09922B}.Release|x64.ActiveCfg = Release|Any CPU + {C4E98E11-EFAC-44B9-8942-591FBB09922B}.Release|x64.Build.0 = Release|Any CPU + {C4E98E11-EFAC-44B9-8942-591FBB09922B}.Release|x86.ActiveCfg = Release|Any CPU + {C4E98E11-EFAC-44B9-8942-591FBB09922B}.Release|x86.Build.0 = Release|Any CPU + {FDE5B67A-607D-4789-A667-BC79D7F0762B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDE5B67A-607D-4789-A667-BC79D7F0762B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDE5B67A-607D-4789-A667-BC79D7F0762B}.Debug|x64.ActiveCfg = Debug|Any CPU + {FDE5B67A-607D-4789-A667-BC79D7F0762B}.Debug|x64.Build.0 = Debug|Any CPU + {FDE5B67A-607D-4789-A667-BC79D7F0762B}.Debug|x86.ActiveCfg = Debug|Any CPU + {FDE5B67A-607D-4789-A667-BC79D7F0762B}.Debug|x86.Build.0 = Debug|Any CPU + {FDE5B67A-607D-4789-A667-BC79D7F0762B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDE5B67A-607D-4789-A667-BC79D7F0762B}.Release|Any CPU.Build.0 = Release|Any CPU + {FDE5B67A-607D-4789-A667-BC79D7F0762B}.Release|x64.ActiveCfg = Release|Any CPU + {FDE5B67A-607D-4789-A667-BC79D7F0762B}.Release|x64.Build.0 = Release|Any CPU + {FDE5B67A-607D-4789-A667-BC79D7F0762B}.Release|x86.ActiveCfg = Release|Any CPU + {FDE5B67A-607D-4789-A667-BC79D7F0762B}.Release|x86.Build.0 = Release|Any CPU + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}.Debug|x64.ActiveCfg = Debug|Any CPU + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}.Debug|x64.Build.0 = Debug|Any CPU + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}.Debug|x86.ActiveCfg = Debug|Any CPU + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}.Debug|x86.Build.0 = Debug|Any CPU + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}.Release|Any CPU.Build.0 = Release|Any CPU + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}.Release|x64.ActiveCfg = Release|Any CPU + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}.Release|x64.Build.0 = Release|Any CPU + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}.Release|x86.ActiveCfg = Release|Any CPU + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2}.Release|x86.Build.0 = Release|Any CPU + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}.Debug|x64.ActiveCfg = Debug|Any CPU + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}.Debug|x64.Build.0 = Debug|Any CPU + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}.Debug|x86.ActiveCfg = Debug|Any CPU + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}.Debug|x86.Build.0 = Debug|Any CPU + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}.Release|Any CPU.Build.0 = Release|Any CPU + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}.Release|x64.ActiveCfg = Release|Any CPU + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}.Release|x64.Build.0 = Release|Any CPU + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}.Release|x86.ActiveCfg = Release|Any CPU + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {E62A732B-740E-479A-918C-17BE95374A7A} = {881589EE-639B-0A35-DBCE-5D94E4FC90C0} + {9E9073C7-139D-4AFC-808D-C2E39AFD04E4} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {45B13D8B-5B81-49C3-82D3-C6295D0DFBCF} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {9D259784-603E-4F96-A2E4-360E291D1D6C} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {A37158B5-8404-4E04-B63F-EA26B237FD42} = {EEFA252F-3CE0-C131-3F8C-AF1C5E35DF91} + {37DF7393-B6F0-45CA-B436-ED84B6D6B107} = {188FE78A-9273-6FA7-91AA-4C340983034C} + {0039A67F-3F7A-428F-A63F-F5AC7CE75ADF} = {188FE78A-9273-6FA7-91AA-4C340983034C} + {6A17E833-F897-4075-A9E6-CFED5AED1858} = {188FE78A-9273-6FA7-91AA-4C340983034C} + {FEE4A8EC-B1FB-4F40-A833-0AEE08B1DDA5} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {6F05EB9E-8CFE-4BD8-9E34-893D4F13DD10} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {E86B329B-216B-4261-A730-2F310059ADCC} = {EEFA252F-3CE0-C131-3F8C-AF1C5E35DF91} + {AE3BB780-FD58-476F-B1BE-3CA147773E28} = {EEFA252F-3CE0-C131-3F8C-AF1C5E35DF91} + {98BD15E6-71A0-45FB-AD6B-6B634C7B4B24} = {188FE78A-9273-6FA7-91AA-4C340983034C} + {4C49A93B-86F9-4064-AE84-E205A3290640} = {188FE78A-9273-6FA7-91AA-4C340983034C} + {F36EF875-752F-446E-9A03-9711B262F220} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {A06BB5B9-E130-4870-9A6E-B222EA900A30} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {BA6DBFAA-9CB7-4768-A7B6-14A548FD4F5A} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {D34E826E-F2C3-4D92-AC89-A36EB1F5F801} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {2FF073D1-2089-4BA2-9CA9-918BE6AC6970} = {EEFA252F-3CE0-C131-3F8C-AF1C5E35DF91} + {53E196FD-3356-45EA-ACD3-CDEA84A5E5A1} = {EEFA252F-3CE0-C131-3F8C-AF1C5E35DF91} + {2C4F2AFE-EECB-4C27-B28E-09FD0147A41A} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {063E4EC8-05EE-4134-AB10-2320B88EB0A7} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {1D2E550F-BCF0-4FC2-B74B-A50988D04229} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {319870ED-F42D-4089-BE2B-C6138D67E531} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {CF033B84-95AC-4250-BF8A-4D615429F3EB} = {EEFA252F-3CE0-C131-3F8C-AF1C5E35DF91} + {CDFF821D-EB6F-4622-821D-F6AD1E5F7F4B} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {495D6005-C265-4D3B-8E90-DFDE60D2EDAA} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {57D00261-2BA7-467B-A890-5F78E32122DC} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {9BBCA112-32B2-4515-9752-0146F251A377} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {BAC4EBFB-4F0F-4770-9408-3748EF27AC17} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {70BFCFDD-7DB4-4875-9933-21229B4D8D8D} = {EEFA252F-3CE0-C131-3F8C-AF1C5E35DF91} + {83B6F2DC-0358-4720-9CBD-36027D988228} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {86ED3702-826C-4D74-99F4-830C8F66923A} = {EEFA252F-3CE0-C131-3F8C-AF1C5E35DF91} + {DF2CCA78-AEA9-4425-9160-A4487C8AB70E} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {1DBE7E05-5408-4B05-8275-5290FF643978} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {A0EC6A50-92AF-4422-9A4B-10020E479FAF} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {03C44C89-7BBE-4D90-951D-CE413EAC767B} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {4FB4B780-6D71-4AB7-A531-CF808F2549CB} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {B72B80C9-FFBE-401F-8393-7558731705F2} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {8832643C-A4D4-4F7A-8051-E36D04372932} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {C4E98E11-EFAC-44B9-8942-591FBB09922B} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {FDE5B67A-607D-4789-A667-BC79D7F0762B} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {45390D20-78E7-4DB4-A31F-59E5DCC3BDB2} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + {0E41A6C8-E17A-48C2-A7EA-BEEC651F3C00} = {C9700E2F-F6F1-A264-19EE-D9EB5C62093D} + EndGlobalSection +EndGlobal diff --git a/MuPDF.NET.Office/01-UnlockFonts/01-UnlockFonts.csproj b/MuPDF.NET.Office/01-UnlockFonts/01-UnlockFonts.csproj new file mode 100644 index 0000000..a23a6b1 --- /dev/null +++ b/MuPDF.NET.Office/01-UnlockFonts/01-UnlockFonts.csproj @@ -0,0 +1,14 @@ + + + Exe + MuPDF.NET.Examples.Office.UnlockFonts + 01-UnlockFonts + + x64 + + + + + + + diff --git a/MuPDF.NET.Office/01-UnlockFonts/Expected/unlock.summary.txt b/MuPDF.NET.Office/01-UnlockFonts/Expected/unlock.summary.txt new file mode 100644 index 0000000..c888337 --- /dev/null +++ b/MuPDF.NET.Office/01-UnlockFonts/Expected/unlock.summary.txt @@ -0,0 +1,2 @@ +fontDirCountMin1=true +unlocked=true diff --git a/MuPDF.NET.Office/01-UnlockFonts/Program.cs b/MuPDF.NET.Office/01-UnlockFonts/Program.cs new file mode 100644 index 0000000..ae654ca --- /dev/null +++ b/MuPDF.NET.Office/01-UnlockFonts/Program.cs @@ -0,0 +1,44 @@ +using MuPDF.NET.Examples.Common; +using MuPDF.NET.Office; + +namespace MuPDF.NET.Examples.Office.UnlockFonts; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.Office / 01-UnlockFonts"); + UnlockFonts(); + } + + /// + /// Unlock Office document support and inspect font search paths. + /// + static void UnlockFonts() + { + var check = new ResultCheck("MuPDF.NET.Office", "01-UnlockFonts"); + + // Optional key from MUPDF_OFFICE_KEY; null still enables restricted mode for samples. + string? key = OfficeLicense.KeyFromEnvironment(); + + // fontPathAuto: discover system font directories (Windows Fonts, fc-list on Linux, …). + int flags = MuPDFOffice.Unlock(key, fontPathAuto: true); + var fonts = MuPDFOffice.GetFontPath(); + + ConsoleEx.Info($"Unlocked: {MuPDFOffice.IsUnlocked}"); + ConsoleEx.Info($"Key flags: 0x{flags:X}"); + ConsoleEx.Info($"Font directory count: {fonts.Count}"); + + // Portable baseline only — absolute font paths and Windows-only dirs differ by OS. + check.Properties( + new Dictionary + { + ["unlocked"] = MuPDFOffice.IsUnlocked ? "true" : "false", + ["fontDirCountMin1"] = fonts.Count >= 1 ? "true" : "false", + }, + "unlock.summary.txt"); + + check.Finish(); + } +} diff --git a/MuPDF.NET.Office/01-UnlockFonts/README.md b/MuPDF.NET.Office/01-UnlockFonts/README.md new file mode 100644 index 0000000..3f42dbd --- /dev/null +++ b/MuPDF.NET.Office/01-UnlockFonts/README.md @@ -0,0 +1,37 @@ +# 01-UnlockFonts + +Unlock Office document support and inspect font search directories. + +## Sample method + +`UnlockFonts()` in `Program.cs`. + +## Package + +- [MuPDF.NET.Office](https://www.nuget.org/packages/MuPDF.NET.Office) + +## Prerequisites + +- Windows x64, Linux x64/arm64, or macOS (Office natives via RID packages). +- Optional `MUPDF_OFFICE_KEY` environment variable. +- Optional license key via environment variable `MUPDF_OFFICE_KEY` (restricted mode works for samples without a key). + +## Input / output + +| | Path | +|--|------| +| Expected | `Expected/unlock.summary.txt` | + +## Run + +```bash +dotnet run --project MuPDF.NET.Office/01-UnlockFonts +``` + +Expected `unlock.summary.txt` checks only portable keys (`unlocked`, `fontDirCountMin1`) so the same baseline passes on Windows, Linux, and macOS. + +## APIs used + +- `MuPDFOffice.Unlock` +- `MuPDFOffice.GetFontPath` +- `MuPDFOffice.IsUnlocked` diff --git a/MuPDF.NET.Office/02-OpenHwpxDocx/02-OpenHwpxDocx.csproj b/MuPDF.NET.Office/02-OpenHwpxDocx/02-OpenHwpxDocx.csproj new file mode 100644 index 0000000..6101f75 --- /dev/null +++ b/MuPDF.NET.Office/02-OpenHwpxDocx/02-OpenHwpxDocx.csproj @@ -0,0 +1,13 @@ + + + Exe + MuPDF.NET.Examples.Office.OpenHwpxDocx + 02-OpenHwpxDocx + x64 + + + + + + + diff --git a/MuPDF.NET.Office/02-OpenHwpxDocx/Expected/pages.summary.txt b/MuPDF.NET.Office/02-OpenHwpxDocx/Expected/pages.summary.txt new file mode 100644 index 0000000..24ea41e --- /dev/null +++ b/MuPDF.NET.Office/02-OpenHwpxDocx/Expected/pages.summary.txt @@ -0,0 +1,2 @@ +docxPages=3 +hwpxPages=1 diff --git a/MuPDF.NET.Office/02-OpenHwpxDocx/Program.cs b/MuPDF.NET.Office/02-OpenHwpxDocx/Program.cs new file mode 100644 index 0000000..dd33c8e --- /dev/null +++ b/MuPDF.NET.Office/02-OpenHwpxDocx/Program.cs @@ -0,0 +1,53 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; +using MuPDF.NET.Office; + +namespace MuPDF.NET.Examples.Office.OpenHwpxDocx; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.Office / 02-OpenHwpxDocx"); + OpenHwpxDocx(); + } + + /// + /// Unlock Office, then open DOCX and HWPX via Document.Open. + /// + static void OpenHwpxDocx() + { + var check = new ResultCheck("MuPDF.NET.Office", "02-OpenHwpxDocx"); + + // Must unlock before opening Office formats. + MuPDFOffice.Unlock(OfficeLicense.KeyFromEnvironment(), fontPathAuto: true); + + string docx = ExamplePaths.OfficeInput("pages.docx"); + string hwpx = ExamplePaths.OfficeInput("sample.hwpx"); + int docxPages; + int hwpxPages; + + using (var d1 = Document.Open(docx)) + { + docxPages = d1.PageCount; + ConsoleEx.Info($"DOCX pages: {docxPages}"); + } + + using (var d2 = Document.Open(hwpx)) + { + hwpxPages = d2.PageCount; + ConsoleEx.Info($"HWPX pages: {hwpxPages}"); + } + + check.Properties( + new Dictionary + { + ["docxPages"] = docxPages.ToString(), + ["hwpxPages"] = hwpxPages.ToString(), + }, + "pages.summary.txt"); + + check.Finish(); + } +} diff --git a/MuPDF.NET.Office/02-OpenHwpxDocx/README.md b/MuPDF.NET.Office/02-OpenHwpxDocx/README.md new file mode 100644 index 0000000..39d22c1 --- /dev/null +++ b/MuPDF.NET.Office/02-OpenHwpxDocx/README.md @@ -0,0 +1,34 @@ +# 02-OpenHwpxDocx + +Unlock Office, then open DOCX and HWPX with `Document.Open`. + +## Sample method + +`OpenHwpxDocx()` in `Program.cs`. + +## Package + +- [MuPDF.NET.Office](https://www.nuget.org/packages/MuPDF.NET.Office) + +## Prerequisites + +- Call `MuPDFOffice.Unlock` before opening Office formats. +- Optional `MUPDF_OFFICE_KEY`. + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET.Office/pages.docx`, `sample.hwpx` | +| Expected | `Expected/pages.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET.Office\02-OpenHwpxDocx +``` + +## APIs used + +- `MuPDFOffice.Unlock` +- `Document.Open` on DOCX / HWPX paths diff --git a/MuPDF.NET.Office/03-ExportToPdf/03-ExportToPdf.csproj b/MuPDF.NET.Office/03-ExportToPdf/03-ExportToPdf.csproj new file mode 100644 index 0000000..11240de --- /dev/null +++ b/MuPDF.NET.Office/03-ExportToPdf/03-ExportToPdf.csproj @@ -0,0 +1,13 @@ + + + Exe + MuPDF.NET.Examples.Office.ExportToPdf + 03-ExportToPdf + x64 + + + + + + + diff --git a/MuPDF.NET.Office/03-ExportToPdf/Expected/pages.summary.txt b/MuPDF.NET.Office/03-ExportToPdf/Expected/pages.summary.txt new file mode 100644 index 0000000..a531124 --- /dev/null +++ b/MuPDF.NET.Office/03-ExportToPdf/Expected/pages.summary.txt @@ -0,0 +1,2 @@ +pageCount=3 +textSha256=3e23d56bc02e4a97421ed42a10a93193dd97f20c08bfa0afd09ebbec9f720e76 diff --git a/MuPDF.NET.Office/03-ExportToPdf/Program.cs b/MuPDF.NET.Office/03-ExportToPdf/Program.cs new file mode 100644 index 0000000..87497db --- /dev/null +++ b/MuPDF.NET.Office/03-ExportToPdf/Program.cs @@ -0,0 +1,35 @@ +using MuPDF.NET.Examples.Common; +using MuPDF.NET.Office; + +namespace MuPDF.NET.Examples.Office.ExportToPdf; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.Office / 03-ExportToPdf"); + ExportToPdf(); + } + + /// + /// Export an Office document to PDF. + /// + static void ExportToPdf() + { + string input = ExamplePaths.OfficeInput("pages.docx"); + string output = ExamplePaths.Output("MuPDF.NET.Office", "03-ExportToPdf", "pages.pdf"); + var check = new ResultCheck("MuPDF.NET.Office", "03-ExportToPdf"); + + MuPDFOffice.Unlock(OfficeLicense.KeyFromEnvironment(), fontPathAuto: true); + + // Renders Office content through sodochandler into a PDF file. + MuPDFOffice.ToPdf(input, output); + + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Wrote: {output} ({new System.IO.FileInfo(output).Length} bytes)"); + + check.Properties(PdfFingerprint.FromFile(output), "pages.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET.Office/03-ExportToPdf/README.md b/MuPDF.NET.Office/03-ExportToPdf/README.md new file mode 100644 index 0000000..3ed9851 --- /dev/null +++ b/MuPDF.NET.Office/03-ExportToPdf/README.md @@ -0,0 +1,35 @@ +# 03-ExportToPdf + +Export an Office document (DOCX) to PDF. + +## Sample method + +`ExportToPdf()` in `Program.cs`. + +## Package + +- [MuPDF.NET.Office](https://www.nuget.org/packages/MuPDF.NET.Office) + +## Prerequisites + +- `MuPDFOffice.Unlock` first. +- Optional `MUPDF_OFFICE_KEY`. + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET.Office/pages.docx` | +| Output | `Output/MuPDF.NET.Office/03-ExportToPdf/pages.pdf` | +| Expected | `Expected/pages.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET.Office\03-ExportToPdf +``` + +## APIs used + +- `MuPDFOffice.Unlock` +- `MuPDFOffice.ToPdf` diff --git a/MuPDF.NET.Office/04-ExportToMarkdownJson/04-ExportToMarkdownJson.csproj b/MuPDF.NET.Office/04-ExportToMarkdownJson/04-ExportToMarkdownJson.csproj new file mode 100644 index 0000000..9b88a21 --- /dev/null +++ b/MuPDF.NET.Office/04-ExportToMarkdownJson/04-ExportToMarkdownJson.csproj @@ -0,0 +1,13 @@ + + + Exe + MuPDF.NET.Examples.Office.ExportToMarkdownJson + 04-ExportToMarkdownJson + x64 + + + + + + + diff --git a/MuPDF.NET.Office/04-ExportToMarkdownJson/Expected/pages.md b/MuPDF.NET.Office/04-ExportToMarkdownJson/Expected/pages.md new file mode 100644 index 0000000..0897c0d --- /dev/null +++ b/MuPDF.NET.Office/04-ExportToMarkdownJson/Expected/pages.md @@ -0,0 +1,17 @@ +This is page one. + + + +This is page two. + + + +This is page three. + + + +This is page four. + + + +This is page five. diff --git a/MuPDF.NET.Office/04-ExportToMarkdownJson/Expected/pages.summary.txt b/MuPDF.NET.Office/04-ExportToMarkdownJson/Expected/pages.summary.txt new file mode 100644 index 0000000..c8f4790 --- /dev/null +++ b/MuPDF.NET.Office/04-ExportToMarkdownJson/Expected/pages.summary.txt @@ -0,0 +1,4 @@ +pageCount=3 +pageHeight=841.9 +pageWidth=595.3 +texts=1:This is page one.|2:This is page two.|3:This is page three. diff --git a/MuPDF.NET.Office/04-ExportToMarkdownJson/Program.cs b/MuPDF.NET.Office/04-ExportToMarkdownJson/Program.cs new file mode 100644 index 0000000..131fc66 --- /dev/null +++ b/MuPDF.NET.Office/04-ExportToMarkdownJson/Program.cs @@ -0,0 +1,41 @@ +using MuPDF.NET.Examples.Common; +using MuPDF.NET.Office; + +namespace MuPDF.NET.Examples.Office.ExportToMarkdownJson; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.Office / 04-ExportToMarkdownJson"); + ExportToMarkdownJson(); + } + + /// + /// Export an Office document to Markdown and JSON. + /// + static void ExportToMarkdownJson() + { + string input = ExamplePaths.OfficeInput("pages.docx"); + string mdOut = ExamplePaths.Output("MuPDF.NET.Office", "04-ExportToMarkdownJson", "pages.md"); + string jsonOut = ExamplePaths.Output("MuPDF.NET.Office", "04-ExportToMarkdownJson", "pages.json"); + var check = new ResultCheck("MuPDF.NET.Office", "04-ExportToMarkdownJson"); + + MuPDFOffice.Unlock(OfficeLicense.KeyFromEnvironment(), fontPathAuto: true); + + // High-level Office export helpers write files directly. + MuPDFOffice.ToMarkdown(input, mdOut); + MuPDFOffice.ToJson(input, jsonOut); + + string md = File.ReadAllText(mdOut); + string json = File.ReadAllText(jsonOut); + ConsoleEx.Info($"Markdown bytes: {md.Length}, JSON bytes: {json.Length}"); + + // Markdown text is stable across OS. JSON embeds OS-specific font names + // and glyph metrics — fingerprint page size + text only. + check.Text(md, "pages.md"); + check.Properties(OfficeJsonFingerprint.FromJson(json), "pages.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET.Office/04-ExportToMarkdownJson/README.md b/MuPDF.NET.Office/04-ExportToMarkdownJson/README.md new file mode 100644 index 0000000..d918cda --- /dev/null +++ b/MuPDF.NET.Office/04-ExportToMarkdownJson/README.md @@ -0,0 +1,45 @@ +# 04-ExportToMarkdownJson + +Export an Office document to Markdown and JSON. + +## Sample method + +`ExportToMarkdownJson()` in `Program.cs`. + +## Package + +- [MuPDF.NET.Office](https://www.nuget.org/packages/MuPDF.NET.Office) + +## Prerequisites + +- `MuPDFOffice.Unlock` first. +- Optional `MUPDF_OFFICE_KEY`. + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET.Office/pages.docx` | +| Output | `pages.md`, `pages.json` under `Output/MuPDF.NET.Office/04-ExportToMarkdownJson/` | +| Expected | `pages.md`, `pages.summary.txt` | + +`pages.json` is still written for inspection. The golden check uses +`pages.summary.txt` (page size + text) because SmartOffice font substitution +differs by OS (e.g. Arial / Liberation Sans on Windows vs Liberation Serif on +Linux), which also changes glyph metrics in the full JSON. + +To improve visual font parity on Linux, install Liberation fonts +(`fonts-liberation` / `fonts-liberation2` on Debian/Ubuntu). Exact Windows +font names still will not match unless you ship and force the same TTF files +via `MuPDFOffice.Unlock(..., fontPath: ...)`. + +## Run + +```bash +dotnet run --project MuPDF.NET.Office/04-ExportToMarkdownJson +``` + +## APIs used + +- `MuPDFOffice.ToMarkdown` +- `MuPDFOffice.ToJson` diff --git a/MuPDF.NET.Office/05-WithPdf4Llm/05-WithPdf4Llm.csproj b/MuPDF.NET.Office/05-WithPdf4Llm/05-WithPdf4Llm.csproj new file mode 100644 index 0000000..ce5fb53 --- /dev/null +++ b/MuPDF.NET.Office/05-WithPdf4Llm/05-WithPdf4Llm.csproj @@ -0,0 +1,14 @@ + + + Exe + MuPDF.NET.Examples.Office.WithPdf4Llm + 05-WithPdf4Llm + x64 + + + + + + + + diff --git a/MuPDF.NET.Office/05-WithPdf4Llm/Expected/sample.hwpx.md b/MuPDF.NET.Office/05-WithPdf4Llm/Expected/sample.hwpx.md new file mode 100644 index 0000000..d0bd5ff --- /dev/null +++ b/MuPDF.NET.Office/05-WithPdf4Llm/Expected/sample.hwpx.md @@ -0,0 +1,2 @@ +안녕하세요 이건 테스트 파일입니다. + diff --git a/MuPDF.NET.Office/05-WithPdf4Llm/Expected/sample.shape.txt b/MuPDF.NET.Office/05-WithPdf4Llm/Expected/sample.shape.txt new file mode 100644 index 0000000..40c18b3 --- /dev/null +++ b/MuPDF.NET.Office/05-WithPdf4Llm/Expected/sample.shape.txt @@ -0,0 +1,2 @@ +HHHHH HH HHH HHHHH. + diff --git a/MuPDF.NET.Office/05-WithPdf4Llm/Program.cs b/MuPDF.NET.Office/05-WithPdf4Llm/Program.cs new file mode 100644 index 0000000..fd866a8 --- /dev/null +++ b/MuPDF.NET.Office/05-WithPdf4Llm/Program.cs @@ -0,0 +1,55 @@ +using System.Text; +using MuPDF.NET.Examples.Common; +using MuPDF.NET.Office; +using MuPDF.NET.PDF4LLM; + +namespace MuPDF.NET.Examples.Office.WithPdf4Llm; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.Office / 05-WithPdf4Llm"); + WithPdf4Llm(); + } + + /// + /// Unlock Office, then run MuPDF4LLM.ToMarkdown on an HWPX path. + /// + static void WithPdf4Llm() + { + string input = ExamplePaths.OfficeInput("sample.hwpx"); + string output = ExamplePaths.Output("MuPDF.NET.Office", "05-WithPdf4Llm", "sample.hwpx.md"); + var check = new ResultCheck("MuPDF.NET.Office", "05-WithPdf4Llm"); + + // Office unlock registers the document handler so PDF4LLM can open HWPX/DOCX paths. + MuPDFOffice.Unlock(OfficeLicense.KeyFromEnvironment(), fontPathAuto: true); + + bool prior = MuPDF4LLM.UseLayout; + try + { + MuPDF4LLM.SetUseLayout(false); + string markdown = MuPDF4LLM.ToMarkdown(input, showProgress: false) ?? ""; + // Always write UTF-8 (no BOM) so Output/ is readable on every OS. + File.WriteAllText(output, markdown, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); + + ConsoleEx.Info($"Opened via Office unlock: {input}"); + ConsoleEx.Info($"Markdown length: {markdown.Length}"); + ConsoleEx.Info( + OfficeTextShape.ContainsHangul(markdown) + ? "Hangul text extracted." + : "Hangul not in Unicode form (common on Windows when SmartOffice falls back to non-CJK fonts); using shape baseline."); + + // Full markdown differs by OS font/ToUnicode coverage. Shape maps Hangul, + // U+FFFD, and NUL to H so Windows and Linux share one Expected file. + check.Text(OfficeTextShape.FromText(markdown), "sample.shape.txt"); + } + finally + { + MuPDF4LLM.SetUseLayout(prior); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET.Office/05-WithPdf4Llm/README.md b/MuPDF.NET.Office/05-WithPdf4Llm/README.md new file mode 100644 index 0000000..2e95453 --- /dev/null +++ b/MuPDF.NET.Office/05-WithPdf4Llm/README.md @@ -0,0 +1,47 @@ +# 05-WithPdf4Llm + +Unlock Office, then run `MuPDF4LLM.ToMarkdown` on an HWPX path. + +## Sample method + +`WithPdf4Llm()` in `Program.cs`. + +## Packages + +- [MuPDF.NET.Office](https://www.nuget.org/packages/MuPDF.NET.Office) +- [MuPDF.NET.PDF4LLM](https://www.nuget.org/packages/MuPDF.NET.PDF4LLM) + +## Prerequisites + +- `MuPDFOffice.Unlock` registers the Office document handler so PDF4LLM can open HWPX/DOCX. +- Optional `MUPDF_OFFICE_KEY`. + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET.Office/sample.hwpx` | +| Output | `Output/MuPDF.NET.Office/05-WithPdf4Llm/sample.hwpx.md` | +| Expected | `Expected/sample.shape.txt` (portable) | + +`sample.hwpx.md` under Expected/ is the correct Hangul reference +(`안녕하세요 이건 테스트 파일입니다.`). On Linux, SmartOffice typically +extracts that Unicode text. On Windows it often falls back to +Arial/Liberation **without Hangul glyphs**, so `ToMarkdown` yields U+FFFD +placeholders (and you may see `cannot create ToUnicode mapping for …Arial`). + +The golden check therefore compares a **text shape** (`H` = Hangul or +replacement/NUL), which matches on both OS. Full markdown is still written +to `Output/` for inspection. + +## Run + +```bash +dotnet run --project MuPDF.NET.Office/05-WithPdf4Llm +``` + +## APIs used + +- `MuPDFOffice.Unlock` +- `MuPDF4LLM.SetUseLayout(false)` +- `MuPDF4LLM.ToMarkdown` diff --git a/MuPDF.NET.PDF4LLM/01-ToMarkdown/01-ToMarkdown.csproj b/MuPDF.NET.PDF4LLM/01-ToMarkdown/01-ToMarkdown.csproj new file mode 100644 index 0000000..e580858 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/01-ToMarkdown/01-ToMarkdown.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.PDF4LLM.ToMarkdown + 01-ToMarkdown + + + + + + diff --git a/MuPDF.NET.PDF4LLM/01-ToMarkdown/Expected/columns.md b/MuPDF.NET.PDF4LLM/01-ToMarkdown/Expected/columns.md new file mode 100644 index 0000000..b5360dd --- /dev/null +++ b/MuPDF.NET.PDF4LLM/01-ToMarkdown/Expected/columns.md @@ -0,0 +1,140 @@ +# **Kid’s News** +## Hammond Elementary School Fall 2000 + +### **Kids’ Right to Vote** + +**by Lindsey** + +The election is a very important +time of the year. Kids should be +able to vote. They should be driven +by their parents to the polling +location to vote and then taken +home by their parents. If kids voted +it would make a difference in +which president was elected. +Adults have no more rights than +kids, and everybody should have +their own right to vote for the +president. If kids could vote, the +law should be that they can’t talk +about their votes, and they should +have to know a lot about the +candidate to vote for him or her. + +When the next election comes, +kids older than 10 should be able +to vote. There are some reasons +why kids can’t vote now. They +don’t have the education and + + +information on our political system +to be able to make a good decision. Kids don’t understand the +views of the candidates on issues +such as the country’s health system, or welfare system, or taxes. +They will easily be persuaded by +other kids’ opinions about whom +they are voting for. Kids would be +voting without knowing what they +are really voting for. They might +copy someone else’s ballot. + +The law should say that if kids +are ever allowed to vote, they +should be supervised. Kids aren’t +very educated in politics. If kids +knew more about the candidates, it +would be fine for them to vote. +Although all adults are allowed to +vote, some adults vote without +knowing how the candidates view +the important issues. These adults +are voting in the same way that +kids would. If these adults do this, +then why can’t children vote? The +real point to make here is that +these adults should not vote at all. I +think that kids should have the +right to vote. + +### **Harry Potter** + +**by Bryan** + +Harry Potter is a good book for +advanced readers. It is a story +about a boy who lost his parents +and was delivered to his aunt and +uncle and his cousin. Since they +hate him so much, they make him +work hard. One day he gets a +letter saying he can go to a +wizarding school, but his uncle +won’t let him go. Then a wizard +comes and takes Harry school +shopping. Next, Harry is taken to +a magic train to a school of witchcraft and wizardry and has a lot of +adventures there. One adventure +is that he finds out the story of +how his parents died. + +After interviewing classmates, I +found some people like and don’t +like Harry Potter. Nick says he +likes Harry Potter because it’s +creative and funny. Damani likes +it because it takes a while to read +and you think something bad will +happen but it doesn’t. Reid says it +is advanced because it’s so long. + +number is 66. + +To make my day complete, he +offered to give me his autograph +and he shook my hand. I wished +him luck on the game that he +would be playing on Monday +Night Football. He got into his car +and left the parking lot. I couldn’t +stop smiling. Just think – I was +lucky enough to interview a +Redskin. What a thrill! + +### **From a Kid to a Redskin** + + +**by Patrick** + +I stood toe to toe with a giant of +a man. It’s amazing how big +professional football players are. +On Saturday afternoon, Oct. 28, +2000, I went to Redskins Park in +Ashburn, Va., with the hope of +interviewing one of the players. +While waiting in the parking lot for +the players to leave practice, I was +lucky enough to see Michael +Moore, all 6 foot 3 inches tall, +weighing 320 pounds. He approached his car and I went over + + +to see if I could ask him a few +questions. + +He told me how his father got +him interested in football when he +was in the 9th grade. Moore’s +greatest teacher in life is his father. +Moore played offense and defense +at Fayette County High School in +Alabama. He attended college at +Alabama for three years and then +transferred to Troy State. In 2000, +“Mookie,” as he is known to his +fellow players, signed a contract +with the Washington Redskins. +He’s an offensive lineman and his + diff --git a/MuPDF.NET.PDF4LLM/01-ToMarkdown/Program.cs b/MuPDF.NET.PDF4LLM/01-ToMarkdown/Program.cs new file mode 100644 index 0000000..6dfa8e8 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/01-ToMarkdown/Program.cs @@ -0,0 +1,45 @@ +using MuPDF.NET.Examples.Common; +using MuPDF.NET.PDF4LLM; + +namespace MuPDF.NET.Examples.PDF4LLM.ToMarkdown; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.PDF4LLM / 01-ToMarkdown"); + ToMarkdown(); + } + + /// + /// Convert a PDF to Markdown (layout off = classic RAG path). + /// + static void ToMarkdown() + { + string input = ExamplePaths.Pdf4LlmInput("columns.pdf"); + string output = ExamplePaths.Output("MuPDF.NET.PDF4LLM", "01-ToMarkdown", "columns.md"); + var check = new ResultCheck("MuPDF.NET.PDF4LLM", "01-ToMarkdown"); + + // Remember prior layout flag so we restore it for other examples in the same process. + bool prior = MuPDF4LLM.UseLayout; + try + { + // false = MuPdfRag markdown (no pymupdf-layout required). + MuPDF4LLM.SetUseLayout(false); + + string markdown = MuPDF4LLM.ToMarkdown(input, showProgress: false) ?? ""; + File.WriteAllText(output, markdown); + + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Markdown length: {markdown.Length}"); + check.Text(markdown, "columns.md"); + } + finally + { + MuPDF4LLM.SetUseLayout(prior); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET.PDF4LLM/01-ToMarkdown/README.md b/MuPDF.NET.PDF4LLM/01-ToMarkdown/README.md new file mode 100644 index 0000000..6fc4f75 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/01-ToMarkdown/README.md @@ -0,0 +1,30 @@ +# 01-ToMarkdown + +Convert a PDF to Markdown (classic RAG path with layout off). + +## Sample method + +`ToMarkdown()` in `Program.cs`. + +## Package + +- [MuPDF.NET.PDF4LLM](https://www.nuget.org/packages/MuPDF.NET.PDF4LLM) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET.PDF4LLM/columns.pdf` | +| Output | `Output/MuPDF.NET.PDF4LLM/01-ToMarkdown/columns.md` | +| Expected | `Expected/columns.md` | + +## Run + +```powershell +dotnet run --project MuPDF.NET.PDF4LLM\01-ToMarkdown +``` + +## APIs used + +- `MuPDF4LLM.SetUseLayout(false)` +- `MuPDF4LLM.ToMarkdown` diff --git a/MuPDF.NET.PDF4LLM/02-ToJsonLayout/02-ToJsonLayout.csproj b/MuPDF.NET.PDF4LLM/02-ToJsonLayout/02-ToJsonLayout.csproj new file mode 100644 index 0000000..89c70bc --- /dev/null +++ b/MuPDF.NET.PDF4LLM/02-ToJsonLayout/02-ToJsonLayout.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.PDF4LLM.ToJsonLayout + 02-ToJsonLayout + + + + + + diff --git a/MuPDF.NET.PDF4LLM/02-ToJsonLayout/Expected/columns.json b/MuPDF.NET.PDF4LLM/02-ToJsonLayout/Expected/columns.json new file mode 100644 index 0000000..589bff6 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/02-ToJsonLayout/Expected/columns.json @@ -0,0 +1 @@ +{"filename":"columns.pdf","page_count":1,"toc":[],"pages":[{"page_number":1,"width":612.0,"height":792.0,"boxes":[{"x0":132.72,"y0":27.070023,"x1":479.23526,"y1":116.303986,"boxclass":"section-header","image":null,"table":null,"textlines":[{"bbox":[132.72,27.070023,479.23526,91.07002],"spans":[{"text":"Kid’s News","bbox":[132.72,27.070023,479.23526,91.07002],"origin":[132.72,76.20001],"size":64.0,"font":"Eurostile-Demi","flags":16,"char_flags":24,"alpha":1.0,"line":0,"block":0,"dir":[1.0,0.0],"chars":null}]},{"bbox":[180.96,94.96799,431.06406,116.303986],"spans":[{"text":"Hammond Elementary School","bbox":[180.96,94.96799,431.06406,116.303986],"origin":[180.96,111.359985],"size":24.0,"font":"ZapfChancery-MediumItalic","flags":6,"char_flags":24,"alpha":1.0,"line":1,"block":1,"dir":[1.0,0.0],"chars":null}]}]},{"x0":531.36,"y0":105.62102,"x1":576.0277,"y1":113.89301,"boxclass":"text","image":null,"table":null,"textlines":[{"bbox":[531.36,105.62102,576.0277,113.89301],"spans":[{"text":"Fall 2000","bbox":[531.36,105.62102,576.0277,113.89301],"origin":[531.36,113.640015],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":0,"block":1,"dir":[1.0,0.0],"chars":null}]}]},{"x0":36.0,"y0":133.75801,"x1":180.6408,"y1":150.804,"boxclass":"section-header","image":null,"table":null,"textlines":[{"bbox":[36.0,133.75801,180.6408,150.804],"spans":[{"text":"Kids’ Right to Vote","bbox":[36.0,133.75801,180.6408,150.804],"origin":[36.0,146.88],"size":18.0,"font":"Optima-Bold","flags":16,"char_flags":24,"alpha":1.0,"line":0,"block":2,"dir":[1.0,0.0],"chars":null}]}]},{"x0":36.0,"y0":164.319,"x1":77.5071,"y1":172.851,"boxclass":"section-header","image":null,"table":null,"textlines":[{"bbox":[36.0,164.319,77.5071,172.851],"spans":[{"text":"by Lindsey","bbox":[36.0,164.319,77.5071,172.851],"origin":[36.0,170.88],"size":9.0,"font":"Optima-Bold","flags":16,"char_flags":24,"alpha":1.0,"line":0,"block":3,"dir":[1.0,0.0],"chars":null}]}]},{"x0":36.0001,"y0":182.06102,"x1":203.56744,"y1":388.3332,"boxclass":"text","image":null,"table":null,"textlines":[{"bbox":[48.0,182.06102,197.81552,192.47801],"spans":[{"text":"The election is a very important","bbox":[48.0,182.06102,197.81552,192.47801],"origin":[48.0,190.08002],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":0,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,195.22803,185.05115,205.67802],"spans":[{"text":"time of the year. Kids should be","bbox":[36.0001,195.22803,185.05115,205.67802],"origin":[36.0001,203.28003],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":1,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,208.46104,203.56744,218.87804],"spans":[{"text":"able to vote. They should be driven","bbox":[36.0001,208.46104,203.56744,218.87804],"origin":[36.0001,216.48004],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":2,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,221.66106,175.41956,232.07805],"spans":[{"text":"by their parents to the polling","bbox":[36.0001,221.66106,175.41956,232.07805],"origin":[36.0001,229.68005],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":3,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,234.86107,183.36159,243.13307],"spans":[{"text":"location to vote and then taken","bbox":[36.0001,234.86107,183.36159,243.13307],"origin":[36.0001,242.88007],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":4,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,248.02808,202.92838,258.4781],"spans":[{"text":"home by their parents. If kids voted","bbox":[36.0001,248.02808,202.92838,258.4781],"origin":[36.0001,256.08008],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":5,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,261.2281,174.81236,269.53308],"spans":[{"text":"it would make a difference in","bbox":[36.0001,261.2281,174.81236,269.53308],"origin":[36.0001,269.2801],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":6,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,274.4611,173.55838,284.8781],"spans":[{"text":"which president was elected.","bbox":[36.0001,274.4611,173.55838,284.8781],"origin":[36.0001,282.4801],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":7,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,287.6611,187.01358,298.07812],"spans":[{"text":"Adults have no more rights than","bbox":[36.0001,287.6611,187.01358,298.07812],"origin":[36.0001,295.6801],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":8,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,300.8611,191.95808,311.27814],"spans":[{"text":"kids, and everybody should have","bbox":[36.0001,300.8611,191.95808,311.27814],"origin":[36.0001,308.88013],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":9,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,314.02814,174.82007,324.47815],"spans":[{"text":"their own right to vote for the","bbox":[36.0001,314.02814,174.82007,324.47815],"origin":[36.0001,322.08014],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":10,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,327.22815,187.67134,337.67816],"spans":[{"text":"president. If kids could vote, the","bbox":[36.0001,327.22815,187.67134,337.67816],"origin":[36.0001,335.28015],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":11,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,340.46115,192.92278,350.87817],"spans":[{"text":"law should be that they can’t talk","bbox":[36.0001,340.46115,192.92278,350.87817],"origin":[36.0001,348.48016],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":12,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,353.66116,196.79257,364.0782],"spans":[{"text":"about their votes, and they should","bbox":[36.0001,353.66116,196.79257,364.0782],"origin":[36.0001,361.68018],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":13,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,366.86118,171.75659,375.13318],"spans":[{"text":"have to know a lot about the","bbox":[36.0001,366.86118,171.75659,375.13318],"origin":[36.0001,374.8802],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":14,"block":4,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,380.0282,188.58435,388.3332],"spans":[{"text":"candidate to vote for him or her.","bbox":[36.0001,380.0282,188.58435,388.3332],"origin":[36.0001,388.0802],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":15,"block":4,"dir":[1.0,0.0],"chars":null}]}]},{"x0":36.0001,"y0":393.2612,"x1":195.35599,"y1":454.33325,"boxclass":"text","image":null,"table":null,"textlines":[{"bbox":[48.0,393.2612,195.35599,402.91922],"spans":[{"text":"When the next election comes,","bbox":[48.0,393.2612,195.35599,402.91922],"origin":[48.0,401.2802],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":0,"block":5,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,406.4612,194.41545,414.73322],"spans":[{"text":"kids older than 10 should be able","bbox":[36.0001,406.4612,194.41545,414.73322],"origin":[36.0001,414.48022],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":1,"block":5,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,419.66122,185.81459,427.93323],"spans":[{"text":"to vote. There are some reasons","bbox":[36.0001,419.66122,185.81459,427.93323],"origin":[36.0001,427.68024],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":2,"block":5,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,432.86124,179.83606,443.27826],"spans":[{"text":"why kids can’t vote now. They","bbox":[36.0001,432.86124,179.83606,443.27826],"origin":[36.0001,440.88025],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":3,"block":5,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,446.06125,174.63197,454.33325],"spans":[{"text":"don’t have the education and","bbox":[36.0001,446.06125,174.63197,454.33325],"origin":[36.0001,454.08026],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":4,"block":5,"dir":[1.0,0.0],"chars":null}]}]},{"x0":58.35001,"y0":461.5,"x1":177.0,"y1":528.0,"boxclass":"picture","image":null,"table":null,"textlines":null},{"x0":222.00018,"y0":142.54816,"x1":387.73282,"y1":298.1983,"boxclass":"text","image":null,"table":null,"textlines":[{"bbox":[222.00018,142.54816,387.73282,152.99815],"spans":[{"text":"information on our political system","bbox":[222.00018,142.54816,387.73282,152.99815],"origin":[222.00018,150.60016],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":0,"block":6,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,155.78117,372.40985,166.19817],"spans":[{"text":"to be able to make a good deci-","bbox":[222.00018,155.78117,372.40985,166.19817],"origin":[222.00018,163.80017],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":1,"block":6,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,168.98119,368.52133,177.25319],"spans":[{"text":"sion. Kids don’t understand the","bbox":[222.00018,168.98119,368.52133,177.25319],"origin":[222.00018,177.00018],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":2,"block":6,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,182.1482,378.5654,190.4532],"spans":[{"text":"views of the candidates on issues","bbox":[222.00018,182.1482,378.5654,190.4532],"origin":[222.00018,190.2002],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":3,"block":6,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,195.38121,373.5176,205.7982],"spans":[{"text":"such as the country’s health sys-","bbox":[222.00018,195.38121,373.5176,205.7982],"origin":[222.00018,203.4002],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":4,"block":6,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,208.54822,374.22595,218.99821],"spans":[{"text":"tem, or welfare system, or taxes.","bbox":[222.00018,208.54822,374.22595,218.99821],"origin":[222.00018,216.60022],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":5,"block":6,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,221.78123,377.31583,232.19823],"spans":[{"text":"They will easily be persuaded by","bbox":[222.00018,221.78123,377.31583,232.19823],"origin":[222.00018,229.80023],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":6,"block":6,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,234.98125,377.7712,245.39824],"spans":[{"text":"other kids’ opinions about whom","bbox":[222.00018,234.98125,377.7712,245.39824],"origin":[222.00018,243.00024],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":7,"block":6,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,248.14825,381.33633,258.59827],"spans":[{"text":"they are voting for. Kids would be","bbox":[222.00018,248.14825,381.33633,258.59827],"origin":[222.00018,256.20026],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":8,"block":6,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,261.38126,384.06876,271.79828],"spans":[{"text":"voting without knowing what they","bbox":[222.00018,261.38126,384.06876,271.79828],"origin":[222.00018,269.40027],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":9,"block":6,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,274.54828,372.76074,284.9983],"spans":[{"text":"are really voting for. They might","bbox":[222.00018,274.54828,372.76074,284.9983],"origin":[222.00018,282.60028],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":10,"block":6,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,287.78128,352.19293,298.1983],"spans":[{"text":"copy someone else’s ballot.","bbox":[222.00018,287.78128,352.19293,298.1983],"origin":[222.00018,295.8003],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":11,"block":6,"dir":[1.0,0.0],"chars":null}]}]},{"x0":222.00018,"y0":300.9483,"x1":389.58203,"y1":522.5985,"boxclass":"text","image":null,"table":null,"textlines":[{"bbox":[234.00008,300.9483,377.11676,311.39832],"spans":[{"text":"The law should say that if kids","bbox":[234.00008,300.9483,377.11676,311.39832],"origin":[234.00008,309.0003],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":0,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,314.1813,362.58585,324.59833],"spans":[{"text":"are ever allowed to vote, they","bbox":[222.00018,314.1813,362.58585,324.59833],"origin":[222.00018,322.20032],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":1,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,327.38132,377.70743,337.79834],"spans":[{"text":"should be supervised. Kids aren’t","bbox":[222.00018,327.38132,377.70743,337.79834],"origin":[222.00018,335.40033],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":2,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,340.54834,372.39236,350.99835],"spans":[{"text":"very educated in politics. If kids","bbox":[222.00018,340.54834,372.39236,350.99835],"origin":[222.00018,348.60034],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":3,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,353.78134,388.92307,363.43936],"spans":[{"text":"knew more about the candidates, it","bbox":[222.00018,353.78134,388.92307,363.43936],"origin":[222.00018,361.80035],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":4,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,366.94836,368.7733,375.25336],"spans":[{"text":"would be fine for them to vote.","bbox":[222.00018,366.94836,368.7733,375.25336],"origin":[222.00018,375.00037],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":5,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,380.18137,382.2241,390.5984],"spans":[{"text":"Although all adults are allowed to","bbox":[222.00018,380.18137,382.2241,390.5984],"origin":[222.00018,388.20038],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":6,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,393.38138,366.8857,403.0394],"spans":[{"text":"vote, some adults vote without","bbox":[222.00018,393.38138,366.8857,403.0394],"origin":[222.00018,401.4004],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":7,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,406.5814,384.06104,416.9984],"spans":[{"text":"knowing how the candidates view","bbox":[222.00018,406.5814,384.06104,416.9984],"origin":[222.00018,414.6004],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":8,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,419.7814,380.94366,430.19843],"spans":[{"text":"the important issues. These adults","bbox":[222.00018,419.7814,380.94366,430.19843],"origin":[222.00018,427.8004],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":9,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,432.9814,369.96567,443.39844],"spans":[{"text":"are voting in the same way that","bbox":[222.00018,432.9814,369.96567,443.39844],"origin":[222.00018,441.00043],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":10,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,446.14844,382.80057,455.83945],"spans":[{"text":"kids would. If these adults do this,","bbox":[222.00018,446.14844,382.80057,455.83945],"origin":[222.00018,454.20044],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":11,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,459.24945,382.63986,469.79846],"spans":[{"text":"then why can’t children vote? The","bbox":[222.00018,459.24945,382.63986,469.79846],"origin":[222.00018,467.40045],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":12,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,472.58145,363.26443,482.99847],"spans":[{"text":"real point to make here is that","bbox":[222.00018,472.58145,363.26443,482.99847],"origin":[222.00018,480.60046],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":13,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,485.78146,389.58203,494.05347],"spans":[{"text":"these adults should not vote at all. I","bbox":[222.00018,485.78146,389.58203,494.05347],"origin":[222.00018,493.80048],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":14,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,498.98148,366.96368,507.25348],"spans":[{"text":"think that kids should have the","bbox":[222.00018,498.98148,366.96368,507.25348],"origin":[222.00018,507.0005],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":15,"block":7,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,512.1815,281.9315,522.5985],"spans":[{"text":"right to vote.","bbox":[222.00018,512.1815,281.9315,522.5985],"origin":[222.00018,520.2005],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":16,"block":7,"dir":[1.0,0.0],"chars":null}]}]},{"x0":408.0,"y0":134.47798,"x1":507.0414,"y1":151.54198,"boxclass":"section-header","image":null,"table":null,"textlines":[{"bbox":[408.0,134.47798,507.0414,151.54198],"spans":[{"text":"Harry Potter","bbox":[408.0,134.47798,507.0414,151.54198],"origin":[408.0,147.59998],"size":18.0,"font":"Optima-Bold","flags":16,"char_flags":24,"alpha":1.0,"line":0,"block":15,"dir":[1.0,0.0],"chars":null}]}]},{"x0":408.0,"y0":165.03897,"x1":443.0118,"y1":173.57097,"boxclass":"section-header","image":null,"table":null,"textlines":[{"bbox":[408.0,165.03897,443.0118,173.57097],"spans":[{"text":"by Bryan","bbox":[408.0,165.03897,443.0118,173.57097],"origin":[408.0,171.59998],"size":9.0,"font":"Optima-Bold","flags":16,"char_flags":24,"alpha":1.0,"line":0,"block":16,"dir":[1.0,0.0],"chars":null}]}]},{"x0":408.0001,"y0":182.74799,"x1":573.7782,"y1":404.3982,"boxclass":"text","image":null,"table":null,"textlines":[{"bbox":[420.0,182.74799,569.4199,193.19798],"spans":[{"text":"Harry Potter is a good book for","bbox":[420.0,182.74799,569.4199,193.19798],"origin":[420.0,190.79999],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":0,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,195.981,551.31384,206.398],"spans":[{"text":"advanced readers. It is a story","bbox":[408.0001,195.981,551.31384,206.398],"origin":[408.0001,204.0],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":1,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,209.18102,564.9164,219.598],"spans":[{"text":"about a boy who lost his parents","bbox":[408.0001,209.18102,564.9164,219.598],"origin":[408.0001,217.20001],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":2,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,222.38103,571.1194,230.65303],"spans":[{"text":"and was delivered to his aunt and","bbox":[408.0001,222.38103,571.1194,230.65303],"origin":[408.0001,230.40002],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":3,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,235.44904,564.3114,245.99803],"spans":[{"text":"uncle and his cousin. Since they","bbox":[408.0001,235.44904,564.3114,245.99803],"origin":[408.0001,243.60004],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":4,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,248.78105,572.7209,259.19806],"spans":[{"text":"hate him so much, they make him","bbox":[408.0001,248.78105,572.7209,259.19806],"origin":[408.0001,256.80005],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":5,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,261.84906,550.9596,272.39807],"spans":[{"text":"work hard. One day he gets a","bbox":[408.0001,261.84906,550.9596,272.39807],"origin":[408.0001,270.00006],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":6,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,275.18106,538.15344,285.59808],"spans":[{"text":"letter saying he can go to a","bbox":[408.0001,275.18106,538.15344,285.59808],"origin":[408.0001,283.20007],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":7,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,288.38107,559.9191,298.7981],"spans":[{"text":"wizarding school, but his uncle","bbox":[408.0001,288.38107,559.9191,298.7981],"origin":[408.0001,296.4001],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":8,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,301.5811,562.7989,311.9981],"spans":[{"text":"won’t let him go. Then a wizard","bbox":[408.0001,301.5811,562.7989,311.9981],"origin":[408.0001,309.6001],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":9,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,314.7811,553.40594,325.19812],"spans":[{"text":"comes and takes Harry school","bbox":[408.0001,314.7811,553.40594,325.19812],"origin":[408.0001,322.8001],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":10,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,327.9811,568.0745,338.39813],"spans":[{"text":"shopping. Next, Harry is taken to","bbox":[408.0001,327.9811,568.0745,338.39813],"origin":[408.0001,336.00012],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":11,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,341.14813,573.7782,351.59814],"spans":[{"text":"a magic train to a school of witch-","bbox":[408.0001,341.14813,573.7782,351.59814],"origin":[408.0001,349.20013],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":12,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,354.34814,573.7781,364.79816],"spans":[{"text":"craft and wizardry and has a lot of","bbox":[408.0001,354.34814,573.7781,364.79816],"origin":[408.0001,362.40015],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":13,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,367.44916,566.6148,375.85315],"spans":[{"text":"adventures there. One adventure","bbox":[408.0001,367.44916,566.6148,375.85315],"origin":[408.0001,375.60016],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":14,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,380.74817,554.02747,391.19818],"spans":[{"text":"is that he finds out the story of","bbox":[408.0001,380.74817,554.02747,391.19818],"origin":[408.0001,388.80017],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":15,"block":17,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,393.98117,511.69617,404.3982],"spans":[{"text":"how his parents died.","bbox":[408.0001,393.98117,511.69617,404.3982],"origin":[408.0001,402.00018],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":16,"block":17,"dir":[1.0,0.0],"chars":null}]}]},{"x0":408.0001,"y0":407.1482,"x1":574.3269,"y1":523.1983,"boxclass":"text","image":null,"table":null,"textlines":[{"bbox":[420.0,407.1482,570.17096,417.5982],"spans":[{"text":"After interviewing classmates, I","bbox":[420.0,407.1482,570.17096,417.5982],"origin":[420.0,415.2002],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":0,"block":18,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,420.3482,570.7882,430.79822],"spans":[{"text":"found some people like and don’t","bbox":[408.0001,420.3482,570.7882,430.79822],"origin":[408.0001,428.4002],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":1,"block":18,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,433.5812,554.0725,443.99823],"spans":[{"text":"like Harry Potter. Nick says he","bbox":[408.0001,433.5812,554.0725,443.99823],"origin":[408.0001,441.60022],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":2,"block":18,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,446.78122,550.65155,457.19824],"spans":[{"text":"likes Harry Potter because it’s","bbox":[408.0001,446.78122,550.65155,457.19824],"origin":[408.0001,454.80023],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":3,"block":18,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,459.94824,567.07135,470.39825],"spans":[{"text":"creative and funny. Damani likes","bbox":[408.0001,459.94824,567.07135,470.39825],"origin":[408.0001,468.00024],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":4,"block":18,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,473.18124,569.52216,481.45325],"spans":[{"text":"it because it takes a while to read","bbox":[408.0001,473.18124,569.52216,481.45325],"origin":[408.0001,481.20026],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":5,"block":18,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,486.38126,570.422,496.79828],"spans":[{"text":"and you think something bad will","bbox":[408.0001,486.38126,570.422,496.79828],"origin":[408.0001,494.40027],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":6,"block":18,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,499.58127,574.3269,509.9983],"spans":[{"text":"happen but it doesn’t. Reid says it","bbox":[408.0001,499.58127,574.3269,509.9983],"origin":[408.0001,507.60028],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":7,"block":18,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.0001,512.7813,565.6007,523.1983],"spans":[{"text":"is advanced because it’s so long.","bbox":[408.0001,512.7813,565.6007,523.1983],"origin":[408.0001,520.8003],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":8,"block":18,"dir":[1.0,0.0],"chars":null}]}]},{"x0":36.0,"y0":543.318,"x1":223.08658,"y1":556.854,"boxclass":"section-header","image":null,"table":null,"textlines":[{"bbox":[36.0,543.318,223.08658,556.854],"spans":[{"text":"From a Kid to a Redskin","bbox":[36.0,543.318,223.08658,556.854],"origin":[36.0,556.44],"size":18.0,"font":"Optima-Bold","flags":16,"char_flags":24,"alpha":1.0,"line":0,"block":8,"dir":[1.0,0.0],"chars":null}]}]},{"x0":222.00018,"y0":563.7485,"x1":363.26227,"y1":587.3985,"boxclass":"text","image":null,"table":null,"textlines":[{"bbox":[222.00018,563.7485,363.26227,572.05347],"spans":[{"text":"to see if I could ask him a few","bbox":[222.00018,563.7485,363.26227,572.05347],"origin":[222.00018,571.8005],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":0,"block":11,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,576.9815,269.71597,587.3985],"spans":[{"text":"questions.","bbox":[222.00018,576.9815,269.71597,587.3985],"origin":[222.00018,585.0005],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":1,"block":11,"dir":[1.0,0.0],"chars":null}]}]},{"x0":36.0,"y0":573.879,"x1":75.5154,"y1":582.411,"boxclass":"section-header","image":null,"table":null,"textlines":[{"bbox":[36.0,573.879,75.5154,582.411],"spans":[{"text":"by Patrick","bbox":[36.0,573.879,75.5154,582.411],"origin":[36.0,580.44],"size":9.0,"font":"Optima-Bold","flags":16,"char_flags":24,"alpha":1.0,"line":0,"block":9,"dir":[1.0,0.0],"chars":null}]}]},{"x0":222.00018,"y0":590.14844,"x1":384.87302,"y1":756.85345,"boxclass":"text","image":null,"table":null,"textlines":[{"bbox":[234.00008,590.14844,375.26654,600.59845],"spans":[{"text":"He told me how his father got","bbox":[234.00008,590.14844,375.26654,600.59845],"origin":[234.00008,598.20044],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":0,"block":12,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,603.34845,384.67706,611.65344],"spans":[{"text":"him interested in football when he","bbox":[222.00018,603.34845,384.67706,611.65344],"origin":[222.00018,611.40045],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":1,"block":12,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,616.5815,361.86194,626.9985],"spans":[{"text":"was in the 9th grade. Moore’s","bbox":[222.00018,616.5815,361.86194,626.9985],"origin":[222.00018,624.60046],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":2,"block":12,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,629.7485,384.87302,640.1985],"spans":[{"text":"greatest teacher in life is his father.","bbox":[222.00018,629.7485,384.87302,640.1985],"origin":[222.00018,637.8005],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":3,"block":12,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,642.9485,384.6627,653.3985],"spans":[{"text":"Moore played offense and defense","bbox":[222.00018,642.9485,384.6627,653.3985],"origin":[222.00018,651.0005],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":4,"block":12,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,656.04944,378.56436,666.59845],"spans":[{"text":"at Fayette County High School in","bbox":[222.00018,656.04944,378.56436,666.59845],"origin":[222.00018,664.20044],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":5,"block":12,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,669.3815,377.3093,679.79846],"spans":[{"text":"Alabama. He attended college at","bbox":[222.00018,669.3815,377.3093,679.79846],"origin":[222.00018,677.40045],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":6,"block":12,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,682.54846,379.11435,692.9985],"spans":[{"text":"Alabama for three years and then","bbox":[222.00018,682.54846,379.11435,692.9985],"origin":[222.00018,690.60046],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":7,"block":12,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,695.6494,379.17813,706.1984],"spans":[{"text":"transferred to Troy State. In 2000,","bbox":[222.00018,695.6494,379.17813,706.1984],"origin":[222.00018,703.8004],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":8,"block":12,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,708.98145,373.0149,718.6394],"spans":[{"text":"“Mookie,” as he is known to his","bbox":[222.00018,708.98145,373.0149,718.6394],"origin":[222.00018,717.0004],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":9,"block":12,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,722.14844,374.86942,732.59845],"spans":[{"text":"fellow players, signed a contract","bbox":[222.00018,722.14844,374.86942,732.59845],"origin":[222.00018,730.20044],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":10,"block":12,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,735.3815,367.52908,745.79846],"spans":[{"text":"with the Washington Redskins.","bbox":[222.00018,735.3815,367.52908,745.79846],"origin":[222.00018,743.40045],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":11,"block":12,"dir":[1.0,0.0],"chars":null}]},{"bbox":[222.00018,748.54846,381.54425,756.85345],"spans":[{"text":"He’s an offensive lineman and his","bbox":[222.00018,748.54846,381.54425,756.85345],"origin":[222.00018,756.60046],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":12,"block":12,"dir":[1.0,0.0],"chars":null}]}]},{"x0":36.0001,"y0":590.748,"x1":202.32996,"y1":759.59796,"boxclass":"text","image":null,"table":null,"textlines":[{"bbox":[48.0,590.748,198.47885,601.198],"spans":[{"text":"I stood toe to toe with a giant of","bbox":[48.0,590.748,198.47885,601.198],"origin":[48.0,598.8],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":0,"block":10,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,603.981,169.31346,614.398],"spans":[{"text":"a man. It’s amazing how big","bbox":[36.0001,603.981,169.31346,614.398],"origin":[36.0001,612.0],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":1,"block":10,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,617.148,189.47318,627.598],"spans":[{"text":"professional football players are.","bbox":[36.0001,617.148,189.47318,627.598],"origin":[36.0001,625.2],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":2,"block":10,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,630.249,188.87146,640.79803],"spans":[{"text":"On Saturday afternoon, Oct. 28,","bbox":[36.0001,630.249,188.87146,640.79803],"origin":[36.0001,638.4],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":3,"block":10,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,643.581,188.8429,653.23895],"spans":[{"text":"2000, I went to Redskins Park in","bbox":[36.0001,643.581,188.8429,653.23895],"origin":[36.0001,651.6],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":4,"block":10,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,656.748,180.81067,667.198],"spans":[{"text":"Ashburn, Va., with the hope of","bbox":[36.0001,656.748,180.81067,667.198],"origin":[36.0001,664.8],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":5,"block":10,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,669.948,185.21837,680.398],"spans":[{"text":"interviewing one of the players.","bbox":[36.0001,669.948,185.21837,680.398],"origin":[36.0001,678.0],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":6,"block":10,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,683.14795,202.32996,693.59796],"spans":[{"text":"While waiting in the parking lot for","bbox":[36.0001,683.14795,202.32996,693.59796],"origin":[36.0001,691.19995],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":7,"block":10,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,696.381,199.26974,706.798],"spans":[{"text":"the players to leave practice, I was","bbox":[36.0001,696.381,199.26974,706.798],"origin":[36.0001,704.39996],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":8,"block":10,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,709.581,172.34949,719.998],"spans":[{"text":"lucky enough to see Michael","bbox":[36.0001,709.581,172.34949,719.998],"origin":[36.0001,717.6],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":9,"block":10,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,722.748,179.12436,732.43896],"spans":[{"text":"Moore, all 6 foot 3 inches tall,","bbox":[36.0001,722.748,179.12436,732.43896],"origin":[36.0001,730.8],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":10,"block":10,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,735.981,177.2555,746.398],"spans":[{"text":"weighing 320 pounds. He ap-","bbox":[36.0001,735.981,177.2555,746.398],"origin":[36.0001,744.0],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":11,"block":10,"dir":[1.0,0.0],"chars":null}]},{"bbox":[36.0001,749.18097,191.91296,759.59796],"spans":[{"text":"proached his car and I went over","bbox":[36.0001,749.18097,191.91296,759.59796],"origin":[36.0001,757.19995],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":12,"block":10,"dir":[1.0,0.0],"chars":null}]}]},{"x0":408.00027,"y0":551.78156,"x1":473.43048,"y1":560.0535,"boxclass":"section-header","image":null,"table":null,"textlines":[{"bbox":[408.00027,551.78156,473.43048,560.0535],"spans":[{"text":"number is 66.","bbox":[408.00027,551.78156,473.43048,560.0535],"origin":[408.00027,559.80054],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":0,"block":13,"dir":[1.0,0.0],"chars":null}]}]},{"x0":408.00027,"y0":564.98157,"x1":569.39465,"y1":692.0535,"boxclass":"text","image":null,"table":null,"textlines":[{"bbox":[420.00018,564.98157,562.4755,575.39856],"spans":[{"text":"To make my day complete, he","bbox":[420.00018,564.98157,562.4755,575.39856],"origin":[420.00018,573.00055],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":0,"block":14,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.00027,578.14856,560.8685,588.5986],"spans":[{"text":"offered to give me his autograph","bbox":[408.00027,578.14856,560.8685,588.5986],"origin":[408.00027,586.20056],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":1,"block":14,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.00027,591.38153,562.10913,601.7985],"spans":[{"text":"and he shook my hand. I wished","bbox":[408.00027,591.38153,562.10913,601.7985],"origin":[408.00027,599.4005],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":2,"block":14,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.00027,604.58154,546.806,614.99854],"spans":[{"text":"him luck on the game that he","bbox":[408.00027,604.58154,546.806,614.99854],"origin":[408.00027,612.6005],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":3,"block":14,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.00027,617.78156,548.05133,628.19855],"spans":[{"text":"would be playing on Monday","bbox":[408.00027,617.78156,548.05133,628.19855],"origin":[408.00027,625.80054],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":4,"block":14,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.00027,630.98157,569.39465,641.39856],"spans":[{"text":"Night Football. He got into his car","bbox":[408.00027,630.98157,569.39465,641.39856],"origin":[408.00027,639.00055],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":5,"block":14,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.00027,644.14856,566.1099,654.5986],"spans":[{"text":"and left the parking lot. I couldn’t","bbox":[408.00027,644.14856,566.1099,654.5986],"origin":[408.00027,652.20056],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":6,"block":14,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.00027,657.38153,550.4481,667.7985],"spans":[{"text":"stop smiling. Just think – I was","bbox":[408.00027,657.38153,550.4481,667.7985],"origin":[408.00027,665.4005],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":7,"block":14,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.00027,670.58154,540.10144,680.99854],"spans":[{"text":"lucky enough to interview a","bbox":[408.00027,670.58154,540.10144,680.99854],"origin":[408.00027,678.6005],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":8,"block":14,"dir":[1.0,0.0],"chars":null}]},{"bbox":[408.00027,683.78156,514.4012,692.0535],"spans":[{"text":"Redskin. What a thrill!","bbox":[408.00027,683.78156,514.4012,692.0535],"origin":[408.00027,691.80054],"size":11.0,"font":"Optima","flags":0,"char_flags":16,"alpha":1.0,"line":9,"block":14,"dir":[1.0,0.0],"chars":null}]}]},{"x0":416.55,"y0":698.05,"x1":576.0,"y1":756.0,"boxclass":"picture","image":null,"table":null,"textlines":null}],"full_ocred":false,"text_ocred":false,"fulltext":[{"Xref":0,"Number":0,"Type":0,"Bbox":[132.72,27.070023,479.23526,91.07002],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[132.72,76.20001],"Bbox":[132.72,27.070023,479.23526,91.07002],"Text":"Kid’s News","Size":64.0,"Flags":16.0,"CharFlags":24,"Bidi":0,"Font":"Eurostile-Demi","Color":1611084,"Alpha":255,"Asc":0.75,"Desc":-0.227}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[132.72,27.070023,479.23526,91.07002]}],"IsRect":true},{"Xref":0,"Number":2,"Type":0,"Bbox":[180.96,94.96799,576.0277,116.303986],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[531.36,113.640015],"Bbox":[531.36,105.62102,576.0277,113.89301],"Text":"Fall 2000","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[531.36,105.62102,576.0277,113.89301]},{"Spans":[{"Chars":null,"Origin":[180.96,111.359985],"Bbox":[180.96,94.96799,431.06406,116.303986],"Text":"Hammond Elementary School","Size":24.0,"Flags":6.0,"CharFlags":24,"Bidi":0,"Font":"ZapfChancery-MediumItalic","Color":16764968,"Alpha":255,"Asc":0.714,"Desc":-0.314}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[180.96,94.96799,431.06406,116.303986]}],"IsRect":true},{"Xref":0,"Number":4,"Type":0,"Bbox":[36.0,133.75801,180.6408,150.804],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[36.0,146.88],"Bbox":[36.0,133.75801,180.6408,150.804],"Text":"Kids’ Right to Vote","Size":18.0,"Flags":16.0,"CharFlags":24,"Bidi":0,"Font":"Optima-Bold","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0,133.75801,180.6408,150.804]}],"IsRect":true},{"Xref":0,"Number":5,"Type":0,"Bbox":[36.0,164.319,77.5071,172.851],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[36.0,170.88],"Bbox":[36.0,164.319,77.5071,172.851],"Text":"by Lindsey","Size":9.0,"Flags":16.0,"CharFlags":24,"Bidi":0,"Font":"Optima-Bold","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0,164.319,77.5071,172.851]}],"IsRect":true},{"Xref":0,"Number":6,"Type":0,"Bbox":[36.0001,182.06102,203.56744,388.3332],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[48.0,190.08002],"Bbox":[48.0,182.06102,197.81552,192.47801],"Text":"The election is a very important","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[48.0,182.06102,197.81552,192.47801]},{"Spans":[{"Chars":null,"Origin":[36.0001,203.28003],"Bbox":[36.0001,195.22803,185.05115,205.67802],"Text":"time of the year. Kids should be","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,195.22803,185.05115,205.67802]},{"Spans":[{"Chars":null,"Origin":[36.0001,216.48004],"Bbox":[36.0001,208.46104,203.56744,218.87804],"Text":"able to vote. They should be driven","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,208.46104,203.56744,218.87804]},{"Spans":[{"Chars":null,"Origin":[36.0001,229.68005],"Bbox":[36.0001,221.66106,175.41956,232.07805],"Text":"by their parents to the polling","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,221.66106,175.41956,232.07805]},{"Spans":[{"Chars":null,"Origin":[36.0001,242.88007],"Bbox":[36.0001,234.86107,183.36159,243.13307],"Text":"location to vote and then taken","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,234.86107,183.36159,243.13307]},{"Spans":[{"Chars":null,"Origin":[36.0001,256.08008],"Bbox":[36.0001,248.02808,202.92838,258.4781],"Text":"home by their parents. If kids voted","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,248.02808,202.92838,258.4781]},{"Spans":[{"Chars":null,"Origin":[36.0001,269.2801],"Bbox":[36.0001,261.2281,174.81236,269.53308],"Text":"it would make a difference in","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,261.2281,174.81236,269.53308]},{"Spans":[{"Chars":null,"Origin":[36.0001,282.4801],"Bbox":[36.0001,274.4611,173.55838,284.8781],"Text":"which president was elected.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,274.4611,173.55838,284.8781]},{"Spans":[{"Chars":null,"Origin":[36.0001,295.6801],"Bbox":[36.0001,287.6611,187.01358,298.07812],"Text":"Adults have no more rights than","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,287.6611,187.01358,298.07812]},{"Spans":[{"Chars":null,"Origin":[36.0001,308.88013],"Bbox":[36.0001,300.8611,191.95808,311.27814],"Text":"kids, and everybody should have","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,300.8611,191.95808,311.27814]},{"Spans":[{"Chars":null,"Origin":[36.0001,322.08014],"Bbox":[36.0001,314.02814,174.82007,324.47815],"Text":"their own right to vote for the","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,314.02814,174.82007,324.47815]},{"Spans":[{"Chars":null,"Origin":[36.0001,335.28015],"Bbox":[36.0001,327.22815,187.67134,337.67816],"Text":"president. If kids could vote, the","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,327.22815,187.67134,337.67816]},{"Spans":[{"Chars":null,"Origin":[36.0001,348.48016],"Bbox":[36.0001,340.46115,192.92278,350.87817],"Text":"law should be that they can’t talk","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,340.46115,192.92278,350.87817]},{"Spans":[{"Chars":null,"Origin":[36.0001,361.68018],"Bbox":[36.0001,353.66116,196.79257,364.0782],"Text":"about their votes, and they should","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,353.66116,196.79257,364.0782]},{"Spans":[{"Chars":null,"Origin":[36.0001,374.8802],"Bbox":[36.0001,366.86118,171.75659,375.13318],"Text":"have to know a lot about the","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,366.86118,171.75659,375.13318]},{"Spans":[{"Chars":null,"Origin":[36.0001,388.0802],"Bbox":[36.0001,380.0282,188.58435,388.3332],"Text":"candidate to vote for him or her.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,380.0282,188.58435,388.3332]}],"IsRect":true},{"Xref":0,"Number":7,"Type":0,"Bbox":[36.0001,393.2612,195.35599,454.33325],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[48.0,401.2802],"Bbox":[48.0,393.2612,195.35599,402.91922],"Text":"When the next election comes,","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[48.0,393.2612,195.35599,402.91922]},{"Spans":[{"Chars":null,"Origin":[36.0001,414.48022],"Bbox":[36.0001,406.4612,194.41545,414.73322],"Text":"kids older than 10 should be able","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,406.4612,194.41545,414.73322]},{"Spans":[{"Chars":null,"Origin":[36.0001,427.68024],"Bbox":[36.0001,419.66122,185.81459,427.93323],"Text":"to vote. There are some reasons","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,419.66122,185.81459,427.93323]},{"Spans":[{"Chars":null,"Origin":[36.0001,440.88025],"Bbox":[36.0001,432.86124,179.83606,443.27826],"Text":"why kids can’t vote now. They","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,432.86124,179.83606,443.27826]},{"Spans":[{"Chars":null,"Origin":[36.0001,454.08026],"Bbox":[36.0001,446.06125,174.63197,454.33325],"Text":"don’t have the education and","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,446.06125,174.63197,454.33325]}],"IsRect":true},{"Xref":0,"Number":8,"Type":0,"Bbox":[222.00018,142.54816,387.73282,298.1983],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[222.00018,150.60016],"Bbox":[222.00018,142.54816,387.73282,152.99815],"Text":"information on our political system","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,142.54816,387.73282,152.99815]},{"Spans":[{"Chars":null,"Origin":[222.00018,163.80017],"Bbox":[222.00018,155.78117,372.40985,166.19817],"Text":"to be able to make a good deci-","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,155.78117,372.40985,166.19817]},{"Spans":[{"Chars":null,"Origin":[222.00018,177.00018],"Bbox":[222.00018,168.98119,368.52133,177.25319],"Text":"sion. Kids don’t understand the","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,168.98119,368.52133,177.25319]},{"Spans":[{"Chars":null,"Origin":[222.00018,190.2002],"Bbox":[222.00018,182.1482,378.5654,190.4532],"Text":"views of the candidates on issues","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,182.1482,378.5654,190.4532]},{"Spans":[{"Chars":null,"Origin":[222.00018,203.4002],"Bbox":[222.00018,195.38121,373.5176,205.7982],"Text":"such as the country’s health sys-","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,195.38121,373.5176,205.7982]},{"Spans":[{"Chars":null,"Origin":[222.00018,216.60022],"Bbox":[222.00018,208.54822,374.22595,218.99821],"Text":"tem, or welfare system, or taxes.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,208.54822,374.22595,218.99821]},{"Spans":[{"Chars":null,"Origin":[222.00018,229.80023],"Bbox":[222.00018,221.78123,377.31583,232.19823],"Text":"They will easily be persuaded by","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,221.78123,377.31583,232.19823]},{"Spans":[{"Chars":null,"Origin":[222.00018,243.00024],"Bbox":[222.00018,234.98125,377.7712,245.39824],"Text":"other kids’ opinions about whom","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,234.98125,377.7712,245.39824]},{"Spans":[{"Chars":null,"Origin":[222.00018,256.20026],"Bbox":[222.00018,248.14825,381.33633,258.59827],"Text":"they are voting for. Kids would be","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,248.14825,381.33633,258.59827]},{"Spans":[{"Chars":null,"Origin":[222.00018,269.40027],"Bbox":[222.00018,261.38126,384.06876,271.79828],"Text":"voting without knowing what they","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,261.38126,384.06876,271.79828]},{"Spans":[{"Chars":null,"Origin":[222.00018,282.60028],"Bbox":[222.00018,274.54828,372.76074,284.9983],"Text":"are really voting for. They might","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,274.54828,372.76074,284.9983]},{"Spans":[{"Chars":null,"Origin":[222.00018,295.8003],"Bbox":[222.00018,287.78128,352.19293,298.1983],"Text":"copy someone else’s ballot.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,287.78128,352.19293,298.1983]}],"IsRect":true},{"Xref":0,"Number":9,"Type":0,"Bbox":[222.00018,300.9483,389.58203,522.5985],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[234.00008,309.0003],"Bbox":[234.00008,300.9483,377.11676,311.39832],"Text":"The law should say that if kids","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[234.00008,300.9483,377.11676,311.39832]},{"Spans":[{"Chars":null,"Origin":[222.00018,322.20032],"Bbox":[222.00018,314.1813,362.58585,324.59833],"Text":"are ever allowed to vote, they","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,314.1813,362.58585,324.59833]},{"Spans":[{"Chars":null,"Origin":[222.00018,335.40033],"Bbox":[222.00018,327.38132,377.70743,337.79834],"Text":"should be supervised. Kids aren’t","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,327.38132,377.70743,337.79834]},{"Spans":[{"Chars":null,"Origin":[222.00018,348.60034],"Bbox":[222.00018,340.54834,372.39236,350.99835],"Text":"very educated in politics. If kids","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,340.54834,372.39236,350.99835]},{"Spans":[{"Chars":null,"Origin":[222.00018,361.80035],"Bbox":[222.00018,353.78134,388.92307,363.43936],"Text":"knew more about the candidates, it","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,353.78134,388.92307,363.43936]},{"Spans":[{"Chars":null,"Origin":[222.00018,375.00037],"Bbox":[222.00018,366.94836,368.7733,375.25336],"Text":"would be fine for them to vote.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,366.94836,368.7733,375.25336]},{"Spans":[{"Chars":null,"Origin":[222.00018,388.20038],"Bbox":[222.00018,380.18137,382.2241,390.5984],"Text":"Although all adults are allowed to","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,380.18137,382.2241,390.5984]},{"Spans":[{"Chars":null,"Origin":[222.00018,401.4004],"Bbox":[222.00018,393.38138,366.8857,403.0394],"Text":"vote, some adults vote without","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,393.38138,366.8857,403.0394]},{"Spans":[{"Chars":null,"Origin":[222.00018,414.6004],"Bbox":[222.00018,406.5814,384.06104,416.9984],"Text":"knowing how the candidates view","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,406.5814,384.06104,416.9984]},{"Spans":[{"Chars":null,"Origin":[222.00018,427.8004],"Bbox":[222.00018,419.7814,380.94366,430.19843],"Text":"the important issues. These adults","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,419.7814,380.94366,430.19843]},{"Spans":[{"Chars":null,"Origin":[222.00018,441.00043],"Bbox":[222.00018,432.9814,369.96567,443.39844],"Text":"are voting in the same way that","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,432.9814,369.96567,443.39844]},{"Spans":[{"Chars":null,"Origin":[222.00018,454.20044],"Bbox":[222.00018,446.14844,382.80057,455.83945],"Text":"kids would. If these adults do this,","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,446.14844,382.80057,455.83945]},{"Spans":[{"Chars":null,"Origin":[222.00018,467.40045],"Bbox":[222.00018,459.24945,382.63986,469.79846],"Text":"then why can’t children vote? The","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,459.24945,382.63986,469.79846]},{"Spans":[{"Chars":null,"Origin":[222.00018,480.60046],"Bbox":[222.00018,472.58145,363.26443,482.99847],"Text":"real point to make here is that","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,472.58145,363.26443,482.99847]},{"Spans":[{"Chars":null,"Origin":[222.00018,493.80048],"Bbox":[222.00018,485.78146,389.58203,494.05347],"Text":"these adults should not vote at all. I","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,485.78146,389.58203,494.05347]},{"Spans":[{"Chars":null,"Origin":[222.00018,507.0005],"Bbox":[222.00018,498.98148,366.96368,507.25348],"Text":"think that kids should have the","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,498.98148,366.96368,507.25348]},{"Spans":[{"Chars":null,"Origin":[222.00018,520.2005],"Bbox":[222.00018,512.1815,281.9315,522.5985],"Text":"right to vote.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,512.1815,281.9315,522.5985]}],"IsRect":true},{"Xref":0,"Number":11,"Type":0,"Bbox":[36.0,543.318,223.08658,556.854],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[36.0,556.44],"Bbox":[36.0,543.318,223.08658,556.854],"Text":"From a Kid to a Redskin","Size":18.0,"Flags":16.0,"CharFlags":24,"Bidi":0,"Font":"Optima-Bold","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0,543.318,223.08658,556.854]}],"IsRect":true},{"Xref":0,"Number":12,"Type":0,"Bbox":[36.0,573.879,75.5154,582.411],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[36.0,580.44],"Bbox":[36.0,573.879,75.5154,582.411],"Text":"by Patrick","Size":9.0,"Flags":16.0,"CharFlags":24,"Bidi":0,"Font":"Optima-Bold","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0,573.879,75.5154,582.411]}],"IsRect":true},{"Xref":0,"Number":13,"Type":0,"Bbox":[36.0001,590.748,202.32996,759.59796],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[48.0,598.8],"Bbox":[48.0,590.748,198.47885,601.198],"Text":"I stood toe to toe with a giant of","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[48.0,590.748,198.47885,601.198]},{"Spans":[{"Chars":null,"Origin":[36.0001,612.0],"Bbox":[36.0001,603.981,169.31346,614.398],"Text":"a man. It’s amazing how big","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,603.981,169.31346,614.398]},{"Spans":[{"Chars":null,"Origin":[36.0001,625.2],"Bbox":[36.0001,617.148,189.47318,627.598],"Text":"professional football players are.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,617.148,189.47318,627.598]},{"Spans":[{"Chars":null,"Origin":[36.0001,638.4],"Bbox":[36.0001,630.249,188.87146,640.79803],"Text":"On Saturday afternoon, Oct. 28,","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,630.249,188.87146,640.79803]},{"Spans":[{"Chars":null,"Origin":[36.0001,651.6],"Bbox":[36.0001,643.581,188.8429,653.23895],"Text":"2000, I went to Redskins Park in","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,643.581,188.8429,653.23895]},{"Spans":[{"Chars":null,"Origin":[36.0001,664.8],"Bbox":[36.0001,656.748,180.81067,667.198],"Text":"Ashburn, Va., with the hope of","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,656.748,180.81067,667.198]},{"Spans":[{"Chars":null,"Origin":[36.0001,678.0],"Bbox":[36.0001,669.948,185.21837,680.398],"Text":"interviewing one of the players.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,669.948,185.21837,680.398]},{"Spans":[{"Chars":null,"Origin":[36.0001,691.19995],"Bbox":[36.0001,683.14795,202.32996,693.59796],"Text":"While waiting in the parking lot for","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,683.14795,202.32996,693.59796]},{"Spans":[{"Chars":null,"Origin":[36.0001,704.39996],"Bbox":[36.0001,696.381,199.26974,706.798],"Text":"the players to leave practice, I was","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,696.381,199.26974,706.798]},{"Spans":[{"Chars":null,"Origin":[36.0001,717.6],"Bbox":[36.0001,709.581,172.34949,719.998],"Text":"lucky enough to see Michael","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,709.581,172.34949,719.998]},{"Spans":[{"Chars":null,"Origin":[36.0001,730.8],"Bbox":[36.0001,722.748,179.12436,732.43896],"Text":"Moore, all 6 foot 3 inches tall,","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,722.748,179.12436,732.43896]},{"Spans":[{"Chars":null,"Origin":[36.0001,744.0],"Bbox":[36.0001,735.981,177.2555,746.398],"Text":"weighing 320 pounds. He ap-","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,735.981,177.2555,746.398]},{"Spans":[{"Chars":null,"Origin":[36.0001,757.19995],"Bbox":[36.0001,749.18097,191.91296,759.59796],"Text":"proached his car and I went over","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[36.0001,749.18097,191.91296,759.59796]}],"IsRect":true},{"Xref":0,"Number":14,"Type":0,"Bbox":[222.00018,563.7485,363.26227,587.3985],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[222.00018,571.8005],"Bbox":[222.00018,563.7485,363.26227,572.05347],"Text":"to see if I could ask him a few","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,563.7485,363.26227,572.05347]},{"Spans":[{"Chars":null,"Origin":[222.00018,585.0005],"Bbox":[222.00018,576.9815,269.71597,587.3985],"Text":"questions.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,576.9815,269.71597,587.3985]}],"IsRect":true},{"Xref":0,"Number":15,"Type":0,"Bbox":[222.00018,590.14844,384.87302,756.85345],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[234.00008,598.20044],"Bbox":[234.00008,590.14844,375.26654,600.59845],"Text":"He told me how his father got","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[234.00008,590.14844,375.26654,600.59845]},{"Spans":[{"Chars":null,"Origin":[222.00018,611.40045],"Bbox":[222.00018,603.34845,384.67706,611.65344],"Text":"him interested in football when he","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,603.34845,384.67706,611.65344]},{"Spans":[{"Chars":null,"Origin":[222.00018,624.60046],"Bbox":[222.00018,616.5815,361.86194,626.9985],"Text":"was in the 9th grade. Moore’s","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,616.5815,361.86194,626.9985]},{"Spans":[{"Chars":null,"Origin":[222.00018,637.8005],"Bbox":[222.00018,629.7485,384.87302,640.1985],"Text":"greatest teacher in life is his father.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,629.7485,384.87302,640.1985]},{"Spans":[{"Chars":null,"Origin":[222.00018,651.0005],"Bbox":[222.00018,642.9485,384.6627,653.3985],"Text":"Moore played offense and defense","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,642.9485,384.6627,653.3985]},{"Spans":[{"Chars":null,"Origin":[222.00018,664.20044],"Bbox":[222.00018,656.04944,378.56436,666.59845],"Text":"at Fayette County High School in","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,656.04944,378.56436,666.59845]},{"Spans":[{"Chars":null,"Origin":[222.00018,677.40045],"Bbox":[222.00018,669.3815,377.3093,679.79846],"Text":"Alabama. He attended college at","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,669.3815,377.3093,679.79846]},{"Spans":[{"Chars":null,"Origin":[222.00018,690.60046],"Bbox":[222.00018,682.54846,379.11435,692.9985],"Text":"Alabama for three years and then","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,682.54846,379.11435,692.9985]},{"Spans":[{"Chars":null,"Origin":[222.00018,703.8004],"Bbox":[222.00018,695.6494,379.17813,706.1984],"Text":"transferred to Troy State. In 2000,","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,695.6494,379.17813,706.1984]},{"Spans":[{"Chars":null,"Origin":[222.00018,717.0004],"Bbox":[222.00018,708.98145,373.0149,718.6394],"Text":"“Mookie,” as he is known to his","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,708.98145,373.0149,718.6394]},{"Spans":[{"Chars":null,"Origin":[222.00018,730.20044],"Bbox":[222.00018,722.14844,374.86942,732.59845],"Text":"fellow players, signed a contract","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,722.14844,374.86942,732.59845]},{"Spans":[{"Chars":null,"Origin":[222.00018,743.40045],"Bbox":[222.00018,735.3815,367.52908,745.79846],"Text":"with the Washington Redskins.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,735.3815,367.52908,745.79846]},{"Spans":[{"Chars":null,"Origin":[222.00018,756.60046],"Bbox":[222.00018,748.54846,381.54425,756.85345],"Text":"He’s an offensive lineman and his","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[222.00018,748.54846,381.54425,756.85345]}],"IsRect":true},{"Xref":0,"Number":16,"Type":0,"Bbox":[408.00027,551.78156,473.43048,560.0535],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[408.00027,559.80054],"Bbox":[408.00027,551.78156,473.43048,560.0535],"Text":"number is 66.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.00027,551.78156,473.43048,560.0535]}],"IsRect":true},{"Xref":0,"Number":17,"Type":0,"Bbox":[408.00027,564.98157,569.39465,692.0535],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[420.00018,573.00055],"Bbox":[420.00018,564.98157,562.4755,575.39856],"Text":"To make my day complete, he","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[420.00018,564.98157,562.4755,575.39856]},{"Spans":[{"Chars":null,"Origin":[408.00027,586.20056],"Bbox":[408.00027,578.14856,560.8685,588.5986],"Text":"offered to give me his autograph","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.00027,578.14856,560.8685,588.5986]},{"Spans":[{"Chars":null,"Origin":[408.00027,599.4005],"Bbox":[408.00027,591.38153,562.10913,601.7985],"Text":"and he shook my hand. I wished","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.00027,591.38153,562.10913,601.7985]},{"Spans":[{"Chars":null,"Origin":[408.00027,612.6005],"Bbox":[408.00027,604.58154,546.806,614.99854],"Text":"him luck on the game that he","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.00027,604.58154,546.806,614.99854]},{"Spans":[{"Chars":null,"Origin":[408.00027,625.80054],"Bbox":[408.00027,617.78156,548.05133,628.19855],"Text":"would be playing on Monday","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.00027,617.78156,548.05133,628.19855]},{"Spans":[{"Chars":null,"Origin":[408.00027,639.00055],"Bbox":[408.00027,630.98157,569.39465,641.39856],"Text":"Night Football. He got into his car","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.00027,630.98157,569.39465,641.39856]},{"Spans":[{"Chars":null,"Origin":[408.00027,652.20056],"Bbox":[408.00027,644.14856,566.1099,654.5986],"Text":"and left the parking lot. I couldn’t","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.00027,644.14856,566.1099,654.5986]},{"Spans":[{"Chars":null,"Origin":[408.00027,665.4005],"Bbox":[408.00027,657.38153,550.4481,667.7985],"Text":"stop smiling. Just think – I was","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.00027,657.38153,550.4481,667.7985]},{"Spans":[{"Chars":null,"Origin":[408.00027,678.6005],"Bbox":[408.00027,670.58154,540.10144,680.99854],"Text":"lucky enough to interview a","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.00027,670.58154,540.10144,680.99854]},{"Spans":[{"Chars":null,"Origin":[408.00027,691.80054],"Bbox":[408.00027,683.78156,514.4012,692.0535],"Text":"Redskin. What a thrill!","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.00027,683.78156,514.4012,692.0535]}],"IsRect":true},{"Xref":0,"Number":19,"Type":0,"Bbox":[408.0,134.47798,507.0414,151.54198],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[408.0,147.59998],"Bbox":[408.0,134.47798,507.0414,151.54198],"Text":"Harry Potter","Size":18.0,"Flags":16.0,"CharFlags":24,"Bidi":0,"Font":"Optima-Bold","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0,134.47798,507.0414,151.54198]}],"IsRect":true},{"Xref":0,"Number":20,"Type":0,"Bbox":[408.0,165.03897,443.0118,173.57097],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[408.0,171.59998],"Bbox":[408.0,165.03897,443.0118,173.57097],"Text":"by Bryan","Size":9.0,"Flags":16.0,"CharFlags":24,"Bidi":0,"Font":"Optima-Bold","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0,165.03897,443.0118,173.57097]}],"IsRect":true},{"Xref":0,"Number":21,"Type":0,"Bbox":[408.0001,182.74799,573.7782,404.3982],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[420.0,190.79999],"Bbox":[420.0,182.74799,569.4199,193.19798],"Text":"Harry Potter is a good book for","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[420.0,182.74799,569.4199,193.19798]},{"Spans":[{"Chars":null,"Origin":[408.0001,204.0],"Bbox":[408.0001,195.981,551.31384,206.398],"Text":"advanced readers. It is a story","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,195.981,551.31384,206.398]},{"Spans":[{"Chars":null,"Origin":[408.0001,217.20001],"Bbox":[408.0001,209.18102,564.9164,219.598],"Text":"about a boy who lost his parents","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,209.18102,564.9164,219.598]},{"Spans":[{"Chars":null,"Origin":[408.0001,230.40002],"Bbox":[408.0001,222.38103,571.1194,230.65303],"Text":"and was delivered to his aunt and","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,222.38103,571.1194,230.65303]},{"Spans":[{"Chars":null,"Origin":[408.0001,243.60004],"Bbox":[408.0001,235.44904,564.3114,245.99803],"Text":"uncle and his cousin. Since they","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,235.44904,564.3114,245.99803]},{"Spans":[{"Chars":null,"Origin":[408.0001,256.80005],"Bbox":[408.0001,248.78105,572.7209,259.19806],"Text":"hate him so much, they make him","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,248.78105,572.7209,259.19806]},{"Spans":[{"Chars":null,"Origin":[408.0001,270.00006],"Bbox":[408.0001,261.84906,550.9596,272.39807],"Text":"work hard. One day he gets a","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,261.84906,550.9596,272.39807]},{"Spans":[{"Chars":null,"Origin":[408.0001,283.20007],"Bbox":[408.0001,275.18106,538.15344,285.59808],"Text":"letter saying he can go to a","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,275.18106,538.15344,285.59808]},{"Spans":[{"Chars":null,"Origin":[408.0001,296.4001],"Bbox":[408.0001,288.38107,559.9191,298.7981],"Text":"wizarding school, but his uncle","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,288.38107,559.9191,298.7981]},{"Spans":[{"Chars":null,"Origin":[408.0001,309.6001],"Bbox":[408.0001,301.5811,562.7989,311.9981],"Text":"won’t let him go. Then a wizard","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,301.5811,562.7989,311.9981]},{"Spans":[{"Chars":null,"Origin":[408.0001,322.8001],"Bbox":[408.0001,314.7811,553.40594,325.19812],"Text":"comes and takes Harry school","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,314.7811,553.40594,325.19812]},{"Spans":[{"Chars":null,"Origin":[408.0001,336.00012],"Bbox":[408.0001,327.9811,568.0745,338.39813],"Text":"shopping. Next, Harry is taken to","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,327.9811,568.0745,338.39813]},{"Spans":[{"Chars":null,"Origin":[408.0001,349.20013],"Bbox":[408.0001,341.14813,573.7782,351.59814],"Text":"a magic train to a school of witch-","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,341.14813,573.7782,351.59814]},{"Spans":[{"Chars":null,"Origin":[408.0001,362.40015],"Bbox":[408.0001,354.34814,573.7781,364.79816],"Text":"craft and wizardry and has a lot of","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,354.34814,573.7781,364.79816]},{"Spans":[{"Chars":null,"Origin":[408.0001,375.60016],"Bbox":[408.0001,367.44916,566.6148,375.85315],"Text":"adventures there. One adventure","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,367.44916,566.6148,375.85315]},{"Spans":[{"Chars":null,"Origin":[408.0001,388.80017],"Bbox":[408.0001,380.74817,554.02747,391.19818],"Text":"is that he finds out the story of","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,380.74817,554.02747,391.19818]},{"Spans":[{"Chars":null,"Origin":[408.0001,402.00018],"Bbox":[408.0001,393.98117,511.69617,404.3982],"Text":"how his parents died.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,393.98117,511.69617,404.3982]}],"IsRect":true},{"Xref":0,"Number":22,"Type":0,"Bbox":[408.0001,407.1482,574.3269,523.1983],"Width":0,"Height":0,"Ext":null,"ColorSpace":0,"Xres":0,"Yres":0,"Bpc":0,"Transform":null,"Size":0,"Image":null,"Mask":null,"CsName":null,"Digest":null,"Lines":[{"Spans":[{"Chars":null,"Origin":[420.0,415.2002],"Bbox":[420.0,407.1482,570.17096,417.5982],"Text":"After interviewing classmates, I","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[420.0,407.1482,570.17096,417.5982]},{"Spans":[{"Chars":null,"Origin":[408.0001,428.4002],"Bbox":[408.0001,420.3482,570.7882,430.79822],"Text":"found some people like and don’t","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,420.3482,570.7882,430.79822]},{"Spans":[{"Chars":null,"Origin":[408.0001,441.60022],"Bbox":[408.0001,433.5812,554.0725,443.99823],"Text":"like Harry Potter. Nick says he","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,433.5812,554.0725,443.99823]},{"Spans":[{"Chars":null,"Origin":[408.0001,454.80023],"Bbox":[408.0001,446.78122,550.65155,457.19824],"Text":"likes Harry Potter because it’s","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,446.78122,550.65155,457.19824]},{"Spans":[{"Chars":null,"Origin":[408.0001,468.00024],"Bbox":[408.0001,459.94824,567.07135,470.39825],"Text":"creative and funny. Damani likes","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,459.94824,567.07135,470.39825]},{"Spans":[{"Chars":null,"Origin":[408.0001,481.20026],"Bbox":[408.0001,473.18124,569.52216,481.45325],"Text":"it because it takes a while to read","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,473.18124,569.52216,481.45325]},{"Spans":[{"Chars":null,"Origin":[408.0001,494.40027],"Bbox":[408.0001,486.38126,570.422,496.79828],"Text":"and you think something bad will","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,486.38126,570.422,496.79828]},{"Spans":[{"Chars":null,"Origin":[408.0001,507.60028],"Bbox":[408.0001,499.58127,574.3269,509.9983],"Text":"happen but it doesn’t. Reid says it","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,499.58127,574.3269,509.9983]},{"Spans":[{"Chars":null,"Origin":[408.0001,520.8003],"Bbox":[408.0001,512.7813,565.6007,523.1983],"Text":"is advanced because it’s so long.","Size":11.0,"Flags":0.0,"CharFlags":16,"Bidi":0,"Font":"Optima","Color":0,"Alpha":255,"Asc":0.753,"Desc":-0.269}],"WMode":0,"Dir":[1.0,0.0],"Bbox":[408.0001,512.7813,565.6007,523.1983]}],"IsRect":true}],"words":[],"links":[]}],"metadata":{"format":"PDF 1.2","title":"Kid's News 1","author":"Alice V. Knox","subject":"Kid's News 1","keywords":"","creator":"Adobe PageMaker 6.52","producer":"Acrobat Distiller 4.0 for Windows","creationDate":"D:20010815145036","modDate":"D:20010920142302-04'00'","trapped":"","encryption":""},"form_fields":{},"from_bytes":false,"image_dpi":150,"image_format":"png","image_path":"","force_text":true,"embed_images":false,"write_images":false,"use_ocr":0} \ No newline at end of file diff --git a/MuPDF.NET.PDF4LLM/02-ToJsonLayout/Expected/layout-status.txt b/MuPDF.NET.PDF4LLM/02-ToJsonLayout/Expected/layout-status.txt new file mode 100644 index 0000000..aecbab5 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/02-ToJsonLayout/Expected/layout-status.txt @@ -0,0 +1 @@ +LAYOUT_AVAILABLE diff --git a/MuPDF.NET.PDF4LLM/02-ToJsonLayout/Program.cs b/MuPDF.NET.PDF4LLM/02-ToJsonLayout/Program.cs new file mode 100644 index 0000000..c9a9858 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/02-ToJsonLayout/Program.cs @@ -0,0 +1,70 @@ +using System.Text.RegularExpressions; +using MuPDF.NET.Examples.Common; +using MuPDF.NET.PDF4LLM; + +namespace MuPDF.NET.Examples.PDF4LLM.ToJsonLayout; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.PDF4LLM / 02-ToJsonLayout"); + ToJsonLayout(); + } + + /// + /// Convert a PDF to layout JSON (requires pymupdf-layout). + /// + static void ToJsonLayout() + { + string input = ExamplePaths.Pdf4LlmInput("columns.pdf"); + string output = ExamplePaths.Output("MuPDF.NET.PDF4LLM", "02-ToJsonLayout", "columns.json"); + var check = new ResultCheck("MuPDF.NET.PDF4LLM", "02-ToJsonLayout"); + + bool prior = MuPDF4LLM.UseLayout; + try + { + // ToJson needs the optional layout provider. + if (!MuPDF4LLM.LayoutAvailable) + { + ConsoleEx.Info("Layout provider unavailable — ToJson requires UseLayout=true."); + ConsoleEx.Info("Install pymupdf-layout (see MuPDF.NET.PDF4LLM README) and re-run."); + check.Text("LAYOUT_UNAVAILABLE\n", "layout-status.txt"); + check.Finish(); + return; + } + + MuPDF4LLM.SetUseLayout(true); + // ToJson embeds an absolute input path; keep only the file name for portable baselines. + string json = NormalizeJsonFilename(MuPDF4LLM.ToJson(input, useOcr: false) ?? "", input); + File.WriteAllText(output, json); + + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"JSON length: {json.Length}"); + check.Text("LAYOUT_AVAILABLE\n", "layout-status.txt"); + check.Text(json, "columns.json"); + } + finally + { + MuPDF4LLM.SetUseLayout(prior); + } + + check.Finish(); + } + + /// + /// Replace the absolute filename field with so Expected/ + /// baselines do not depend on the machine checkout path. + /// + static string NormalizeJsonFilename(string json, string inputPath) + { + string name = Path.GetFileName(inputPath); + return Regex.Replace( + json, + "\"filename\"\\s*:\\s*\"(?:\\\\.|[^\"\\\\])*\"", + "\"filename\":\"" + name.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"", + RegexOptions.CultureInvariant, + TimeSpan.FromSeconds(2)); + } +} diff --git a/MuPDF.NET.PDF4LLM/02-ToJsonLayout/README.md b/MuPDF.NET.PDF4LLM/02-ToJsonLayout/README.md new file mode 100644 index 0000000..cb8a102 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/02-ToJsonLayout/README.md @@ -0,0 +1,36 @@ +# 02-ToJsonLayout + +Convert a PDF to layout JSON. Requires the optional **pymupdf-layout** provider. + +## Sample method + +`ToJsonLayout()` in `Program.cs`. + +## Package + +- [MuPDF.NET.PDF4LLM](https://www.nuget.org/packages/MuPDF.NET.PDF4LLM) + +## Prerequisites + +- Install [pymupdf-layout](https://pypi.org/project/pymupdf-layout/) so `MuPDF4LLM.LayoutAvailable` is true. +- Without layout, the sample records `LAYOUT_UNAVAILABLE` and skips JSON. + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET.PDF4LLM/columns.pdf` | +| Output | `Output/MuPDF.NET.PDF4LLM/02-ToJsonLayout/columns.json` | +| Expected | `layout-status.txt`, `columns.json` | + +## Run + +```powershell +dotnet run --project MuPDF.NET.PDF4LLM\02-ToJsonLayout +``` + +## APIs used + +- `MuPDF4LLM.LayoutAvailable` +- `MuPDF4LLM.SetUseLayout(true)` +- `MuPDF4LLM.ToJson` diff --git a/MuPDF.NET.PDF4LLM/03-ToText/03-ToText.csproj b/MuPDF.NET.PDF4LLM/03-ToText/03-ToText.csproj new file mode 100644 index 0000000..1848a88 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/03-ToText/03-ToText.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.PDF4LLM.ToText + 03-ToText + + + + + + diff --git a/MuPDF.NET.PDF4LLM/03-ToText/Expected/columns.txt b/MuPDF.NET.PDF4LLM/03-ToText/Expected/columns.txt new file mode 100644 index 0000000..b7055ad --- /dev/null +++ b/MuPDF.NET.PDF4LLM/03-ToText/Expected/columns.txt @@ -0,0 +1,38 @@ +Kid’s News Hammond Elementary School + +Fall 2000 + +Kids’ Right to Vote + +by Lindsey + +The election is a very important time of the year. Kids should be able to vote. They should be driven by their parents to the polling location to vote and then taken home by their parents. If kids voted it would make a difference in which president was elected. Adults have no more rights than kids, and everybody should have their own right to vote for the president. If kids could vote, the law should be that they can’t talk about their votes, and they should have to know a lot about the candidate to vote for him or her. + +When the next election comes, kids older than 10 should be able to vote. There are some reasons why kids can’t vote now. They don’t have the education and + +information on our political system to be able to make a good decision. Kids don’t understand the views of the candidates on issues such as the country’s health system, or welfare system, or taxes. They will easily be persuaded by other kids’ opinions about whom they are voting for. Kids would be voting without knowing what they are really voting for. They might copy someone else’s ballot. + +The law should say that if kids are ever allowed to vote, they should be supervised. Kids aren’t very educated in politics. If kids knew more about the candidates, it would be fine for them to vote. Although all adults are allowed to vote, some adults vote without knowing how the candidates view the important issues. These adults are voting in the same way that kids would. If these adults do this, then why can’t children vote? The real point to make here is that these adults should not vote at all. I think that kids should have the right to vote. + +Harry Potter + +by Bryan + +Harry Potter is a good book for advanced readers. It is a story about a boy who lost his parents and was delivered to his aunt and uncle and his cousin. Since they hate him so much, they make him work hard. One day he gets a letter saying he can go to a wizarding school, but his uncle won’t let him go. Then a wizard comes and takes Harry school shopping. Next, Harry is taken to a magic train to a school of witchcraft and wizardry and has a lot of adventures there. One adventure is that he finds out the story of how his parents died. + +After interviewing classmates, I found some people like and don’t like Harry Potter. Nick says he likes Harry Potter because it’s creative and funny. Damani likes it because it takes a while to read and you think something bad will happen but it doesn’t. Reid says it is advanced because it’s so long. + +From a Kid to a Redskin + +to see if I could ask him a few questions. + +by Patrick + +He told me how his father got him interested in football when he was in the 9th grade. Moore’s greatest teacher in life is his father. Moore played offense and defense at Fayette County High School in Alabama. He attended college at Alabama for three years and then transferred to Troy State. In 2000, “Mookie,” as he is known to his fellow players, signed a contract with the Washington Redskins. He’s an offensive lineman and his + +I stood toe to toe with a giant of a man. It’s amazing how big professional football players are. On Saturday afternoon, Oct. 28, 2000, I went to Redskins Park in Ashburn, Va., with the hope of interviewing one of the players. While waiting in the parking lot for the players to leave practice, I was lucky enough to see Michael Moore, all 6 foot 3 inches tall, weighing 320 pounds. He approached his car and I went over + +number is 66. + +To make my day complete, he offered to give me his autograph and he shook my hand. I wished him luck on the game that he would be playing on Monday Night Football. He got into his car and left the parking lot. I couldn’t stop smiling. Just think – I was lucky enough to interview a Redskin. What a thrill! + diff --git a/MuPDF.NET.PDF4LLM/03-ToText/Expected/layout-status.txt b/MuPDF.NET.PDF4LLM/03-ToText/Expected/layout-status.txt new file mode 100644 index 0000000..aecbab5 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/03-ToText/Expected/layout-status.txt @@ -0,0 +1 @@ +LAYOUT_AVAILABLE diff --git a/MuPDF.NET.PDF4LLM/03-ToText/Program.cs b/MuPDF.NET.PDF4LLM/03-ToText/Program.cs new file mode 100644 index 0000000..dcdad01 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/03-ToText/Program.cs @@ -0,0 +1,52 @@ +using MuPDF.NET.Examples.Common; +using MuPDF.NET.PDF4LLM; + +namespace MuPDF.NET.Examples.PDF4LLM.ToText; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.PDF4LLM / 03-ToText"); + ToText(); + } + + /// + /// Convert a PDF to plain text via layout (requires pymupdf-layout). + /// + static void ToText() + { + string input = ExamplePaths.Pdf4LlmInput("columns.pdf"); + string output = ExamplePaths.Output("MuPDF.NET.PDF4LLM", "03-ToText", "columns.txt"); + var check = new ResultCheck("MuPDF.NET.PDF4LLM", "03-ToText"); + + bool prior = MuPDF4LLM.UseLayout; + try + { + // ToText requires UseLayout=true. + if (!MuPDF4LLM.LayoutAvailable) + { + ConsoleEx.Info("Layout provider unavailable — ToText requires UseLayout=true."); + check.Text("LAYOUT_UNAVAILABLE\n", "layout-status.txt"); + check.Finish(); + return; + } + + MuPDF4LLM.SetUseLayout(true); + string text = MuPDF4LLM.ToText(input, useOcr: false, showProgress: false) ?? ""; + File.WriteAllText(output, text); + + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Text length: {text.Length}"); + check.Text("LAYOUT_AVAILABLE\n", "layout-status.txt"); + check.Text(text, "columns.txt"); + } + finally + { + MuPDF4LLM.SetUseLayout(prior); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET.PDF4LLM/03-ToText/README.md b/MuPDF.NET.PDF4LLM/03-ToText/README.md new file mode 100644 index 0000000..fbf96e3 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/03-ToText/README.md @@ -0,0 +1,34 @@ +# 03-ToText + +Convert a PDF to plain text via layout. Requires **pymupdf-layout**. + +## Sample method + +`ToText()` in `Program.cs`. + +## Package + +- [MuPDF.NET.PDF4LLM](https://www.nuget.org/packages/MuPDF.NET.PDF4LLM) + +## Prerequisites + +- Same layout provider as `02-ToJsonLayout`. Without it, the sample records `LAYOUT_UNAVAILABLE`. + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET.PDF4LLM/columns.pdf` | +| Output | `Output/MuPDF.NET.PDF4LLM/03-ToText/columns.txt` | +| Expected | `layout-status.txt`, `columns.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET.PDF4LLM\03-ToText +``` + +## APIs used + +- `MuPDF4LLM.SetUseLayout(true)` +- `MuPDF4LLM.ToText` diff --git a/MuPDF.NET.PDF4LLM/04-Ocr/04-Ocr.csproj b/MuPDF.NET.PDF4LLM/04-Ocr/04-Ocr.csproj new file mode 100644 index 0000000..6b0e92b --- /dev/null +++ b/MuPDF.NET.PDF4LLM/04-Ocr/04-Ocr.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.PDF4LLM.Ocr + 04-Ocr + + + + + + diff --git a/MuPDF.NET.PDF4LLM/04-Ocr/Expected/ocr-off.md b/MuPDF.NET.PDF4LLM/04-Ocr/Expected/ocr-off.md new file mode 100644 index 0000000..e69de29 diff --git a/MuPDF.NET.PDF4LLM/04-Ocr/Expected/ocr-on.md b/MuPDF.NET.PDF4LLM/04-Ocr/Expected/ocr-on.md new file mode 100644 index 0000000..ac986ca --- /dev/null +++ b/MuPDF.NET.PDF4LLM/04-Ocr/Expected/ocr-on.md @@ -0,0 +1,18 @@ +MENARDS ‑ FRANKLIN Rebate Receipt + +3706000036311561045758319509 + +11% Rebate on Everything (15A) Rebate #3706 + +Offer valid 04‑07‑25 Thru 04‑13‑25 + +You have one year from purchase date to mail in rebates. Store: 3195 + +To obtain rebate form, pickup at Rebate Center in store, or go to www.menards.com and download as needed. Rebate is in the form of a Menards Merchandise Credit Check. + +See rebate form for terms and conditions related to rebate submission. + +11% Rebate Amount 3.63 62449 11 5610 04/13/25 11:51AM 3195 + +3.63 + diff --git a/MuPDF.NET.PDF4LLM/04-Ocr/Program.cs b/MuPDF.NET.PDF4LLM/04-Ocr/Program.cs new file mode 100644 index 0000000..53d7cba --- /dev/null +++ b/MuPDF.NET.PDF4LLM/04-Ocr/Program.cs @@ -0,0 +1,54 @@ +using MuPDF.NET.Examples.Common; +using MuPDF.NET.PDF4LLM; + +namespace MuPDF.NET.Examples.PDF4LLM.Ocr; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.PDF4LLM / 04-Ocr"); + OcrCompare(); + } + + /// + /// Compare Markdown with OCR on vs off (layout mode required for OCR). + /// + static void OcrCompare() + { + string input = ExamplePaths.Pdf4LlmInput("Ocr.pdf"); + string withOcr = ExamplePaths.Output("MuPDF.NET.PDF4LLM", "04-Ocr", "ocr-on.md"); + string withoutOcr = ExamplePaths.Output("MuPDF.NET.PDF4LLM", "04-Ocr", "ocr-off.md"); + var check = new ResultCheck("MuPDF.NET.PDF4LLM", "04-Ocr"); + + bool prior = MuPDF4LLM.UseLayout; + try + { + // OCR is only implemented in the layout pipeline (DocumentLayout). + // With UseLayout=false, useOcr is ignored and scanned pages stay empty. + MuPDF4LLM.SetUseLayout(true); + + // Without OCR: only embedded text. + string mdOff = MuPDF4LLM.ToMarkdown(input, showProgress: false, useOcr: false) ?? ""; + File.WriteAllText(withoutOcr, mdOff); + + // With OCR: Tesseract where the pipeline decides it helps (needs tessdata). + string mdOn = MuPDF4LLM.ToMarkdown(input, showProgress: false, useOcr: true) ?? ""; + File.WriteAllText(withOcr, mdOn); + + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Markdown length useOcr=false: {mdOff.Length}"); + ConsoleEx.Info($"Markdown length useOcr=true: {mdOn.Length}"); + + check.Text(mdOff, "ocr-off.md"); + check.Text(mdOn, "ocr-on.md"); + } + finally + { + MuPDF4LLM.SetUseLayout(prior); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET.PDF4LLM/04-Ocr/README.md b/MuPDF.NET.PDF4LLM/04-Ocr/README.md new file mode 100644 index 0000000..6559c53 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/04-Ocr/README.md @@ -0,0 +1,34 @@ +# 04-Ocr + +Compare Markdown extraction with OCR on vs off. + +## Sample method + +`OcrCompare()` in `Program.cs`. + +## Package + +- [MuPDF.NET.PDF4LLM](https://www.nuget.org/packages/MuPDF.NET.PDF4LLM) + +## Prerequisites + +- OCR needs **layout mode** (`SetUseLayout(true)`) — `useOcr` is ignored when layout is off. +- OCR path also needs a working Tesseract / tessdata install when `useOcr: true`. + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET.PDF4LLM/Ocr.pdf` | +| Output | `ocr-on.md`, `ocr-off.md` under `Output/MuPDF.NET.PDF4LLM/04-Ocr/` | +| Expected | `ocr-on.md`, `ocr-off.md` | + +## Run + +```powershell +dotnet run --project MuPDF.NET.PDF4LLM\04-Ocr +``` + +## APIs used + +- `MuPDF4LLM.ToMarkdown(..., useOcr: false|true)` diff --git a/MuPDF.NET.PDF4LLM/05-TablesCsv/05-TablesCsv.csproj b/MuPDF.NET.PDF4LLM/05-TablesCsv/05-TablesCsv.csproj new file mode 100644 index 0000000..727f839 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/05-TablesCsv/05-TablesCsv.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.PDF4LLM.TablesCsv + 05-TablesCsv + + + + + + diff --git a/MuPDF.NET.PDF4LLM/05-TablesCsv/Expected/capitals.csv b/MuPDF.NET.PDF4LLM/05-TablesCsv/Expected/capitals.csv new file mode 100644 index 0000000..b7b082d --- /dev/null +++ b/MuPDF.NET.PDF4LLM/05-TablesCsv/Expected/capitals.csv @@ -0,0 +1,33 @@ +tableCount=1 +rows=31 cols=5 +Afghanistan,Kabul,4601789,11.5%,2021 +Albania,Tirana,557422,19.5%,2011 +Algeria,Algiers,3915811,8.9%,2011 +"American Samoa (USA)","Pago Pago",3656,8.1%,2010 +Andorra,"Andorra la Vella",22873,28.9%,2022 +Angola,Luanda,2571861,7.5%,2020 +"Anguilla (UK)","The Valley",1067,6.8%,2011 +"Antigua and Barbuda","St. John's",22219,23.8%,2011 +Argentina,"Buenos Aires",2891082,6.4%,2010 +Armenia,Yerevan,1096100,39.3%,2021 +"Aruba (Netherlands)",Oranjestad,28294,26.6%,2010 +Australia,Canberra,431380,1.7%,2020 +Austria,Vienna,1962779,22.0%,2022 +Azerbaijan,Baku,2303100,22.3%,2022 +Bahamas,Nassau,274400,67.3%,2016 +Bahrain,Manama,200000,13.7%,2020 +Bangladesh,Dhaka,8906039,5.3%,2011 +Barbados,Bridgetown,110000,39.1%,2014 +Belarus,Minsk,1996553,20.8%,2022 +Belgium,Brussels,187686,1.6%,2022 +Belize,Belmopan,20621,5.2%,2016 +Benin,Porto-Novo,264320,2.0%,2013 +"Bermuda (UK)",Hamilton,854,1.3%,2016 +Bhutan,Thimphu,114551,14.7%,2017 +Bolivia,Sucre,360544,3.0%,2022 +"Bosnia and Herzegovina",Sarajevo,275524,8.4%,2013 +Botswana,Gaborone,273602,10.6%,2020 +Brazil,Brasília,2648532,1.2%,2012 +"British Virgin Islands (UK)","Road Town",12603,40.5%,2012 +Brunei,"Bandar Seri Begawan",100700,22.6%,2007 +Bulgaria,Sofia,1307439,19.0%,2021 diff --git a/MuPDF.NET.PDF4LLM/05-TablesCsv/Program.cs b/MuPDF.NET.PDF4LLM/05-TablesCsv/Program.cs new file mode 100644 index 0000000..dce5be5 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/05-TablesCsv/Program.cs @@ -0,0 +1,64 @@ +using System.Linq; +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.PDF4LLM.TablesCsv; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.PDF4LLM / 05-TablesCsv"); + TablesToCsv(); + } + + /// + /// Detect tables and write the first table as CSV. + /// + static void TablesToCsv() + { + string input = ExamplePaths.Pdf4LlmInput("national-capitals.pdf"); + string output = ExamplePaths.Output("MuPDF.NET.PDF4LLM", "05-TablesCsv", "capitals.csv"); + var check = new ResultCheck("MuPDF.NET.PDF4LLM", "05-TablesCsv"); + + using var doc = Document.Open(input); + using Page page = doc[0]; + + // Same table finder as MuPDF.NET (PDF4LLM package pulls it in). + List tables = Utils.GetTables( + page, + clip: page.Rect, + vertical_strategy: "lines_strict", + horizontal_strategy: "lines_strict"); + + var sb = new StringBuilder(); + sb.Append("tableCount=").Append(tables.Count).Append('\n'); + if (tables.Count > 0) + { + Table table = tables[0]; + sb.Append("rows=").Append(table.RowCount).Append(" cols=").Append(table.ColCount).Append('\n'); + + // Extract() returns row → cell text. + List> rows = table.Extract(); + foreach (List row in rows) + sb.Append(string.Join(",", row.Select(CsvEscape))).Append('\n'); + } + + string csv = sb.ToString(); + File.WriteAllText(output, csv); + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Tables found: {tables.Count}"); + check.Text(csv, "capitals.csv"); + check.Finish(); + } + + static string CsvEscape(string? cell) + { + string s = (cell ?? "").Replace("\r\n", " ").Replace('\n', ' ').Replace('\r', ' ').Trim(); + if (s.Contains(',') || s.Contains('"') || s.Contains(' ')) + return "\"" + s.Replace("\"", "\"\"") + "\""; + return s; + } +} diff --git a/MuPDF.NET.PDF4LLM/05-TablesCsv/README.md b/MuPDF.NET.PDF4LLM/05-TablesCsv/README.md new file mode 100644 index 0000000..92c6823 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/05-TablesCsv/README.md @@ -0,0 +1,30 @@ +# 05-TablesCsv + +Detect tables on a page and write the first table as CSV. + +## Sample method + +`TablesToCsv()` in `Program.cs`. + +## Package + +- [MuPDF.NET.PDF4LLM](https://www.nuget.org/packages/MuPDF.NET.PDF4LLM) (uses MuPDF.NET table APIs) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET.PDF4LLM/national-capitals.pdf` | +| Output | `Output/MuPDF.NET.PDF4LLM/05-TablesCsv/capitals.csv` | +| Expected | `Expected/capitals.csv` | + +## Run + +```powershell +dotnet run --project MuPDF.NET.PDF4LLM\05-TablesCsv +``` + +## APIs used + +- `Utils.GetTables` +- `Table.Extract` diff --git a/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/06-LlamaMarkdownReader.csproj b/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/06-LlamaMarkdownReader.csproj new file mode 100644 index 0000000..cea0c87 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/06-LlamaMarkdownReader.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.PDF4LLM.LlamaMarkdownReader + 06-LlamaMarkdownReader + + + + + + diff --git a/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/Expected/llama-docs.txt b/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/Expected/llama-docs.txt new file mode 100644 index 0000000..63dd16c --- /dev/null +++ b/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/Expected/llama-docs.txt @@ -0,0 +1,143 @@ +docCount=1 +--- page 1 --- +chars=3885 +# **Kid’s News** +## Hammond Elementary School Fall 2000 + +### **Kids’ Right to Vote** + +**by Lindsey** + +The election is a very important +time of the year. Kids should be +able to vote. They should be driven +by their parents to the polling +location to vote and then taken +home by their parents. If kids voted +it would make a difference in +which president was elected. +Adults have no more rights than +kids, and everybody should have +their own right to vote for the +president. If kids could vote, the +law should be that they can’t talk +about their votes, and they should +have to know a lot about the +candidate to vote for him or her. + +When the next election comes, +kids older than 10 should be able +to vote. There are some reasons +why kids can’t vote now. They +don’t have the education and + + +information on our political system +to be able to make a good decision. Kids don’t understand the +views of the candidates on issues +such as the country’s health system, or welfare system, or taxes. +They will easily be persuaded by +other kids’ opinions about whom +they are voting for. Kids would be +voting without knowing what they +are really voting for. They might +copy someone else’s ballot. + +The law should say that if kids +are ever allowed to vote, they +should be supervised. Kids aren’t +very educated in politics. If kids +knew more about the candidates, it +would be fine for them to vote. +Although all adults are allowed to +vote, some adults vote without +knowing how the candidates view +the important issues. These adults +are voting in the same way that +kids would. If these adults do this, +then why can’t children vote? The +real point to make here is that +these adults should not vote at all. I +think that kids should have the +right to vote. + +### **Harry Potter** + +**by Bryan** + +Harry Potter is a good book for +advanced readers. It is a story +about a boy who lost his parents +and was delivered to his aunt and +uncle and his cousin. Since they +hate him so much, they make him +work hard. One day he gets a +letter saying he can go to a +wizarding school, but his uncle +won’t let him go. Then a wizard +comes and takes Harry school +shopping. Next, Harry is taken to +a magic train to a school of witchcraft and wizardry and has a lot of +adventures there. One adventure +is that he finds out the story of +how his parents died. + +After interviewing classmates, I +found some people like and don’t +like Harry Potter. Nick says he +likes Harry Potter because it’s +creative and funny. Damani likes +it because it takes a while to read +and you think something bad will +happen but it doesn’t. Reid says it +is advanced because it’s so long. + +number is 66. + +To make my day complete, he +offered to give me his autograph +and he shook my hand. I wished +him luck on the game that he +would be playing on Monday +Night Football. He got into his car +and left the parking lot. I couldn’t +stop smiling. Just think – I was +lucky enough to interview a +Redskin. What a thrill! + +### **From a Kid to a Redskin** + + +**by Patrick** + +I stood toe to toe with a giant of +a man. It’s amazing how big +professional football players are. +On Saturday afternoon, Oct. 28, +2000, I went to Redskins Park in +Ashburn, Va., with the hope of +interviewing one of the players. +While waiting in the parking lot for +the players to leave practice, I was +lucky enough to see Michael +Moore, all 6 foot 3 inches tall, +weighing 320 pounds. He approached his car and I went over + + +to see if I could ask him a few +questions. + +He told me how his father got +him interested in football when he +was in the 9th grade. Moore’s +greatest teacher in life is his father. +Moore played offense and defense +at Fayette County High School in +Alabama. He attended college at +Alabama for three years and then +transferred to Troy State. In 2000, +“Mookie,” as he is known to his +fellow players, signed a contract +with the Washington Redskins. +He’s an offensive lineman and his + diff --git a/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/Program.cs b/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/Program.cs new file mode 100644 index 0000000..a2e13a3 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/Program.cs @@ -0,0 +1,66 @@ +using System.Text; +using MuPDF.NET.Examples.Common; +using MuPDF.NET.PDF4LLM; +using MuPDF.NET.PDF4LLM.Llama; + +namespace MuPDF.NET.Examples.PDF4LLM.LlamaMarkdownReader; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.PDF4LLM / 06-LlamaMarkdownReader"); + LlamaMarkdownReader(); + } + + /// + /// Load LlamaIndex-style documents (one per page) via PDFMarkdownReader. + /// + static void LlamaMarkdownReader() + { + string input = ExamplePaths.Pdf4LlmInput("columns.pdf"); + string output = ExamplePaths.Output("MuPDF.NET.PDF4LLM", "06-LlamaMarkdownReader", "llama-docs.txt"); + var check = new ResultCheck("MuPDF.NET.PDF4LLM", "06-LlamaMarkdownReader"); + + bool prior = MuPDF4LLM.UseLayout; + try + { + // Pin classic RAG markdown so the golden Expected/ stays stable. + // (With layout available, UseLayout defaults to true and output changes.) + MuPDF4LLM.SetUseLayout(false); + + // Reader wraps ToMarkdown per page and fills ExtraInfo (page, total_pages, metadata, …). + var reader = new PDFMarkdownReader(); + List docs = reader.LoadData(input); + + var sb = new StringBuilder(); + sb.Append("docCount=").Append(docs.Count).Append('\n'); + for (int i = 0; i < docs.Count; i++) + { + LlamaIndexDocument d = docs[i]; + object? page = null; + d.ExtraInfo?.TryGetValue("page", out page); + + string text = ResultCheck.NormalizeText(d.Text ?? ""); + sb.Append("--- page ").Append(page ?? (i + 1)).Append(" ---\n"); + sb.Append("chars=").Append(text.Length).Append('\n'); + sb.Append(text); + if (!text.EndsWith('\n')) + sb.Append('\n'); + } + + string report = sb.ToString(); + File.WriteAllText(output, report); + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Llama documents: {docs.Count}"); + check.Text(report, "llama-docs.txt"); + } + finally + { + MuPDF4LLM.SetUseLayout(prior); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/README.md b/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/README.md new file mode 100644 index 0000000..195e693 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/README.md @@ -0,0 +1,31 @@ +# 06-LlamaMarkdownReader + +Load LlamaIndex-compatible documents (one per page) via `PDFMarkdownReader`. + +## Sample method + +`LlamaMarkdownReader()` in `Program.cs`. + +## Package + +- [MuPDF.NET.PDF4LLM](https://www.nuget.org/packages/MuPDF.NET.PDF4LLM) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET.PDF4LLM/columns.pdf` | +| Output | `Output/MuPDF.NET.PDF4LLM/06-LlamaMarkdownReader/llama-docs.txt` | +| Expected | `Expected/llama-docs.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET.PDF4LLM\06-LlamaMarkdownReader +``` + +## APIs used + +- `MuPDF4LLM.SetUseLayout(false)` — classic RAG markdown (stable Expected/) +- `PDFMarkdownReader.LoadData` +- `LlamaIndexDocument.Text` / `ExtraInfo` diff --git a/MuPDF.NET.PDF4LLM/07-GetKeyValues/07-GetKeyValues.csproj b/MuPDF.NET.PDF4LLM/07-GetKeyValues/07-GetKeyValues.csproj new file mode 100644 index 0000000..d24fd1d --- /dev/null +++ b/MuPDF.NET.PDF4LLM/07-GetKeyValues/07-GetKeyValues.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.PDF4LLM.GetKeyValues + 07-GetKeyValues + + + + + + diff --git a/MuPDF.NET.PDF4LLM/07-GetKeyValues/Expected/keyvalues.txt b/MuPDF.NET.PDF4LLM/07-GetKeyValues/Expected/keyvalues.txt new file mode 100644 index 0000000..752ca7e --- /dev/null +++ b/MuPDF.NET.PDF4LLM/07-GetKeyValues/Expected/keyvalues.txt @@ -0,0 +1,48 @@ +fieldCount=47 +acknowledgeOther pages=System.Collections.Generic.List`1[System.Int32];value= +amountAndDate pages=System.Collections.Generic.List`1[System.Int32];value= +certificate pages=System.Collections.Generic.List`1[System.Int32];value= +claimNo pages=System.Collections.Generic.List`1[System.Int32];value=EC202307310 +clueDetail pages=System.Collections.Generic.List`1[System.Int32];value= +conclusion pages=System.Collections.Generic.List`1[System.Int32];value= +currentDetail pages=System.Collections.Generic.List`1[System.Int32];value= +currentMoney pages=System.Collections.Generic.List`1[System.Int32];value= +currentMoneyId pages=System.Collections.Generic.List`1[System.Int32];value=USD +debt pages=System.Collections.Generic.List`1[System.Int32];value= +debtor pages=System.Collections.Generic.List`1[System.Int32];value=KLIMA-THERM SP. Z O.O. +defaultReason_1 pages=System.Collections.Generic.List`1[System.Int32];value= +defaultReason_2 pages=System.Collections.Generic.List`1[System.Int32];value= +defaultReason_3 pages=System.Collections.Generic.List`1[System.Int32];value= +defaultReason_4 pages=System.Collections.Generic.List`1[System.Int32];value= +defaultReason_5 pages=System.Collections.Generic.List`1[System.Int32];value= +difficultReason pages=System.Collections.Generic.List`1[System.Int32];value= +disputeReason pages=System.Collections.Generic.List`1[System.Int32];value= +entrustMoney pages=System.Collections.Generic.List`1[System.Int32];value=20,348,526.10 +feedbackTime pages=System.Collections.Generic.List`1[System.Int32];value= +fluctuationReason pages=System.Collections.Generic.List`1[System.Int32];value= +fraudFollows pages=System.Collections.Generic.List`1[System.Int32];value= +ifAcknowledge pages=System.Collections.Generic.List`1[System.Int32];value= +ifHaveClue pages=System.Collections.Generic.List`1[System.Int32];value= +ifReceived pages=System.Collections.Generic.List`1[System.Int32];value= +investigationAgency pages=System.Collections.Generic.List`1[System.Int32];value=IA Group B.V +makeSureWay pages=System.Collections.Generic.List`1[System.Int32];value= +moneyFollows pages=System.Collections.Generic.List`1[System.Int32];value= +moneyId pages=System.Collections.Generic.List`1[System.Int32];value=EUR +other pages=System.Collections.Generic.List`1[System.Int32];value= +otherReason pages=System.Collections.Generic.List`1[System.Int32];value= +partlyDetail pages=System.Collections.Generic.List`1[System.Int32];value= +placementDate pages=System.Collections.Generic.List`1[System.Int32];value=2023-11-09 +plan pages=System.Collections.Generic.List`1[System.Int32];value= +policyHolder pages=System.Collections.Generic.List`1[System.Int32];value=广东美的楼宇科技有限公司 +problem_1 pages=System.Collections.Generic.List`1[System.Int32];value= +problem_2 pages=System.Collections.Generic.List`1[System.Int32];value= +problem_3 pages=System.Collections.Generic.List`1[System.Int32];value= +proposition pages=System.Collections.Generic.List`1[System.Int32];value= +receiveOther pages=System.Collections.Generic.List`1[System.Int32];value= +reportDate pages=System.Collections.Generic.List`1[System.Int32];value= +substance pages=System.Collections.Generic.List`1[System.Int32];value= +surveyFind_1 pages=System.Collections.Generic.List`1[System.Int32];value= +surveyFind_2 pages=System.Collections.Generic.List`1[System.Int32];value= +surveyFind_3 pages=System.Collections.Generic.List`1[System.Int32];value= +trustNoticeNo pages=System.Collections.Generic.List`1[System.Int32];value=EC202307310-CFTD-01 +图像1_af_image pages=System.Collections.Generic.List`1[System.Int32];value= diff --git a/MuPDF.NET.PDF4LLM/07-GetKeyValues/Program.cs b/MuPDF.NET.PDF4LLM/07-GetKeyValues/Program.cs new file mode 100644 index 0000000..25e00a8 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/07-GetKeyValues/Program.cs @@ -0,0 +1,58 @@ +using System.Linq; +using System.Text; +using MuPDF.NET.Examples.Common; +using MuPDF.NET.PDF4LLM; + +namespace MuPDF.NET.Examples.PDF4LLM.GetKeyValues; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.PDF4LLM / 07-GetKeyValues"); + GetKeyValues(); + } + + /// + /// Extract AcroForm field names/values via MuPDF4LLM.GetKeyValues. + /// + static void GetKeyValues() + { + string input = ExamplePaths.Pdf4LlmInput("Widget.pdf"); + string output = ExamplePaths.Output("MuPDF.NET.PDF4LLM", "07-GetKeyValues", "keyvalues.txt"); + var check = new ResultCheck("MuPDF.NET.PDF4LLM", "07-GetKeyValues"); + + // Returns fieldName → property bag (value, page, …) for interactive form fields. + Dictionary> fields = + MuPDF4LLM.GetKeyValues(input, includeXrefs: false); + + var sb = new StringBuilder(); + sb.Append("fieldCount=").Append(fields.Count).Append('\n'); + foreach (string name in fields.Keys.OrderBy(k => k, StringComparer.Ordinal)) + { + Dictionary info = fields[name]; + // Property keys may vary slightly by form; dump a stable sorted view. + string props = string.Join( + ";", + info.OrderBy(kv => kv.Key, StringComparer.Ordinal) + .Select(kv => kv.Key + "=" + FormatValue(kv.Value))); + sb.Append(name).Append('\t').Append(props).Append('\n'); + } + + string text = sb.ToString(); + File.WriteAllText(output, text); + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Form fields: {fields.Count}"); + check.Text(text, "keyvalues.txt"); + check.Finish(); + } + + static string FormatValue(object? value) + { + if (value == null) + return ""; + string s = value.ToString() ?? ""; + return s.Replace('\t', ' ').Replace('\n', ' ').Replace('\r', ' '); + } +} diff --git a/MuPDF.NET.PDF4LLM/07-GetKeyValues/README.md b/MuPDF.NET.PDF4LLM/07-GetKeyValues/README.md new file mode 100644 index 0000000..a2da102 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/07-GetKeyValues/README.md @@ -0,0 +1,29 @@ +# 07-GetKeyValues + +Extract AcroForm field names and values via `MuPDF4LLM.GetKeyValues`. + +## Sample method + +`GetKeyValues()` in `Program.cs`. + +## Package + +- [MuPDF.NET.PDF4LLM](https://www.nuget.org/packages/MuPDF.NET.PDF4LLM) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET.PDF4LLM/Widget.pdf` | +| Output | `Output/MuPDF.NET.PDF4LLM/07-GetKeyValues/keyvalues.txt` | +| Expected | `Expected/keyvalues.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET.PDF4LLM\07-GetKeyValues +``` + +## APIs used + +- `MuPDF4LLM.GetKeyValues` diff --git a/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/08-MarkdownToPdf.csproj b/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/08-MarkdownToPdf.csproj new file mode 100644 index 0000000..adfd25b --- /dev/null +++ b/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/08-MarkdownToPdf.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.PDF4LLM.MarkdownToPdf + 08-MarkdownToPdf + + + + + + diff --git a/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/Expected/sample.summary.txt b/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/Expected/sample.summary.txt new file mode 100644 index 0000000..c8b1875 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/Expected/sample.summary.txt @@ -0,0 +1,2 @@ +pageCount=1 +textSha256=d23a3a685e289bbe8b553080f9f6134ef2232be368413792ae613760e46a105e diff --git a/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/Program.cs b/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/Program.cs new file mode 100644 index 0000000..4754699 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/Program.cs @@ -0,0 +1,32 @@ +using MuPDF.NET.Examples.Common; +using MuPDF.NET.PDF4LLM; + +namespace MuPDF.NET.Examples.PDF4LLM.MarkdownToPdf; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET.PDF4LLM / 08-MarkdownToPdf"); + MarkdownToPdf(); + } + + /// + /// Render a Markdown file to PDF (Story). + /// + static void MarkdownToPdf() + { + string input = ExamplePaths.Pdf4LlmInput("sample.md"); + string output = ExamplePaths.Output("MuPDF.NET.PDF4LLM", "08-MarkdownToPdf", "sample.pdf"); + var check = new ResultCheck("MuPDF.NET.PDF4LLM", "08-MarkdownToPdf"); + + // When outputPath is set, the PDF is written to disk (return value is null). + MuPDF4LLM.MarkdownToPdf(input, outputPath: output); + + ConsoleEx.Info($"Markdown: {input}"); + ConsoleEx.Info($"Wrote PDF: {output}"); + check.Properties(PdfFingerprint.FromFile(output), "sample.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/README.md b/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/README.md new file mode 100644 index 0000000..a2de073 --- /dev/null +++ b/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/README.md @@ -0,0 +1,29 @@ +# 08-MarkdownToPdf + +Render a Markdown file to PDF using Story. + +## Sample method + +`MarkdownToPdf()` in `Program.cs`. + +## Package + +- [MuPDF.NET.PDF4LLM](https://www.nuget.org/packages/MuPDF.NET.PDF4LLM) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET.PDF4LLM/sample.md` | +| Output | `Output/MuPDF.NET.PDF4LLM/08-MarkdownToPdf/sample.pdf` | +| Expected | `Expected/sample.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET.PDF4LLM\08-MarkdownToPdf +``` + +## APIs used + +- `MuPDF4LLM.MarkdownToPdf` diff --git a/MuPDF.NET/01-OpenSave/01-OpenSave.csproj b/MuPDF.NET/01-OpenSave/01-OpenSave.csproj new file mode 100644 index 0000000..ef6c784 --- /dev/null +++ b/MuPDF.NET/01-OpenSave/01-OpenSave.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.OpenSave + 01-OpenSave + + + + + + diff --git a/MuPDF.NET/01-OpenSave/Expected/sample-copy.summary.txt b/MuPDF.NET/01-OpenSave/Expected/sample-copy.summary.txt new file mode 100644 index 0000000..cf4c51b --- /dev/null +++ b/MuPDF.NET/01-OpenSave/Expected/sample-copy.summary.txt @@ -0,0 +1,2 @@ +pageCount=3 +textSha256=31804ecc4c72047b41012fce71e3e0a203864fe1587c3283ad6ea34ee1ec41dc diff --git a/MuPDF.NET/01-OpenSave/Program.cs b/MuPDF.NET/01-OpenSave/Program.cs new file mode 100644 index 0000000..9eb4bfe --- /dev/null +++ b/MuPDF.NET/01-OpenSave/Program.cs @@ -0,0 +1,39 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.OpenSave; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 01-OpenSave"); + OpenSave(); + } + + /// + /// Open a PDF and save a copy. Copy this method into your project and adjust paths. + /// + static void OpenSave() + { + // Input fixture and output path used by this examples solution. + string input = ExamplePaths.MuPdfNetInput("sample.pdf"); + string output = ExamplePaths.Output("MuPDF.NET", "01-OpenSave", "sample-copy.pdf"); + var check = new ResultCheck("MuPDF.NET", "01-OpenSave"); + + // Open the document (dispose closes native handles). + using (var doc = Document.Open(input)) + { + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Pages: {doc.PageCount}"); + + // Write a full copy to disk. + doc.Save(output); + } + + // Compare against Expected/ baseline (examples harness — remove when copying). + check.Properties(PdfFingerprint.FromFile(output), "sample-copy.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/01-OpenSave/README.md b/MuPDF.NET/01-OpenSave/README.md new file mode 100644 index 0000000..1ed21fe --- /dev/null +++ b/MuPDF.NET/01-OpenSave/README.md @@ -0,0 +1,32 @@ +# 01-OpenSave + +Open a PDF and write a full copy to disk. + +## Sample method + +`OpenSave()` in `Program.cs` — copy this method into your project and adjust paths. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/sample.pdf` | +| Output | `Output/MuPDF.NET/01-OpenSave/sample-copy.pdf` | +| Expected | `Expected/sample-copy.summary.txt` (page count + text fingerprint) | + +## Run + +```powershell +dotnet run --project MuPDF.NET\01-OpenSave +# refresh baseline: +dotnet run --project MuPDF.NET\01-OpenSave -- --update-expected +``` + +## APIs used + +- `Document.Open` +- `Document.Save` diff --git a/MuPDF.NET/02-PagesMergeSplit/02-PagesMergeSplit.csproj b/MuPDF.NET/02-PagesMergeSplit/02-PagesMergeSplit.csproj new file mode 100644 index 0000000..5a001af --- /dev/null +++ b/MuPDF.NET/02-PagesMergeSplit/02-PagesMergeSplit.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.PagesMergeSplit + 02-PagesMergeSplit + + + + + + diff --git a/MuPDF.NET/02-PagesMergeSplit/Expected/first-page.summary.txt b/MuPDF.NET/02-PagesMergeSplit/Expected/first-page.summary.txt new file mode 100644 index 0000000..090db1d --- /dev/null +++ b/MuPDF.NET/02-PagesMergeSplit/Expected/first-page.summary.txt @@ -0,0 +1,2 @@ +pageCount=1 +textSha256=1ac06642eb1eacf11d13c8c23b5fe174fcfe9cde7ebab52df7bf77903496f830 diff --git a/MuPDF.NET/02-PagesMergeSplit/Expected/merged.summary.txt b/MuPDF.NET/02-PagesMergeSplit/Expected/merged.summary.txt new file mode 100644 index 0000000..de5a795 --- /dev/null +++ b/MuPDF.NET/02-PagesMergeSplit/Expected/merged.summary.txt @@ -0,0 +1,2 @@ +pageCount=4 +textSha256=1f551d28a8a765d22f474b2f6279d6963b34aea67443df50267c6c8c7351c22a diff --git a/MuPDF.NET/02-PagesMergeSplit/Program.cs b/MuPDF.NET/02-PagesMergeSplit/Program.cs new file mode 100644 index 0000000..5c86900 --- /dev/null +++ b/MuPDF.NET/02-PagesMergeSplit/Program.cs @@ -0,0 +1,51 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.PagesMergeSplit; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 02-PagesMergeSplit"); + PagesMergeSplit(); + } + + /// + /// Merge pages from two PDFs, then extract the first page. + /// + static void PagesMergeSplit() + { + string a = ExamplePaths.MuPdfNetInput("sample.pdf"); + string b = ExamplePaths.MuPdfNetInput("Blank.pdf"); + string merged = ExamplePaths.Output("MuPDF.NET", "02-PagesMergeSplit", "merged.pdf"); + string firstPage = ExamplePaths.Output("MuPDF.NET", "02-PagesMergeSplit", "first-page.pdf"); + var check = new ResultCheck("MuPDF.NET", "02-PagesMergeSplit"); + + // Open both source PDFs. + using (var docA = Document.Open(a)) + using (var docB = Document.Open(b)) + { + ConsoleEx.Info($"A pages={docA.PageCount}, B pages={docB.PageCount}"); + + // Insert page 0 of B into A at position 1 (0-based startAt). + docA.InsertPdf(docB, fromPage: 0, toPage: 0, startAt: 1); + docA.Save(merged); + ConsoleEx.Info($"Merged page count: {docA.PageCount}"); + } + + // Split: copy only the first page of the merged file into a new document. + using (var src = Document.Open(merged)) + using (var one = new Document()) + { + one.InsertPdf(src, fromPage: 0, toPage: 0); + one.Save(firstPage); + } + + // Examples harness baselines. + check.Properties(PdfFingerprint.FromFile(merged), "merged.summary.txt"); + check.Properties(PdfFingerprint.FromFile(firstPage), "first-page.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/02-PagesMergeSplit/README.md b/MuPDF.NET/02-PagesMergeSplit/README.md new file mode 100644 index 0000000..46d57aa --- /dev/null +++ b/MuPDF.NET/02-PagesMergeSplit/README.md @@ -0,0 +1,30 @@ +# 02-PagesMergeSplit + +Merge pages from two PDFs, then extract the first page into a new document. + +## Sample method + +`PagesMergeSplit()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/sample.pdf`, `Blank.pdf` | +| Output | `merged.pdf`, `first-page.pdf` under `Output/MuPDF.NET/02-PagesMergeSplit/` | +| Expected | `merged.summary.txt`, `first-page.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\02-PagesMergeSplit +``` + +## APIs used + +- `Document.InsertPdf` +- `Document.Save` diff --git a/MuPDF.NET/03-RenderPixmap/03-RenderPixmap.csproj b/MuPDF.NET/03-RenderPixmap/03-RenderPixmap.csproj new file mode 100644 index 0000000..941ccba --- /dev/null +++ b/MuPDF.NET/03-RenderPixmap/03-RenderPixmap.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.RenderPixmap + 03-RenderPixmap + + + + + + diff --git a/MuPDF.NET/03-RenderPixmap/Expected/page-1.png.sha256 b/MuPDF.NET/03-RenderPixmap/Expected/page-1.png.sha256 new file mode 100644 index 0000000..97f8046 --- /dev/null +++ b/MuPDF.NET/03-RenderPixmap/Expected/page-1.png.sha256 @@ -0,0 +1 @@ +d2a5ba67a2fff1abb52aaa1ac3ad38babd4de2a82f0c5ba6487ede64bcb49507 diff --git a/MuPDF.NET/03-RenderPixmap/Program.cs b/MuPDF.NET/03-RenderPixmap/Program.cs new file mode 100644 index 0000000..443e8fc --- /dev/null +++ b/MuPDF.NET/03-RenderPixmap/Program.cs @@ -0,0 +1,41 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.RenderPixmap; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 03-RenderPixmap"); + RenderPixmap(); + } + + /// + /// Render page 1 to a PNG pixmap at 2× zoom. + /// + static void RenderPixmap() + { + string input = ExamplePaths.MuPdfNetInput("sample.pdf"); + string output = ExamplePaths.Output("MuPDF.NET", "03-RenderPixmap", "page-1.png"); + var check = new ResultCheck("MuPDF.NET", "03-RenderPixmap"); + + // Open document and first page. + using (var doc = Document.Open(input)) + using (Page page = doc[0]) + // Matrix(2,2) = 2× resolution (about 144 dpi from a 72 dpi page). + using (Pixmap pix = page.GetPixmap(matrix: new Matrix(2, 2))) + { + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Page 1 size: {page.Rect.Width:0.#} x {page.Rect.Height:0.#}"); + + // Save the rendered bitmap as PNG. + pix.Save(output); + } + + // Examples harness: compare PNG by SHA-256. + check.FileSha256(output, "page-1.png.sha256"); + check.Finish(); + } +} diff --git a/MuPDF.NET/03-RenderPixmap/README.md b/MuPDF.NET/03-RenderPixmap/README.md new file mode 100644 index 0000000..4008175 --- /dev/null +++ b/MuPDF.NET/03-RenderPixmap/README.md @@ -0,0 +1,30 @@ +# 03-RenderPixmap + +Render page 1 to a PNG pixmap at 2× zoom. + +## Sample method + +`RenderPixmap()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/sample.pdf` | +| Output | `Output/MuPDF.NET/03-RenderPixmap/page-1.png` | +| Expected | `Expected/page-1.png.sha256` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\03-RenderPixmap +``` + +## APIs used + +- `Page.GetPixmap` with `Matrix(2, 2)` +- `Pixmap.Save` diff --git a/MuPDF.NET/04-TextExtractSearch/04-TextExtractSearch.csproj b/MuPDF.NET/04-TextExtractSearch/04-TextExtractSearch.csproj new file mode 100644 index 0000000..a505fd5 --- /dev/null +++ b/MuPDF.NET/04-TextExtractSearch/04-TextExtractSearch.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.TextExtractSearch + 04-TextExtractSearch + + + + + + diff --git a/MuPDF.NET/04-TextExtractSearch/Expected/page-1.txt b/MuPDF.NET/04-TextExtractSearch/Expected/page-1.txt new file mode 100644 index 0000000..8cc5107 --- /dev/null +++ b/MuPDF.NET/04-TextExtractSearch/Expected/page-1.txt @@ -0,0 +1,36 @@ +Datei: +Hydraulik: +IBN-Code: +ait-deutschland GmbH +D-95359 Kasendorf +Industriestraße 3 +SEITE_1/3 +TRL +ext. +HK / KK +HUP +TB1 +FP1 +HK / KK +TW +MK1 +XXX +WTPSK +TA +M +ZUP +BUP +A +B +ZWE +TBW +Hybrox +HSV +TW +TW +TW +Hybrox589713a +Dieses Schema ist ein Anlagenbeispiel ohne Absperr- und Sicherheitseinrichtungen, was +die fachliche Planung vor Ort nicht ersetzt. +Alle regionalen Normen, Gesetze und Vorschriften sind dabei einzuhalten. Die +Rohrdimension muss planerisch ermittelt werden. diff --git a/MuPDF.NET/04-TextExtractSearch/Expected/search.summary.txt b/MuPDF.NET/04-TextExtractSearch/Expected/search.summary.txt new file mode 100644 index 0000000..3840f99 --- /dev/null +++ b/MuPDF.NET/04-TextExtractSearch/Expected/search.summary.txt @@ -0,0 +1,2 @@ +hitCount=1 +needle=Hydraulik diff --git a/MuPDF.NET/04-TextExtractSearch/Program.cs b/MuPDF.NET/04-TextExtractSearch/Program.cs new file mode 100644 index 0000000..a4709ed --- /dev/null +++ b/MuPDF.NET/04-TextExtractSearch/Program.cs @@ -0,0 +1,50 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.TextExtractSearch; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 04-TextExtractSearch"); + TextExtractSearch(); + } + + /// + /// Extract plain text from page 1 and search for a string. + /// + static void TextExtractSearch() + { + string input = ExamplePaths.MuPdfNetInput("sample.pdf"); + const string needle = "Hydraulik"; + var check = new ResultCheck("MuPDF.NET", "04-TextExtractSearch"); + + using (var doc = Document.Open(input)) + using (Page page = doc[0]) + { + // "text" = plain text extraction (also: "blocks", "words", "html", "dict", …). + string text = page.GetText("text") ?? ""; + + // SearchFor returns hit rectangles on the page. + var hits = page.SearchFor(needle); + + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Page 1 characters: {text.Length}"); + ConsoleEx.Info($"SearchFor(\"{needle}\") hits: {hits.Count}"); + + // Examples harness baselines. + check.Text(text, "page-1.txt"); + check.Properties( + new Dictionary + { + ["needle"] = needle, + ["hitCount"] = hits.Count.ToString(), + }, + "search.summary.txt"); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET/04-TextExtractSearch/README.md b/MuPDF.NET/04-TextExtractSearch/README.md new file mode 100644 index 0000000..d7227d4 --- /dev/null +++ b/MuPDF.NET/04-TextExtractSearch/README.md @@ -0,0 +1,29 @@ +# 04-TextExtractSearch + +Extract plain text from page 1 and search for a string (`Hydraulik`). + +## Sample method + +`TextExtractSearch()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/sample.pdf` | +| Expected | `page-1.txt`, `search.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\04-TextExtractSearch +``` + +## APIs used + +- `Page.GetText("text")` +- `Page.SearchFor` diff --git a/MuPDF.NET/05-Recolor/05-Recolor.csproj b/MuPDF.NET/05-Recolor/05-Recolor.csproj new file mode 100644 index 0000000..59ef333 --- /dev/null +++ b/MuPDF.NET/05-Recolor/05-Recolor.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.Recolor + 05-Recolor + + + + + + diff --git a/MuPDF.NET/05-Recolor/Expected/recolor.summary.txt b/MuPDF.NET/05-Recolor/Expected/recolor.summary.txt new file mode 100644 index 0000000..bcbf527 --- /dev/null +++ b/MuPDF.NET/05-Recolor/Expected/recolor.summary.txt @@ -0,0 +1,6 @@ +csAfter=ICCBased +csBefore=DeviceRGB +extractComponents=4 +extractCsName=ICCBased(CMYK,Artifex CMYK SWOP Profile) +pageCount=2 +textSha256=3ac66637cc45a40b201f066997325ee6d3f84f219bab5766565a6f3bfe5e5ccf diff --git a/MuPDF.NET/05-Recolor/Program.cs b/MuPDF.NET/05-Recolor/Program.cs new file mode 100644 index 0000000..bab4bc6 --- /dev/null +++ b/MuPDF.NET/05-Recolor/Program.cs @@ -0,0 +1,61 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.Recolor; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 05-Recolor"); + Recolor(); + } + + /// + /// Recolor page images/vectors to DeviceCMYK (4 components). Copy this method into your project. + /// + static void Recolor() + { + string input = ExamplePaths.MuPdfNetInput("Color.pdf"); + string output = ExamplePaths.Output("MuPDF.NET", "05-Recolor", "recolor.pdf"); + var check = new ResultCheck("MuPDF.NET", "05-Recolor"); + + using (var doc = Document.Open(input)) + { + List before = doc.GetPageImages(0); + if (before.Count == 0) + throw new InvalidOperationException("No images on page 1."); + + // GetPageImages: CsName is the PDF /ColorSpace name (e.g. DeviceRGB). + // AltCsName is only set for some alternate spaces — usually empty. + string csBefore = before[0].CsName ?? ""; + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Page 1 image colorspace before: {csBefore}"); + + // Recolor page 0 → 4 components (DeviceCMYK / ICC-based CMYK). + doc.Recolor(0, 4); + + List after = doc.GetPageImages(0); + // Prefer CsName (not AltCsName). Use null/empty coalescing carefully: + // AltCsName is often "" after recolor; `??` does NOT fall through empty strings. + string csAfter = string.IsNullOrEmpty(after[0].CsName) ? "" : after[0].CsName; + + // ExtractImage gives component count + a richer cs-name (includes ICC profile). + ImageInfo extracted = doc.ExtractImage(after[0].Xref); + ConsoleEx.Info($"Page 1 image colorspace after: {csAfter}"); + ConsoleEx.Info($"ExtractImage: n={extracted.ColorSpace}, cs-name={extracted.CsName}"); + + doc.Save(output); + + var props = PdfFingerprint.FromFile(output); + props["csBefore"] = csBefore; + props["csAfter"] = csAfter; + props["extractCsName"] = extracted.CsName ?? ""; + props["extractComponents"] = extracted.ColorSpace.ToString(); + check.Properties(props, "recolor.summary.txt"); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET/05-Recolor/README.md b/MuPDF.NET/05-Recolor/README.md new file mode 100644 index 0000000..2f7f842 --- /dev/null +++ b/MuPDF.NET/05-Recolor/README.md @@ -0,0 +1,40 @@ +# 05-Recolor + +Convert page content to another **device colorspace** with `Document.Recolor` (here: 4 components → CMYK). + +After recolor, `GetPageImages().CsName` is typically `ICCBased` (not `DeviceCMYK`). Use `ExtractImage` for component count (`ColorSpace == 4`) and a richer `CsName` that includes the ICC profile. Do **not** use `AltCsName` for this check — it is usually empty, and `??` does not fall through `""`. + +This is **not** ICC soft-proofing / print separations — for those see [`19-ColorManagement`](../19-ColorManagement/). + +## Sample method + +`Recolor()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/Color.pdf` | +| Output | `Output/MuPDF.NET/05-Recolor/recolor.pdf` | +| Expected | `Expected/recolor.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\05-Recolor +``` + +## APIs used + +- `Document.GetPageImages` +- `Document.Recolor(page, components)` — `1`=Gray, `3`=RGB, `4`=CMYK + +## Related + +| Example | Topic | +|---------|--------| +| [`19-ColorManagement`](../19-ColorManagement/) | ICC profiles, soft proof, separations, overprint | diff --git a/MuPDF.NET/06-StoryHtmlBox/06-StoryHtmlBox.csproj b/MuPDF.NET/06-StoryHtmlBox/06-StoryHtmlBox.csproj new file mode 100644 index 0000000..7b84ca2 --- /dev/null +++ b/MuPDF.NET/06-StoryHtmlBox/06-StoryHtmlBox.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.StoryHtmlBox + 06-StoryHtmlBox + + + + + + diff --git a/MuPDF.NET/06-StoryHtmlBox/Expected/story.summary.txt b/MuPDF.NET/06-StoryHtmlBox/Expected/story.summary.txt new file mode 100644 index 0000000..cca5a4c --- /dev/null +++ b/MuPDF.NET/06-StoryHtmlBox/Expected/story.summary.txt @@ -0,0 +1,3 @@ +pageCount=1 +scale=1 +textSha256=8b2f83baf0e7379c33b469e8a1361771d69ddc8ccceaf9f14ef325e500cee72d diff --git a/MuPDF.NET/06-StoryHtmlBox/Program.cs b/MuPDF.NET/06-StoryHtmlBox/Program.cs new file mode 100644 index 0000000..f1d321d --- /dev/null +++ b/MuPDF.NET/06-StoryHtmlBox/Program.cs @@ -0,0 +1,57 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.StoryHtmlBox; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 06-StoryHtmlBox"); + StoryHtmlBox(); + } + + /// + /// Lay out HTML into a page rectangle via Story / InsertHtmlBox. + /// + static void StoryHtmlBox() + { + string output = ExamplePaths.Output("MuPDF.NET", "06-StoryHtmlBox", "story.pdf"); + var check = new ResultCheck("MuPDF.NET", "06-StoryHtmlBox"); + + // HTML content for the Story engine (CSS inline styles are supported). + const string html = """ + +

MuPDF.NET

+

Story / InsertHtmlbox example for customer samples.

+
    +
  • Headings and paragraphs
  • +
  • Bold inline markup
  • +
  • Simple lists
  • +
+ +"""; + + // Create an empty PDF and a default A4-ish page. + using (var doc = Document.Open()) + { + using Page page = doc.NewPage(); + + // Target rectangle in PDF points (72 pt = 1 inch). + var rect = new Rect(72, 72, 520, 720); + + // scaleLow: 0 = allow shrinking so content fits the box. + (float spare, float scale) = page.InsertHtmlBox(rect, html, scaleLow: 0f); + ConsoleEx.Info($"Inserted HTML box (spareHeight={spare:F1}, scale={scale:F3})"); + + doc.Save(output); + + var props = PdfFingerprint.FromFile(output); + props["scale"] = scale.ToString("0.###", System.Globalization.CultureInfo.InvariantCulture); + check.Properties(props, "story.summary.txt"); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET/06-StoryHtmlBox/README.md b/MuPDF.NET/06-StoryHtmlBox/README.md new file mode 100644 index 0000000..3e9690e --- /dev/null +++ b/MuPDF.NET/06-StoryHtmlBox/README.md @@ -0,0 +1,30 @@ +# 06-StoryHtmlBox + +Lay out HTML into a page rectangle using Story / `InsertHtmlBox`. + +## Sample method + +`StoryHtmlBox()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | HTML string in code (no fixture file) | +| Output | `Output/MuPDF.NET/06-StoryHtmlBox/story.pdf` | +| Expected | `Expected/story.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\06-StoryHtmlBox +``` + +## APIs used + +- `Document.NewPage` +- `Page.InsertHtmlBox` diff --git a/MuPDF.NET/07-AnnotationsRedact/07-AnnotationsRedact.csproj b/MuPDF.NET/07-AnnotationsRedact/07-AnnotationsRedact.csproj new file mode 100644 index 0000000..bf21957 --- /dev/null +++ b/MuPDF.NET/07-AnnotationsRedact/07-AnnotationsRedact.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.AnnotationsRedact + 07-AnnotationsRedact + + + + + + diff --git a/MuPDF.NET/07-AnnotationsRedact/Expected/annotated.summary.txt b/MuPDF.NET/07-AnnotationsRedact/Expected/annotated.summary.txt new file mode 100644 index 0000000..6b385d9 --- /dev/null +++ b/MuPDF.NET/07-AnnotationsRedact/Expected/annotated.summary.txt @@ -0,0 +1,3 @@ +annotCount=2 +pageCount=1 +textSha256=3700b4e919a79e8c3da6ce4aa6b6d45103da3952f41d0f887f83a68acd1f73b3 diff --git a/MuPDF.NET/07-AnnotationsRedact/Program.cs b/MuPDF.NET/07-AnnotationsRedact/Program.cs new file mode 100644 index 0000000..e03a2c7 --- /dev/null +++ b/MuPDF.NET/07-AnnotationsRedact/Program.cs @@ -0,0 +1,61 @@ +using System.Linq; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.AnnotationsRedact; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 07-AnnotationsRedact"); + AnnotationsRedact(); + } + + /// + /// Add text/rect annotations and apply a redaction. + /// + static void AnnotationsRedact() + { + string input = ExamplePaths.MuPdfNetInput("Blank.pdf"); + string output = ExamplePaths.Output("MuPDF.NET", "07-AnnotationsRedact", "annotated.pdf"); + var check = new ResultCheck("MuPDF.NET", "07-AnnotationsRedact"); + + using (var doc = Document.Open(input)) + using (Page page = doc[0]) + { + var rect = new Rect(72, 72, 300, 120); + + // Sticky-note style text annotation. + Annot text = page.AddTextAnnot(new Point(rect.X0, rect.Y0), "Example note"); + text.SetInfo( + content: "Hello from 07-AnnotationsRedact", + title: "MuPDF.NET.Examples", + creationDate: null, + modDate: null, + subject: null); + text.Update(); // rebuild appearance stream + + // Rectangle markup annotation (red stroke). + Annot box = page.AddRectAnnot(rect); + box.SetColors(stroke: new[] { 1f, 0f, 0f }); + box.Update(); + + // Redaction: mark area, then ApplyRedactions() permanently removes content. + Annot redact = page.AddRedactAnnot(new Rect(72, 200, 250, 230), text: "REDACTED"); + redact.Update(); + page.ApplyRedactions(); + + int annotCount = page.Annots().Count(); + ConsoleEx.Info($"Page annots remaining: {annotCount}"); + doc.Save(output); + + var props = PdfFingerprint.FromFile(output); + props["annotCount"] = annotCount.ToString(); + check.Properties(props, "annotated.summary.txt"); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET/07-AnnotationsRedact/README.md b/MuPDF.NET/07-AnnotationsRedact/README.md new file mode 100644 index 0000000..9912387 --- /dev/null +++ b/MuPDF.NET/07-AnnotationsRedact/README.md @@ -0,0 +1,31 @@ +# 07-AnnotationsRedact + +Add a text note, a rectangle annotation, and apply a redaction. + +## Sample method + +`AnnotationsRedact()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/Blank.pdf` | +| Output | `Output/MuPDF.NET/07-AnnotationsRedact/annotated.pdf` | +| Expected | `Expected/annotated.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\07-AnnotationsRedact +``` + +## APIs used + +- `Page.AddTextAnnot` / `AddRectAnnot` / `AddRedactAnnot` +- `Annot.Update` +- `Page.ApplyRedactions` diff --git a/MuPDF.NET/08-FormWidgets/08-FormWidgets.csproj b/MuPDF.NET/08-FormWidgets/08-FormWidgets.csproj new file mode 100644 index 0000000..85f5b7d --- /dev/null +++ b/MuPDF.NET/08-FormWidgets/08-FormWidgets.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.FormWidgets + 08-FormWidgets + + + + + + diff --git a/MuPDF.NET/08-FormWidgets/Expected/widgets.txt b/MuPDF.NET/08-FormWidgets/Expected/widgets.txt new file mode 100644 index 0000000..07663cf --- /dev/null +++ b/MuPDF.NET/08-FormWidgets/Expected/widgets.txt @@ -0,0 +1,21 @@ +widgetCount=20 +acknowledgeOther Text +claimNo Text EC202307310 +debtor Text KLIMA-THERM SP. Z O.O. +entrustMoney Text 20,348,526.10 +ifAcknowledge RadioButton +ifAcknowledge RadioButton +ifAcknowledge RadioButton +ifAcknowledge RadioButton +ifReceived RadioButton +ifReceived RadioButton +ifReceived RadioButton +ifReceived RadioButton +ifReceived RadioButton +investigationAgency Text IA Group B.V +moneyId Text EUR +partlyDetail Text +placementDate Text 2023-11-09 +policyHolder Text 广东美的楼宇科技有限公司 +reportDate Text +trustNoticeNo Text EC202307310-CFTD-01 diff --git a/MuPDF.NET/08-FormWidgets/Program.cs b/MuPDF.NET/08-FormWidgets/Program.cs new file mode 100644 index 0000000..dbe5af5 --- /dev/null +++ b/MuPDF.NET/08-FormWidgets/Program.cs @@ -0,0 +1,49 @@ +using System.Linq; +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.FormWidgets; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 08-FormWidgets"); + FormWidgets(); + } + + /// + /// List AcroForm widgets on page 1 (name, type, value). + /// + static void FormWidgets() + { + string input = ExamplePaths.MuPdfNetInput("Widget.pdf"); + var check = new ResultCheck("MuPDF.NET", "08-FormWidgets"); + + using var doc = Document.Open(input); + using Page page = doc[0]; + + // Widgets() walks the page's form fields. + var widgets = page.Widgets().ToList(); + + var sb = new StringBuilder(); + sb.Append("widgetCount=").Append(widgets.Count).Append('\n'); + foreach (Widget w in widgets.OrderBy(w => w.FieldName ?? "", StringComparer.Ordinal)) + { + // FieldTypeString is human-readable (Text, CheckBox, …). + sb.Append(w.FieldName ?? "(unnamed)") + .Append('\t') + .Append(w.FieldTypeString) + .Append('\t') + .Append(w.FieldValue ?? "") + .Append('\n'); + } + + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Widgets on page 1: {widgets.Count}"); + check.Text(sb.ToString(), "widgets.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/08-FormWidgets/README.md b/MuPDF.NET/08-FormWidgets/README.md new file mode 100644 index 0000000..2efd671 --- /dev/null +++ b/MuPDF.NET/08-FormWidgets/README.md @@ -0,0 +1,29 @@ +# 08-FormWidgets + +List AcroForm widgets on page 1 (field name, type, value). + +## Sample method + +`FormWidgets()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/Widget.pdf` | +| Expected | `Expected/widgets.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\08-FormWidgets +``` + +## APIs used + +- `Page.Widgets` +- `Widget.FieldName` / `FieldTypeString` / `FieldValue` diff --git a/MuPDF.NET/09-InsertImage/09-InsertImage.csproj b/MuPDF.NET/09-InsertImage/09-InsertImage.csproj new file mode 100644 index 0000000..d01c923 --- /dev/null +++ b/MuPDF.NET/09-InsertImage/09-InsertImage.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.InsertImage + 09-InsertImage + + + + + + diff --git a/MuPDF.NET/09-InsertImage/Expected/with-logo.summary.txt b/MuPDF.NET/09-InsertImage/Expected/with-logo.summary.txt new file mode 100644 index 0000000..00c75e4 --- /dev/null +++ b/MuPDF.NET/09-InsertImage/Expected/with-logo.summary.txt @@ -0,0 +1,3 @@ +imageFile=logo.png +pageCount=1 +textSha256=01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b diff --git a/MuPDF.NET/09-InsertImage/Program.cs b/MuPDF.NET/09-InsertImage/Program.cs new file mode 100644 index 0000000..0e7f26d --- /dev/null +++ b/MuPDF.NET/09-InsertImage/Program.cs @@ -0,0 +1,44 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.InsertImage; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 09-InsertImage"); + InsertImage(); + } + + /// + /// Create a page and insert a PNG into a rectangle. + /// + static void InsertImage() + { + string logo = ExamplePaths.MuPdfNetInput("logo.png"); + string output = ExamplePaths.Output("MuPDF.NET", "09-InsertImage", "with-logo.pdf"); + var check = new ResultCheck("MuPDF.NET", "09-InsertImage"); + + // Empty document + custom page size (points). + using (var doc = Document.Open()) + { + using Page page = doc.NewPage(width: 400, height: 300); + + // Destination rectangle for the image on the page (100×100 pt). + var rect = new Rect(40, 40, 140, 140); + + // Insert from file path; returns the image xref number. + int xref = page.InsertImage(rect, filename: logo); + ConsoleEx.Info($"Inserted image xref={xref} from {logo}"); + + doc.Save(output); + } + + var props = PdfFingerprint.FromFile(output); + props["imageFile"] = Path.GetFileName(logo); + check.Properties(props, "with-logo.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/09-InsertImage/README.md b/MuPDF.NET/09-InsertImage/README.md new file mode 100644 index 0000000..730cba8 --- /dev/null +++ b/MuPDF.NET/09-InsertImage/README.md @@ -0,0 +1,38 @@ +# 09-InsertImage + +**Insert** a new image onto a page (`Page.InsertImage`). + +To **swap** an existing image xref, see [`17-ReplaceImage`](../17-ReplaceImage/). + +## Sample method + +`InsertImage()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/logo.png` | +| Output | `Output/MuPDF.NET/09-InsertImage/with-logo.pdf` | +| Expected | `Expected/with-logo.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\09-InsertImage +``` + +## APIs used + +- `Document.NewPage` +- `Page.InsertImage` + +## Related + +| Example | Topic | +|---------|--------| +| [`17-ReplaceImage`](../17-ReplaceImage/) | Replace an existing image by xref | diff --git a/MuPDF.NET/10-OutlineLinks/10-OutlineLinks.csproj b/MuPDF.NET/10-OutlineLinks/10-OutlineLinks.csproj new file mode 100644 index 0000000..7ce2713 --- /dev/null +++ b/MuPDF.NET/10-OutlineLinks/10-OutlineLinks.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.OutlineLinks + 10-OutlineLinks + + + + + + diff --git a/MuPDF.NET/10-OutlineLinks/Expected/outline-links.summary.txt b/MuPDF.NET/10-OutlineLinks/Expected/outline-links.summary.txt new file mode 100644 index 0000000..ed6c353 --- /dev/null +++ b/MuPDF.NET/10-OutlineLinks/Expected/outline-links.summary.txt @@ -0,0 +1,2 @@ +pageCount=2 +textSha256=cc4c56478af5e286e36c36cffe99ac6f12ad5e3bc83b7639d196eb6d939e9232 diff --git a/MuPDF.NET/10-OutlineLinks/Expected/outline-links.txt b/MuPDF.NET/10-OutlineLinks/Expected/outline-links.txt new file mode 100644 index 0000000..2ad5a90 --- /dev/null +++ b/MuPDF.NET/10-OutlineLinks/Expected/outline-links.txt @@ -0,0 +1,5 @@ +1 Overview 1 +1 Details 2 +2 Details subsection 2 +---links--- +p1 1 1 72,200,200,240 diff --git a/MuPDF.NET/10-OutlineLinks/Program.cs b/MuPDF.NET/10-OutlineLinks/Program.cs new file mode 100644 index 0000000..c0cd24b --- /dev/null +++ b/MuPDF.NET/10-OutlineLinks/Program.cs @@ -0,0 +1,108 @@ +using System.Linq; +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.OutlineLinks; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 10-OutlineLinks"); + OutlineLinks(); + } + + /// + /// Build a TOC (bookmarks) and an internal go-to link. + /// + static void OutlineLinks() + { + string output = ExamplePaths.Output("MuPDF.NET", "10-OutlineLinks", "outline-links.pdf"); + var check = new ResultCheck("MuPDF.NET", "10-OutlineLinks"); + + using (var doc = Document.Open()) + { + // Page 1: heading + blue hot-spot rectangle (link target area). + using (Page p0 = doc.NewPage()) + { + var linkFrom = new Rect(72, 200, 200, 240); + var writer = new MuPDF.NET.TextWriter(p0.Rect); + writer.FillTextbox(new Rect(72, 72, 500, 120), "Chapter 1 — Overview", new Font(fontName: "helv")); + writer.FillTextbox(linkFrom, "Go to Chapter 2", new Font(fontName: "helv"), fontSize: 11); + writer.WriteText(p0); + p0.DrawRect(linkFrom, color: new[] { 0f, 0f, 1f }); + } + + // Page 2: second chapter. + using (Page p1 = doc.NewPage()) + { + var writer = new MuPDF.NET.TextWriter(p1.Rect); + writer.FillTextbox(new Rect(72, 72, 500, 120), "Chapter 2 — Details", new Font(fontName: "helv")); + writer.WriteText(p1); + p1.DrawRect(new Rect(72, 200, 200, 240), color: new[] { 0f, 0.5f, 0f }); + } + + // TOC rows: [level, title, 1-based page]. Level 2 nests under the previous level 1. + doc.SetToc(new List + { + new List { 1, "Overview", 1 }, + new List { 1, "Details", 2 }, + new List { 2, "Details subsection", 2 }, + }); + + // Internal link: click the blue rect on page 1 → jump to page 2 (0-based page index). + using (Page p0 = doc[0]) + { + p0.InsertLink(new Dictionary + { + ["kind"] = Constants.LinkGoto, + ["from"] = new Rect(72, 200, 200, 240), // same rect as "Go to Chapter 2" label + ["page"] = 1, + ["to"] = new Point(72, 72), + }); + } + + doc.Save(output); + } + + // Re-open and dump TOC + links for the Expected/ baseline. + using (var doc = Document.Open(output)) + { + var sb = new StringBuilder(); + foreach (var item in doc.GetToc(simple: true)) + sb.Append(item.level).Append('\t').Append(item.title).Append('\t').Append(item.page).Append('\n'); + + sb.Append("---links---\n"); + for (int i = 0; i < doc.PageCount; i++) + { + using Page page = doc[i]; + foreach (LinkInfo link in page.GetLinks().OrderBy(l => l.From?.Y0 ?? 0).ThenBy(l => l.From?.X0 ?? 0)) + { + sb.Append("p").Append(i + 1) + .Append('\t').Append((int)link.Kind) + .Append('\t').Append(link.Page) + .Append('\t').Append(FormatRect(link.From)) + .Append('\n'); + } + } + + string text = sb.ToString(); + File.WriteAllText(ExamplePaths.Output("MuPDF.NET", "10-OutlineLinks", "outline-links.txt"), text); + ConsoleEx.Info($"Wrote: {output}"); + check.Text(text, "outline-links.txt"); + check.Properties(PdfFingerprint.FromFile(output), "outline-links.summary.txt"); + } + + check.Finish(); + } + + static string FormatRect(Rect? r) + { + if (r == null) + return ""; + return string.Create(System.Globalization.CultureInfo.InvariantCulture, + $"{r.X0:0.##},{r.Y0:0.##},{r.X1:0.##},{r.Y1:0.##}"); + } +} diff --git a/MuPDF.NET/10-OutlineLinks/README.md b/MuPDF.NET/10-OutlineLinks/README.md new file mode 100644 index 0000000..131547b --- /dev/null +++ b/MuPDF.NET/10-OutlineLinks/README.md @@ -0,0 +1,31 @@ +# 10-OutlineLinks + +Build a table of contents (bookmarks) and an internal go-to link. + +## Sample method + +`OutlineLinks()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | Created in code (two pages + text) | +| Output | `Output/MuPDF.NET/10-OutlineLinks/outline-links.pdf` | +| Expected | `outline-links.txt`, `outline-links.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\10-OutlineLinks +``` + +## APIs used + +- `TextWriter` / `Font` +- `Document.SetToc` / `GetToc` +- `Page.InsertLink` / `GetLinks` diff --git a/MuPDF.NET/11-Tables/11-Tables.csproj b/MuPDF.NET/11-Tables/11-Tables.csproj new file mode 100644 index 0000000..b09890e --- /dev/null +++ b/MuPDF.NET/11-Tables/11-Tables.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.Tables + 11-Tables + + + + + + diff --git a/MuPDF.NET/11-Tables/Expected/tables.md b/MuPDF.NET/11-Tables/Expected/tables.md new file mode 100644 index 0000000..1bc70e3 --- /dev/null +++ b/MuPDF.NET/11-Tables/Expected/tables.md @@ -0,0 +1,22 @@ +tableCount=1 +table[0] rows=17 cols=7 +|行业名称|指标名称|计量
单位|大型|中型|小型|微型| +|---|---|---|---|---|---|---| +|农、林、牧、渔业|营业收入(Y)|万元|Y≥20000|500≤Y<20000|50≤Y<500|Y<50| +|工业 *|从业人员(X)
营业收入(Y)|人
万元|X≥1000
Y≥40000|300≤X<1000
2000≤Y<40000|20≤X<300
300≤Y<2000|X<20
Y<300| +|建筑业|营业收入(Y)
资产总额(Z)|万元
万元|Y≥80000
Z≥80000|6000≤Y<80000
5000≤Z<80000|300≤Y<6000
300≤Z<5000|Y<300
Z<300| +|批发业|从业人员(X)
营业收入(Y)|人
万元|X≥200
Y≥40000|20≤X<200
5000≤Y<40000|5≤X<20
1000≤Y<5000|X<5
Y<1000| +|零售业|从业人员(X)
营业收入(Y)|人
万元|X≥300
Y≥20000|50≤X<300
500≤Y<20000|10≤X<50
100≤Y<500|X<10
Y<100| +|交通运输业 *|从业人员(X)
营业收入(Y)|人
万元|X≥1000
Y≥30000|300≤X<1000
3000≤Y<30000|20≤X<300
200≤Y<3000|X<20
Y<200| +|仓储业*|从业人员(X)
营业收入(Y)|人
万元|X≥200
Y≥30000|100≤X<200
1000≤Y<30000|20≤X<100
100≤Y<1000|X<20
Y<100| +|邮政业|从业人员(X)
营业收入(Y)|人
万元|X≥1000
Y≥30000|300≤X<1000
2000≤Y<30000|20≤X<300
100≤Y<2000|X<20
Y<100| +|住宿业|从业人员(X)
营业收入(Y)|人
万元|X≥300
Y≥10000|100≤X<300
2000≤Y<10000|10≤X<100
100≤Y<2000|X<10
Y<100| +|餐饮业|从业人员(X)
营业收入(Y)|人
万元|X≥300
Y≥10000|100≤X<300
2000≤Y<10000|10≤X<100
100≤Y<2000|X<10
Y<100| +|信息传输业 *|从业人员(X)
营业收入(Y)|人
万元|X≥2000
Y≥100000|100≤X<2000
1000≤Y<100000|10≤X<100
100≤Y<1000|X<10
Y<100| +|软件和信息技术服务业|从业人员(X)
营业收入(Y)|人
万元|X≥300
Y≥10000|100≤X<300
1000≤Y<10000|10≤X<100
50≤Y<1000|X<10
Y<50| +|房地产开发经营|营业收入(Y)
资产总额(Z)|万元
万元|Y≥200000
Z≥10000|1000≤Y<200000
5000≤Z<10000|100≤Y<1000
2000≤Z<5000|Y<100
Z<2000| +|物业管理|从业人员(X)
营业收入(Y)|人
万元|X≥1000
Y≥5000|300≤X<1000
1000≤Y<5000|100≤X<300
500≤Y<1000|X<100
Y<500| +|租赁和商务服务业|从业人员(X)
资产总额(Z)|人
万元|X≥300
Z≥120000|100≤X<300
8000≤Z<120000|10≤X<100
100≤Z<8000|X<10
Z<100| +|其他未列明行业 *|从业人员(X)|人|X≥300|100≤X<300|10≤X<100|X<10| + + diff --git a/MuPDF.NET/11-Tables/Program.cs b/MuPDF.NET/11-Tables/Program.cs new file mode 100644 index 0000000..8089d10 --- /dev/null +++ b/MuPDF.NET/11-Tables/Program.cs @@ -0,0 +1,60 @@ +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.Tables; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 11-Tables"); + FindTables(); + } + + /// + /// Detect tables on a page and export Markdown. + /// + static void FindTables() + { + string input = ExamplePaths.MuPdfNetInput("err_table.pdf"); + string output = ExamplePaths.Output("MuPDF.NET", "11-Tables", "tables.md"); + var check = new ResultCheck("MuPDF.NET", "11-Tables"); + + using var doc = Document.Open(input); + using Page page = doc[0]; + + // lines_strict uses vector lines as table grid (good for ruled tables). + List
tables = Utils.GetTables( + page, + clip: page.Rect, + vertical_strategy: "lines_strict", + horizontal_strategy: "lines_strict"); + + var sb = new StringBuilder(); + sb.Append("tableCount=").Append(tables.Count).Append('\n'); + for (int i = 0; i < tables.Count; i++) + { + Table t = tables[i]; + sb.Append("table[").Append(i).Append("] rows=").Append(t.RowCount) + .Append(" cols=").Append(t.ColCount).Append('\n'); + try + { + // ToMarkdown builds a GitHub-style markdown table from cell text. + sb.Append(t.ToMarkdown() ?? "").Append('\n'); + } + catch (Exception ex) + { + sb.Append("ToMarkdown failed: ").Append(ex.Message).Append('\n'); + } + } + + string text = sb.ToString(); + File.WriteAllText(output, text); + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Tables found: {tables.Count}"); + check.Text(text, "tables.md"); + check.Finish(); + } +} diff --git a/MuPDF.NET/11-Tables/README.md b/MuPDF.NET/11-Tables/README.md new file mode 100644 index 0000000..8f96b54 --- /dev/null +++ b/MuPDF.NET/11-Tables/README.md @@ -0,0 +1,30 @@ +# 11-Tables + +Detect tables on a page and export them as Markdown. + +## Sample method + +`FindTables()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/err_table.pdf` | +| Output | `Output/MuPDF.NET/11-Tables/tables.md` | +| Expected | `Expected/tables.md` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\11-Tables +``` + +## APIs used + +- `Utils.GetTables` (`lines_strict` strategy) +- `Table.ToMarkdown` diff --git a/MuPDF.NET/12-Barcodes/12-Barcodes.csproj b/MuPDF.NET/12-Barcodes/12-Barcodes.csproj new file mode 100644 index 0000000..916a8bb --- /dev/null +++ b/MuPDF.NET/12-Barcodes/12-Barcodes.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.Barcodes + 12-Barcodes + + + + + + diff --git a/MuPDF.NET/12-Barcodes/Expected/barcodes.txt b/MuPDF.NET/12-Barcodes/Expected/barcodes.txt new file mode 100644 index 0000000..77509d5 --- /dev/null +++ b/MuPDF.NET/12-Barcodes/Expected/barcodes.txt @@ -0,0 +1,21 @@ +barcodeCount=20 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +OMRSQUARELPATTERN 0 +EAN2 30 +PHARMA 47 +PHARMA 67 +EAN2 80 +QR MuPDF.NET.Examples diff --git a/MuPDF.NET/12-Barcodes/Expected/qr.summary.txt b/MuPDF.NET/12-Barcodes/Expected/qr.summary.txt new file mode 100644 index 0000000..907809d --- /dev/null +++ b/MuPDF.NET/12-Barcodes/Expected/qr.summary.txt @@ -0,0 +1,2 @@ +pageCount=1 +textSha256=01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b diff --git a/MuPDF.NET/12-Barcodes/Program.cs b/MuPDF.NET/12-Barcodes/Program.cs new file mode 100644 index 0000000..87c4b7e --- /dev/null +++ b/MuPDF.NET/12-Barcodes/Program.cs @@ -0,0 +1,55 @@ +using System.Linq; +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.Barcodes; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 12-Barcodes"); + WriteAndReadBarcode(); + } + + /// + /// Write a QR code to a PDF, then read it back. + /// + static void WriteAndReadBarcode() + { + const string payload = "MuPDF.NET.Examples"; + string output = ExamplePaths.Output("MuPDF.NET", "12-Barcodes", "qr.pdf"); + var check = new ResultCheck("MuPDF.NET", "12-Barcodes"); + + // Create a small page and draw a QR into the given rectangle. + using (var doc = Document.Open()) + { + using Page page = doc.NewPage(width: 300, height: 300); + var rect = new Rect(40, 40, 260, 260); + page.WriteBarcode(rect, payload, BarcodeFormat.QR, forceFitToRect: true, pureBarcode: true); + doc.Save(output); + } + + // Re-open and decode barcodes on the page. + using (var doc = Document.Open(output)) + using (Page page = doc[0]) + { + List found = page.ReadBarcodes().ToList(); + var sb = new StringBuilder(); + sb.Append("barcodeCount=").Append(found.Count).Append('\n'); + foreach (Barcode b in found.OrderBy(b => b.Text ?? "", StringComparer.Ordinal)) + { + sb.Append(b.BarcodeFormat).Append('\t').Append(b.Text ?? "").Append('\n'); + } + + ConsoleEx.Info($"Wrote QR for: {payload}"); + ConsoleEx.Info($"Read back: {found.Count} barcode(s)"); + check.Text(sb.ToString(), "barcodes.txt"); + check.Properties(PdfFingerprint.FromFile(output), "qr.summary.txt"); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET/12-Barcodes/README.md b/MuPDF.NET/12-Barcodes/README.md new file mode 100644 index 0000000..c1206aa --- /dev/null +++ b/MuPDF.NET/12-Barcodes/README.md @@ -0,0 +1,30 @@ +# 12-Barcodes + +Write a QR code into a PDF, then read it back. + +## Sample method + +`WriteAndReadBarcode()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | Payload string in code (`MuPDF.NET.Examples`) | +| Output | `Output/MuPDF.NET/12-Barcodes/qr.pdf` | +| Expected | `barcodes.txt`, `qr.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\12-Barcodes +``` + +## APIs used + +- `Page.WriteBarcode` (`BarcodeFormat.QR`) +- `Page.ReadBarcodes` diff --git a/MuPDF.NET/13-EmbeddedFiles/13-EmbeddedFiles.csproj b/MuPDF.NET/13-EmbeddedFiles/13-EmbeddedFiles.csproj new file mode 100644 index 0000000..7247696 --- /dev/null +++ b/MuPDF.NET/13-EmbeddedFiles/13-EmbeddedFiles.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.EmbeddedFiles + 13-EmbeddedFiles + + + + + + diff --git a/MuPDF.NET/13-EmbeddedFiles/Expected/embedded.txt b/MuPDF.NET/13-EmbeddedFiles/Expected/embedded.txt new file mode 100644 index 0000000..6b75238 --- /dev/null +++ b/MuPDF.NET/13-EmbeddedFiles/Expected/embedded.txt @@ -0,0 +1,2 @@ +embeddedCount=1 +note.txt note.txt 42 dea46a02effef779f376d9125350202150e989fd8bb66b5ec8310fe332ebcbe7 diff --git a/MuPDF.NET/13-EmbeddedFiles/Expected/with-attachment.summary.txt b/MuPDF.NET/13-EmbeddedFiles/Expected/with-attachment.summary.txt new file mode 100644 index 0000000..75896d0 --- /dev/null +++ b/MuPDF.NET/13-EmbeddedFiles/Expected/with-attachment.summary.txt @@ -0,0 +1,2 @@ +pageCount=1 +textSha256=16647954c146404e2b7418ad84f45ea5430a2b844ae367ccb8ad371b8b8465f5 diff --git a/MuPDF.NET/13-EmbeddedFiles/Program.cs b/MuPDF.NET/13-EmbeddedFiles/Program.cs new file mode 100644 index 0000000..189eff3 --- /dev/null +++ b/MuPDF.NET/13-EmbeddedFiles/Program.cs @@ -0,0 +1,73 @@ +using System.Linq; +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.EmbeddedFiles; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 13-EmbeddedFiles"); + EmbeddedFiles(); + } + + /// + /// Attach a file to a PDF and list embedded attachments. + /// + static void EmbeddedFiles() + { + string blank = ExamplePaths.MuPdfNetInput("Blank.pdf"); + string note = ExamplePaths.MuPdfNetInput("note.txt"); + string output = ExamplePaths.Output("MuPDF.NET", "13-EmbeddedFiles", "with-attachment.pdf"); + var check = new ResultCheck("MuPDF.NET", "13-EmbeddedFiles"); + + byte[] payload = File.ReadAllBytes(note); + const string embName = "note.txt"; + + using (var doc = Document.Open(blank)) + { + // Replace any prior attachment with the same logical name. + if (doc.GetEmbeddedFileNames().Contains(embName)) + doc.DeleteEmbeddedFile(embName); + + // AddEmbeddedFile stores bytes in the PDF EmbeddedFiles name tree. + int xref = doc.AddEmbeddedFile( + name: embName, + buffer: payload, + filename: embName, + uFileName: embName, + desc: "Example attachment"); + + ConsoleEx.Info($"Added embedded file '{embName}' (xref={xref}, {payload.Length} bytes)"); + doc.Save(output, garbage: 4, deflate: 1); + } + + // Verify by listing names and hashing extracted bytes. + using (var doc = Document.Open(output)) + { + var sb = new StringBuilder(); + sb.Append("embeddedCount=").Append(doc.EmbeddedFileCount).Append('\n'); + foreach (string name in doc.GetEmbeddedFileNames().OrderBy(n => n, StringComparer.Ordinal)) + { + var info = doc.GetEmbeddedFileInfo(name); + byte[] data = doc.GetEmbeddedFile(name); + sb.Append(name) + .Append('\t').Append(info.GetValueOrDefault("filename")) + .Append('\t').Append(info.GetValueOrDefault("size") ?? data.Length) + .Append('\t').Append(ResultCheck.Sha256HexBytes(data)) + .Append('\n'); + } + + string text = sb.ToString(); + File.WriteAllText(ExamplePaths.Output("MuPDF.NET", "13-EmbeddedFiles", "embedded.txt"), text); + ConsoleEx.Info($"EmbeddedFileCount: {doc.EmbeddedFileCount}"); + check.Text(text, "embedded.txt"); + check.Properties(PdfFingerprint.FromFile(output), "with-attachment.summary.txt"); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET/13-EmbeddedFiles/README.md b/MuPDF.NET/13-EmbeddedFiles/README.md new file mode 100644 index 0000000..7c2d315 --- /dev/null +++ b/MuPDF.NET/13-EmbeddedFiles/README.md @@ -0,0 +1,30 @@ +# 13-EmbeddedFiles + +Attach a text file to a PDF and list embedded attachments. + +## Sample method + +`EmbeddedFiles()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/Blank.pdf`, `note.txt` | +| Output | `Output/MuPDF.NET/13-EmbeddedFiles/with-attachment.pdf` | +| Expected | `embedded.txt`, `with-attachment.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\13-EmbeddedFiles +``` + +## APIs used + +- `Document.AddEmbeddedFile` / `DeleteEmbeddedFile` +- `Document.GetEmbeddedFileNames` / `GetEmbeddedFile` / `GetEmbeddedFileInfo` diff --git a/MuPDF.NET/14-Metadata/14-Metadata.csproj b/MuPDF.NET/14-Metadata/14-Metadata.csproj new file mode 100644 index 0000000..cefe616 --- /dev/null +++ b/MuPDF.NET/14-Metadata/14-Metadata.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.Metadata + 14-Metadata + + + + + + diff --git a/MuPDF.NET/14-Metadata/Expected/metadata.txt b/MuPDF.NET/14-Metadata/Expected/metadata.txt new file mode 100644 index 0000000..8cbeba9 --- /dev/null +++ b/MuPDF.NET/14-Metadata/Expected/metadata.txt @@ -0,0 +1,22 @@ +---before--- +author= +creationDate=D:20231211141625+01'00' +creator=AutoCAD LT 2023 - Deutsch (German) 2023 (24.2) +encryption= +format=PDF 1.7 +keywords= +modDate=D:20240502120953+02'00' +producer=pdfplot16.hdi 16.02.072.00000 +subject= +title=Schemaalpha +trapped= +---after--- +author=Artifex +creator=MuPDF.NET.Examples +encryption= +format=PDF 1.7 +keywords= +producer=pdfplot16.hdi 16.02.072.00000 +subject=14-Metadata sample +title=MuPDF.NET.Examples — Metadata +trapped= diff --git a/MuPDF.NET/14-Metadata/Expected/with-metadata.summary.txt b/MuPDF.NET/14-Metadata/Expected/with-metadata.summary.txt new file mode 100644 index 0000000..cf4c51b --- /dev/null +++ b/MuPDF.NET/14-Metadata/Expected/with-metadata.summary.txt @@ -0,0 +1,2 @@ +pageCount=3 +textSha256=31804ecc4c72047b41012fce71e3e0a203864fe1587c3283ad6ea34ee1ec41dc diff --git a/MuPDF.NET/14-Metadata/Program.cs b/MuPDF.NET/14-Metadata/Program.cs new file mode 100644 index 0000000..b2bc2bb --- /dev/null +++ b/MuPDF.NET/14-Metadata/Program.cs @@ -0,0 +1,73 @@ +using System.Linq; +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.Metadata; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 14-Metadata"); + Metadata(); + } + + /// + /// Read and update PDF document metadata. + /// + static void Metadata() + { + string input = ExamplePaths.MuPdfNetInput("sample.pdf"); + string output = ExamplePaths.Output("MuPDF.NET", "14-Metadata", "with-metadata.pdf"); + var check = new ResultCheck("MuPDF.NET", "14-Metadata"); + + using (var doc = Document.Open(input)) + { + // MetaData is a dictionary of standard PDF info keys (title, author, …). + Dictionary before = doc.MetaData; + + var sb = new StringBuilder(); + sb.Append("---before---\n"); + foreach (string key in before.Keys.OrderBy(k => k, StringComparer.Ordinal)) + sb.Append(key).Append('=').Append(before[key] ?? "").Append('\n'); + + // SetMetadata replaces/merges the document info dictionary. + doc.SetMetadata(new Dictionary + { + ["title"] = "MuPDF.NET.Examples — Metadata", + ["author"] = "Artifex", + ["subject"] = "14-Metadata sample", + ["creator"] = "MuPDF.NET.Examples", + }); + + doc.Save(output); + + // Re-read from the saved file for a stable dump. + using (var saved = Document.Open(output)) + { + sb.Append("---after---\n"); + foreach (string key in saved.MetaData.Keys.OrderBy(k => k, StringComparer.Ordinal)) + { + // Skip volatile date fields for Expected/ baselines. + if (key.Equals("creationDate", StringComparison.OrdinalIgnoreCase) + || key.Equals("modDate", StringComparison.OrdinalIgnoreCase) + || key.Equals("creationdate", StringComparison.OrdinalIgnoreCase) + || key.Equals("moddate", StringComparison.OrdinalIgnoreCase)) + continue; + sb.Append(key).Append('=').Append(saved.MetaData[key] ?? "").Append('\n'); + } + } + + string text = sb.ToString(); + File.WriteAllText(ExamplePaths.Output("MuPDF.NET", "14-Metadata", "metadata.txt"), text); + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Wrote: {output}"); + check.Text(text, "metadata.txt"); + check.Properties(PdfFingerprint.FromFile(output), "with-metadata.summary.txt"); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET/14-Metadata/README.md b/MuPDF.NET/14-Metadata/README.md new file mode 100644 index 0000000..eddea01 --- /dev/null +++ b/MuPDF.NET/14-Metadata/README.md @@ -0,0 +1,30 @@ +# 14-Metadata + +Read and update PDF document metadata (`title`, `author`, …). + +## Sample method + +`Metadata()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/sample.pdf` | +| Output | `with-metadata.pdf`, `metadata.txt` | +| Expected | `metadata.txt`, `with-metadata.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\14-Metadata +``` + +## APIs used + +- `Document.MetaData` +- `Document.SetMetadata` diff --git a/MuPDF.NET/15-TextWriter/15-TextWriter.csproj b/MuPDF.NET/15-TextWriter/15-TextWriter.csproj new file mode 100644 index 0000000..f965900 --- /dev/null +++ b/MuPDF.NET/15-TextWriter/15-TextWriter.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.TextWriterSample + 15-TextWriter + + + + + + diff --git a/MuPDF.NET/15-TextWriter/Expected/hello.summary.txt b/MuPDF.NET/15-TextWriter/Expected/hello.summary.txt new file mode 100644 index 0000000..fa234a8 --- /dev/null +++ b/MuPDF.NET/15-TextWriter/Expected/hello.summary.txt @@ -0,0 +1,2 @@ +pageCount=1 +textSha256=5acd390f9f8cedcd860d86dd41ebe844b21cd3b356292b7afe3ab399b4eac9c8 diff --git a/MuPDF.NET/15-TextWriter/Program.cs b/MuPDF.NET/15-TextWriter/Program.cs new file mode 100644 index 0000000..f0e8446 --- /dev/null +++ b/MuPDF.NET/15-TextWriter/Program.cs @@ -0,0 +1,42 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.TextWriterSample; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 15-TextWriter"); + TextWriterHello(); + } + + /// + /// Write text onto a new page with TextWriter + Font. + /// + static void TextWriterHello() + { + string output = ExamplePaths.Output("MuPDF.NET", "15-TextWriter", "hello.pdf"); + var check = new ResultCheck("MuPDF.NET", "15-TextWriter"); + + using (var doc = Document.Open()) + { + using Page page = doc.NewPage(); + + // TextWriter lays out text into a rectangle; Font("helv") is the built-in Helvetica. + var writer = new MuPDF.NET.TextWriter(page.Rect); + writer.FillTextbox( + new Rect(72, 72, 500, 200), + "Hello from MuPDF.NET TextWriter!", + new Font(fontName: "helv")); + writer.WriteText(page); + + doc.Save(output); + ConsoleEx.Info($"Wrote: {output}"); + } + + check.Properties(PdfFingerprint.FromFile(output), "hello.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/15-TextWriter/README.md b/MuPDF.NET/15-TextWriter/README.md new file mode 100644 index 0000000..bd5f7fc --- /dev/null +++ b/MuPDF.NET/15-TextWriter/README.md @@ -0,0 +1,29 @@ +# 15-TextWriter + +Write text onto a new page with `TextWriter` and a built-in font. + +## Sample method + +`TextWriterHello()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Output | `Output/MuPDF.NET/15-TextWriter/hello.pdf` | +| Expected | `Expected/hello.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\15-TextWriter +``` + +## APIs used + +- `TextWriter.FillTextbox` / `WriteText` +- `Font` diff --git a/MuPDF.NET/16-DrawShapes/16-DrawShapes.csproj b/MuPDF.NET/16-DrawShapes/16-DrawShapes.csproj new file mode 100644 index 0000000..086f8db --- /dev/null +++ b/MuPDF.NET/16-DrawShapes/16-DrawShapes.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.DrawShapes + 16-DrawShapes + + + + + + diff --git a/MuPDF.NET/16-DrawShapes/Expected/shapes.summary.txt b/MuPDF.NET/16-DrawShapes/Expected/shapes.summary.txt new file mode 100644 index 0000000..907809d --- /dev/null +++ b/MuPDF.NET/16-DrawShapes/Expected/shapes.summary.txt @@ -0,0 +1,2 @@ +pageCount=1 +textSha256=01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b diff --git a/MuPDF.NET/16-DrawShapes/Program.cs b/MuPDF.NET/16-DrawShapes/Program.cs new file mode 100644 index 0000000..b28b45b --- /dev/null +++ b/MuPDF.NET/16-DrawShapes/Program.cs @@ -0,0 +1,50 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.DrawShapes; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 16-DrawShapes"); + DrawShapes(); + } + + /// + /// Draw lines, rectangles, and circles on a page. + /// + static void DrawShapes() + { + string output = ExamplePaths.Output("MuPDF.NET", "16-DrawShapes", "shapes.pdf"); + var check = new ResultCheck("MuPDF.NET", "16-DrawShapes"); + + using (var doc = Document.Open()) + { + using Page page = doc.NewPage(); + + // Dashed horizontal lines (PDF dash pattern "[5] 0"). + page.DrawLine(new Point(72, 100), new Point(500, 100), width: 1f, dashes: "[5] 0"); + page.DrawLine(new Point(72, 130), new Point(500, 130), width: 2f, color: new[] { 1f, 0f, 0f }); + + // Stroke rectangle (blue). + page.DrawRect(new Rect(72, 180, 220, 280), color: new[] { 0f, 0f, 1f }, width: 1.5f); + + // Filled circle (green fill, dark stroke). + page.DrawCircle( + new Point(350, 230), + radius: 40, + color: new[] { 0f, 0f, 0f }, + fill: new[] { 0f, 0.6f, 0f }, + width: 1f); + + doc.Save(output); + ConsoleEx.Info($"Wrote: {output}"); + } + + // Drawing-only PDF may have little extractable text — fingerprint still records pageCount. + check.Properties(PdfFingerprint.FromFile(output), "shapes.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/16-DrawShapes/README.md b/MuPDF.NET/16-DrawShapes/README.md new file mode 100644 index 0000000..06be6ae --- /dev/null +++ b/MuPDF.NET/16-DrawShapes/README.md @@ -0,0 +1,30 @@ +# 16-DrawShapes + +Draw dashed lines, a rectangle, and a filled circle. + +## Sample method + +`DrawShapes()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Output | `Output/MuPDF.NET/16-DrawShapes/shapes.pdf` | +| Expected | `Expected/shapes.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\16-DrawShapes +``` + +## APIs used + +- `Page.DrawLine` +- `Page.DrawRect` +- `Page.DrawCircle` diff --git a/MuPDF.NET/17-ReplaceImage/17-ReplaceImage.csproj b/MuPDF.NET/17-ReplaceImage/17-ReplaceImage.csproj new file mode 100644 index 0000000..75fad15 --- /dev/null +++ b/MuPDF.NET/17-ReplaceImage/17-ReplaceImage.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.ReplaceImage + 17-ReplaceImage + + + + + + diff --git a/MuPDF.NET/17-ReplaceImage/Expected/replaced.summary.txt b/MuPDF.NET/17-ReplaceImage/Expected/replaced.summary.txt new file mode 100644 index 0000000..e7e7bab --- /dev/null +++ b/MuPDF.NET/17-ReplaceImage/Expected/replaced.summary.txt @@ -0,0 +1,3 @@ +pageCount=2 +replacement=logo.png +textSha256=3ac66637cc45a40b201f066997325ee6d3f84f219bab5766565a6f3bfe5e5ccf diff --git a/MuPDF.NET/17-ReplaceImage/Program.cs b/MuPDF.NET/17-ReplaceImage/Program.cs new file mode 100644 index 0000000..7824c7c --- /dev/null +++ b/MuPDF.NET/17-ReplaceImage/Program.cs @@ -0,0 +1,54 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.ReplaceImage; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 17-ReplaceImage"); + ReplaceImage(); + } + + /// + /// Replace the first image on a page with the Artifex logo at its native aspect ratio. + /// + static void ReplaceImage() + { + string input = ExamplePaths.MuPdfNetInput("Color.pdf"); + string replacement = ExamplePaths.MuPdfNetInput("logo.png"); + string output = ExamplePaths.Output("MuPDF.NET", "17-ReplaceImage", "replaced.pdf"); + var check = new ResultCheck("MuPDF.NET", "17-ReplaceImage"); + + using (var doc = Document.Open(input)) + using (Page page = doc[0]) + { + // List images on the page (xref + colorspace, …). + List images = page.GetImages(full: true); + if (images.Count == 0) + throw new InvalidOperationException("No images found on page 1."); + + int xref = images[0].Xref; + List places = page.GetImageRects(xref); + if (places.Count == 0) + throw new InvalidOperationException("Image xref is not drawn on page 1."); + Rect place = places[0].Rect; + + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Replacing image xref={xref} with {Path.GetFileName(replacement)}"); + ConsoleEx.Info($"Placement: {place}"); + + // Clear the old image, then insert into the same rect keeping the logo ratio. + page.DeleteImage(xref); + page.InsertImage(place, filename: replacement, keepProportion: true); + doc.Save(output); + } + + var props = PdfFingerprint.FromFile(output); + props["replacement"] = Path.GetFileName(replacement); + check.Properties(props, "replaced.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/17-ReplaceImage/README.md b/MuPDF.NET/17-ReplaceImage/README.md new file mode 100644 index 0000000..6f5bbfa --- /dev/null +++ b/MuPDF.NET/17-ReplaceImage/README.md @@ -0,0 +1,42 @@ +# 17-ReplaceImage + +**Replace** an existing page image while keeping the replacement’s width/height ratio (`InsertImage` with `keepProportion: true`). + +`Page.ReplaceImage` swaps image bytes but keeps the old draw matrix (it can stretch). This sample clears the old image (`DeleteImage`) and inserts into the same rectangle with `keepProportion: true`. + +To **add** a new image, see [`09-InsertImage`](../09-InsertImage/). + +## Sample method + +`ReplaceImage()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/Color.pdf`, `logo.png` | +| Output | `Output/MuPDF.NET/17-ReplaceImage/replaced.pdf` | +| Expected | `Expected/replaced.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\17-ReplaceImage +``` + +## APIs used + +- `Page.GetImages` +- `Page.GetImageRects` +- `Page.DeleteImage` +- `Page.InsertImage` (`keepProportion: true`) + +## Related + +| Example | Topic | +|---------|--------| +| [`09-InsertImage`](../09-InsertImage/) | Insert a new image onto a page | diff --git a/MuPDF.NET/18-ZugferdEmbedded/18-ZugferdEmbedded.csproj b/MuPDF.NET/18-ZugferdEmbedded/18-ZugferdEmbedded.csproj new file mode 100644 index 0000000..d44128a --- /dev/null +++ b/MuPDF.NET/18-ZugferdEmbedded/18-ZugferdEmbedded.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.ZugferdEmbedded + 18-ZugferdEmbedded + + + + + + diff --git a/MuPDF.NET/18-ZugferdEmbedded/Expected/zugferd-with-xml.summary.txt b/MuPDF.NET/18-ZugferdEmbedded/Expected/zugferd-with-xml.summary.txt new file mode 100644 index 0000000..65d7895 --- /dev/null +++ b/MuPDF.NET/18-ZugferdEmbedded/Expected/zugferd-with-xml.summary.txt @@ -0,0 +1,2 @@ +pageCount=1 +textSha256=163783ee9f85b0ba7aa19bd5a57bb5532b8d85225423ac324b25b6862c14f403 diff --git a/MuPDF.NET/18-ZugferdEmbedded/Expected/zugferd.txt b/MuPDF.NET/18-ZugferdEmbedded/Expected/zugferd.txt new file mode 100644 index 0000000..22f4788 --- /dev/null +++ b/MuPDF.NET/18-ZugferdEmbedded/Expected/zugferd.txt @@ -0,0 +1,4 @@ +embeddedCount=1 +facturX=factur-x.xml +xmlSha256=bc9a945e75127e6721b59e60ee39dd0cd23ecf2088c71a00f15e792747d69061 +roundTripMatch=true diff --git a/MuPDF.NET/18-ZugferdEmbedded/Program.cs b/MuPDF.NET/18-ZugferdEmbedded/Program.cs new file mode 100644 index 0000000..420e3d1 --- /dev/null +++ b/MuPDF.NET/18-ZugferdEmbedded/Program.cs @@ -0,0 +1,88 @@ +using System.Linq; +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.ZugferdEmbedded; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 18-ZugferdEmbedded"); + ZugferdEmbedded(); + } + + /// + /// Extract ZUGFeRD / Factur-X XML from a PDF and re-embed it. + /// + static void ZugferdEmbedded() + { + string pdfPath = ExamplePaths.MuPdfNetInput("zugferd-muster-rechnung.pdf"); + string xmlPath = ExamplePaths.MuPdfNetInput("zugferd-muster-rechnung.xml"); + string extractedOut = ExamplePaths.Output("MuPDF.NET", "18-ZugferdEmbedded", "extracted-factur-x.xml"); + string outputPdf = ExamplePaths.Output("MuPDF.NET", "18-ZugferdEmbedded", "zugferd-with-xml.pdf"); + var check = new ResultCheck("MuPDF.NET", "18-ZugferdEmbedded"); + + const string embName = "factur-x.xml"; + byte[] xmlBytes = File.ReadAllBytes(xmlPath); + + // --- Extract any existing embedded files from the sample invoice PDF --- + using (var doc = Document.Open(pdfPath)) + { + ConsoleEx.Info($"Opened: {pdfPath}"); + ConsoleEx.Info($"EmbeddedFileCount: {doc.EmbeddedFileCount}"); + + foreach (string name in doc.GetEmbeddedFileNames()) + { + byte[] data = doc.GetEmbeddedFile(name); + // Prefer Factur-X / ZUGFeRD XML by name when present. + if (name.Contains("factur", StringComparison.OrdinalIgnoreCase) + || name.EndsWith(".xml", StringComparison.OrdinalIgnoreCase)) + { + File.WriteAllBytes(extractedOut, data); + ConsoleEx.Info($"Extracted '{name}' → {extractedOut} ({data.Length} bytes)"); + } + } + } + + // --- Embed (or replace) the standalone XML into a copy of the PDF --- + using (var doc = Document.Open(pdfPath)) + { + if (doc.GetEmbeddedFileNames().Contains(embName)) + doc.DeleteEmbeddedFile(embName); + + int xref = doc.AddEmbeddedFile( + name: embName, + buffer: xmlBytes, + filename: embName, + uFileName: embName, + desc: "Factur-X / ZUGFeRD XML invoice"); + + ConsoleEx.Info($"Added '{embName}' xref={xref}"); + doc.Save(outputPdf, garbage: 4, deflate: 1); + } + + // Round-trip check + Expected dump. + using (var verify = Document.Open(outputPdf)) + { + byte[] extracted = verify.GetEmbeddedFile(embName); + bool match = extracted.AsSpan().SequenceEqual(xmlBytes); + + var sb = new StringBuilder(); + sb.Append("embeddedCount=").Append(verify.EmbeddedFileCount).Append('\n'); + sb.Append("facturX=").Append(embName).Append('\n'); + sb.Append("xmlSha256=").Append(ResultCheck.Sha256HexBytes(extracted)).Append('\n'); + sb.Append("roundTripMatch=").Append(match ? "true" : "false").Append('\n'); + + string text = sb.ToString(); + File.WriteAllText(ExamplePaths.Output("MuPDF.NET", "18-ZugferdEmbedded", "zugferd.txt"), text); + ConsoleEx.Info(match ? "Round-trip OK" : "Round-trip MISMATCH"); + check.Text(text, "zugferd.txt"); + check.Properties(PdfFingerprint.FromFile(outputPdf), "zugferd-with-xml.summary.txt"); + } + + check.Finish(); + } +} diff --git a/MuPDF.NET/18-ZugferdEmbedded/README.md b/MuPDF.NET/18-ZugferdEmbedded/README.md new file mode 100644 index 0000000..574b432 --- /dev/null +++ b/MuPDF.NET/18-ZugferdEmbedded/README.md @@ -0,0 +1,29 @@ +# 18-ZugferdEmbedded + +Extract and re-embed ZUGFeRD / Factur-X XML using PDF EmbeddedFiles. + +## Sample method + +`ZugferdEmbedded()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `zugferd-muster-rechnung.pdf`, `zugferd-muster-rechnung.xml` | +| Output | `extracted-factur-x.xml`, `zugferd-with-xml.pdf` | +| Expected | `zugferd.txt`, `zugferd-with-xml.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\18-ZugferdEmbedded +``` + +## APIs used + +- `Document.GetEmbeddedFile` / `AddEmbeddedFile` / `DeleteEmbeddedFile` diff --git a/MuPDF.NET/19-ColorManagement/19-ColorManagement.csproj b/MuPDF.NET/19-ColorManagement/19-ColorManagement.csproj new file mode 100644 index 0000000..68cf3fe --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/19-ColorManagement.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.ColorManagement + 19-ColorManagement + + + + + + diff --git a/MuPDF.NET/19-ColorManagement/Expected/color-report.txt b/MuPDF.NET/19-ColorManagement/Expected/color-report.txt new file mode 100644 index 0000000..758c8c8 --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/Expected/color-report.txt @@ -0,0 +1,45 @@ +--- High-performance PDF viewing --- +viewer pages=2 size=1191x1684 n=3 +--- DeviceRGB / DeviceGray / DeviceCMYK --- +device-rgb n=3 cs=DeviceRGB +device-gray n=1 cs=DeviceGray +device-cmyk n=4 cs=DeviceCMYK +--- PDF object inspection --- +catalogXref=29 +xrefLength=30 +outputIntents.type=null +xref=1 stream=False Type=/Font Subtype=/Type1 +xref=2 stream=False Type=/Font Subtype=/Type1 +xref=3 stream=False Type=null Subtype=null +xref=4 stream=False Type=/Pages Subtype=null +xref=5 stream=False Type=/Page Subtype=null +xref=6 stream=False Type=/Page Subtype=null +xref=7 stream=True Type=null Subtype=null +xref=8 stream=True Type=null Subtype=null +--- Zoom and navigation --- +zoom pages=2 links=0 toc=2 size=1191x1684 +--- ICC profile colorspaces --- +display name=NULL.icc n=3 icc=True deviceN=False +display.c0=Red +display.c1=Green +display.c2=Blue +proof name=Proof.icc n=3 icc=True deviceN=False +proof.c0=Red +proof.c1=Green +proof.c2=Blue +--- Color-managed rendering with a display ICC profile --- +display-profile-render=ok +--- Output Intent --- +outputIntent=none +--- DeviceN / spot colors / separations --- +separations=none +--- Overprint simulation --- +usesOverprint=False +sim op=1 opm=1 bp=1 ri=1 +--- Rendering intent and color parameters --- +renderingIntent perceptual=ok relative=ok +ri=1 bp=1 op=1 opm=1 +--- Prepress analysis foundation --- +prepress outputIntent=False overprintPages=0 separationPages=0 pageCount=2 +--- ICC soft proof --- +soft-proof=ok diff --git a/MuPDF.NET/19-ColorManagement/Expected/device-cmyk.pam.sha256 b/MuPDF.NET/19-ColorManagement/Expected/device-cmyk.pam.sha256 new file mode 100644 index 0000000..c4281cd --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/Expected/device-cmyk.pam.sha256 @@ -0,0 +1 @@ +257d5bd0c5e9423b8d96520f8d728f5bfd18b24349386d358a1c63705f1a619c diff --git a/MuPDF.NET/19-ColorManagement/Expected/device-gray.png.sha256 b/MuPDF.NET/19-ColorManagement/Expected/device-gray.png.sha256 new file mode 100644 index 0000000..d524868 --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/Expected/device-gray.png.sha256 @@ -0,0 +1 @@ +4fefef0b30121b87a4bb2a6ef32e8ea08ed5a9e94710d2039b6326b448b1f4c3 diff --git a/MuPDF.NET/19-ColorManagement/Expected/device-rgb.png.sha256 b/MuPDF.NET/19-ColorManagement/Expected/device-rgb.png.sha256 new file mode 100644 index 0000000..013b30c --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/Expected/device-rgb.png.sha256 @@ -0,0 +1 @@ +686595bfb3e303b0bcd9fab9b0469c1779e1b53303b69afefafbbe7ee4d40d29 diff --git a/MuPDF.NET/19-ColorManagement/Expected/display-profile-render.png.sha256 b/MuPDF.NET/19-ColorManagement/Expected/display-profile-render.png.sha256 new file mode 100644 index 0000000..c7f7ead --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/Expected/display-profile-render.png.sha256 @@ -0,0 +1 @@ +f50db450c040adf3267512bba0e6394ce26e35272de8ebe542dc8fa5c37b707a diff --git a/MuPDF.NET/19-ColorManagement/Expected/rendering-intent-perceptual.png.sha256 b/MuPDF.NET/19-ColorManagement/Expected/rendering-intent-perceptual.png.sha256 new file mode 100644 index 0000000..3e5cbcf --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/Expected/rendering-intent-perceptual.png.sha256 @@ -0,0 +1 @@ +52a3fc060941d63d8892cd8f7f6afa6f71ade0daab0cd8572d0b019ad12fa39d diff --git a/MuPDF.NET/19-ColorManagement/Expected/rendering-intent-relative-bpc-overprint.png.sha256 b/MuPDF.NET/19-ColorManagement/Expected/rendering-intent-relative-bpc-overprint.png.sha256 new file mode 100644 index 0000000..a4bae46 --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/Expected/rendering-intent-relative-bpc-overprint.png.sha256 @@ -0,0 +1 @@ +dd74a5f9a4e97166e8956eca4284c58c700271f423de081e77058c4551bf1d3f diff --git a/MuPDF.NET/19-ColorManagement/Expected/soft-proof.png.sha256 b/MuPDF.NET/19-ColorManagement/Expected/soft-proof.png.sha256 new file mode 100644 index 0000000..c7f7ead --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/Expected/soft-proof.png.sha256 @@ -0,0 +1 @@ +f50db450c040adf3267512bba0e6394ce26e35272de8ebe542dc8fa5c37b707a diff --git a/MuPDF.NET/19-ColorManagement/Expected/viewer-page-1-144dpi.png.sha256 b/MuPDF.NET/19-ColorManagement/Expected/viewer-page-1-144dpi.png.sha256 new file mode 100644 index 0000000..f703385 --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/Expected/viewer-page-1-144dpi.png.sha256 @@ -0,0 +1 @@ +1af32b2b4ac2487bb2aef93f13222c4dbce1fa686dd082f568c79ea61fc71882 diff --git a/MuPDF.NET/19-ColorManagement/Expected/zoomed-page-1.png.sha256 b/MuPDF.NET/19-ColorManagement/Expected/zoomed-page-1.png.sha256 new file mode 100644 index 0000000..69f9ed4 --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/Expected/zoomed-page-1.png.sha256 @@ -0,0 +1 @@ +4af511521e69b4d966ab22d4972636ac3a09942613936519234a19b04b1d9f05 diff --git a/MuPDF.NET/19-ColorManagement/Program.cs b/MuPDF.NET/19-ColorManagement/Program.cs new file mode 100644 index 0000000..5522e52 --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/Program.cs @@ -0,0 +1,428 @@ +using System.Linq; +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; +using mupdf; + +namespace MuPDF.NET.Examples.MuPDFNet.ColorManagement; + +/// +/// Color / print samples ported from the Artifex ColorDemo project: +/// device colorspaces, ICC profiles, soft proofing, output intents, +/// DeviceN / spot separations, overprint, and rendering intents. +/// +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 19-ColorManagement"); + ColorManagement(); + } + + /// + /// Run all color-management demos. Copy individual Demo* methods below into your project. + /// + static void ColorManagement() + { + string inputPdf = ExamplePaths.MuPdfNetInput(Path.Combine("color", "test.pdf")); + string displayProfile = ExamplePaths.MuPdfNetInput(Path.Combine("color", "NULL.icc")); + string proofProfile = ExamplePaths.MuPdfNetInput(Path.Combine("color", "Proof.icc")); + string outputDir = Path.GetDirectoryName( + ExamplePaths.Output("MuPDF.NET", "19-ColorManagement", "_"))!; + var check = new ResultCheck("MuPDF.NET", "19-ColorManagement"); + var report = new StringBuilder(); + + ConsoleEx.Info($"Input PDF: {inputPdf}"); + ConsoleEx.Info($"Display profile: {displayProfile}"); + ConsoleEx.Info($"Proof profile: {proofProfile}"); + ConsoleEx.Info($"Output folder: {outputDir}"); + + DemoPdfViewing(inputPdf, outputDir, check, report); + DemoDeviceColorspaces(inputPdf, outputDir, check, report); + DemoPdfObjectInspection(inputPdf, report); + DemoZoomAndNavigation(inputPdf, outputDir, check, report); + DemoIccProfiles(displayProfile, proofProfile, report); + DemoColorManagedRendering(inputPdf, displayProfile, outputDir, check, report); + DemoOutputIntent(inputPdf, report); + DemoDeviceNSpotColorsAndSeparations(inputPdf, outputDir, check, report); + DemoOverprint(inputPdf, report); + DemoRenderingIntentAndColorParameters(inputPdf, displayProfile, proofProfile, outputDir, check, report); + DemoPrepressAnalysis(inputPdf, report); + DemoSoftProof(inputPdf, displayProfile, proofProfile, outputDir, check, report); + + check.Text(report.ToString(), "color-report.txt"); + check.Finish(); + } + + /// High-performance first-page rendering suitable for a viewer. + static void DemoPdfViewing(string inputPdf, string outputDir, ResultCheck check, StringBuilder report) + { + Section("High-performance PDF viewing", report); + using var document = Document.Open(inputPdf); + using var page = document[0]; + + string output = Path.Combine(outputDir, "viewer-page-1-144dpi.png"); + using var pixmap = page.GetPixmap(dpi: 144, cs: Colorspace.Rgb, alpha: false); + pixmap.Save(output); + + report.Append("viewer pages=").Append(document.PageCount) + .Append(" size=").Append(pixmap.Width).Append('x').Append(pixmap.Height) + .Append(" n=").Append(pixmap.N).Append('\n'); + check.FileSha256(output, "viewer-page-1-144dpi.png.sha256"); + ConsoleEx.Info($"Saved: {output}"); + } + + /// DeviceRGB, DeviceGray, and DeviceCMYK output rendering. + static void DemoDeviceColorspaces(string inputPdf, string outputDir, ResultCheck check, StringBuilder report) + { + Section("DeviceRGB / DeviceGray / DeviceCMYK", report); + using var document = Document.Open(inputPdf); + using var page = document[0]; + + using (var rgb = page.GetPixmap(dpi: 96, cs: Colorspace.Rgb)) + { + string path = Path.Combine(outputDir, "device-rgb.png"); + rgb.Save(path); + report.Append("device-rgb n=").Append(rgb.N).Append(" cs=").Append(rgb.Colorspace?.Name).Append('\n'); + check.FileSha256(path, "device-rgb.png.sha256"); + } + + using (var gray = page.GetPixmap(dpi: 96, cs: Colorspace.Gray)) + { + string path = Path.Combine(outputDir, "device-gray.png"); + gray.Save(path); + report.Append("device-gray n=").Append(gray.N).Append(" cs=").Append(gray.Colorspace?.Name).Append('\n'); + check.FileSha256(path, "device-gray.png.sha256"); + } + + using (var cmyk = page.GetPixmap(dpi: 96, cs: Colorspace.Cmyk)) + { + // PAM supports CMYK samples; PNG is RGB/Gray only. + string path = Path.Combine(outputDir, "device-cmyk.pam"); + cmyk.Save(path); + report.Append("device-cmyk n=").Append(cmyk.N).Append(" cs=").Append(cmyk.Colorspace?.Name).Append('\n'); + check.FileSha256(path, "device-cmyk.pam.sha256"); + } + } + + /// PDF catalog, trailer, xref, dictionary, and stream inspection. + static void DemoPdfObjectInspection(string inputPdf, StringBuilder report) + { + Section("PDF object inspection", report); + using var document = Document.Open(inputPdf); + if (!document.IsPdf) + { + report.Append("not-pdf\n"); + return; + } + + report.Append("catalogXref=").Append(document.PdfCatalog).Append('\n'); + report.Append("xrefLength=").Append(document.XrefLength).Append('\n'); + + var outputIntents = document.XrefGetKey(document.PdfCatalog, "OutputIntents"); + report.Append("outputIntents.type=").Append(outputIntents.type).Append('\n'); + + int objectCount = Math.Max(0, Math.Min(8, document.XrefLength - 1)); + for (int xref = 1; xref <= objectCount; xref++) + { + var type = document.XrefGetKey(xref, "Type"); + var subtype = document.XrefGetKey(xref, "Subtype"); + report.Append("xref=").Append(xref) + .Append(" stream=").Append(document.XrefIsStream(xref)) + .Append(" Type=").Append(type.value) + .Append(" Subtype=").Append(subtype.value) + .Append('\n'); + } + } + + /// Matrix zoom, page iteration, links, and outline navigation. + static void DemoZoomAndNavigation(string inputPdf, string outputDir, ResultCheck check, StringBuilder report) + { + Section("Zoom and navigation", report); + using var document = Document.Open(inputPdf); + using var page = document[0]; + using var zoomed = page.GetPixmap(matrix: new Matrix(2, 2), cs: Colorspace.Rgb); + string output = Path.Combine(outputDir, "zoomed-page-1.png"); + zoomed.Save(output); + + report.Append("zoom pages=").Append(document.PageCount) + .Append(" links=").Append(page.GetLinks().Count) + .Append(" toc=").Append(document.GetToc().Count) + .Append(" size=").Append(zoomed.Width).Append('x').Append(zoomed.Height) + .Append('\n'); + check.FileSha256(output, "zoomed-page-1.png.sha256"); + } + + /// Create MuPDF ICC colorspaces from display/proof profile files. + static void DemoIccProfiles(string displayProfile, string proofProfile, StringBuilder report) + { + Section("ICC profile colorspaces", report); + using (var display = LoadIccColorspace(displayProfile)) + AppendNativeColorspace(report, "display", display); + + using (var proof = LoadIccColorspace(proofProfile)) + AppendNativeColorspace(report, "proof", proof); + } + + /// Render page 1 directly into a display ICC colorspace. + static void DemoColorManagedRendering( + string inputPdf, + string displayProfile, + string outputDir, + ResultCheck check, + StringBuilder report) + { + Section("Color-managed rendering with a display ICC profile", report); + string output = Path.Combine(outputDir, "display-profile-render.png"); + RenderIccPage(inputPdf, displayProfile, proofProfile: null, output); + report.Append("display-profile-render=ok\n"); + check.FileSha256(output, "display-profile-render.png.sha256"); + } + + /// Read the PDF Output Intent semantically through MuPDF. + static void DemoOutputIntent(string inputPdf, StringBuilder report) + { + Section("Output Intent", report); + using var document = new FzDocument(inputPdf); + using var outputIntent = document.fz_document_output_intent(); + + if (outputIntent == null || outputIntent.m_internal_value() == 0) + { + report.Append("outputIntent=none\n"); + return; + } + + AppendNativeColorspace(report, "outputIntent", outputIntent); + } + + /// Enumerate DeviceN/spot plates and render all plates composited to RGB. + static void DemoDeviceNSpotColorsAndSeparations( + string inputPdf, + string outputDir, + ResultCheck check, + StringBuilder report) + { + Section("DeviceN / spot colors / separations", report); + using var document = new FzDocument(inputPdf); + using var page = document.fz_load_page(0); + using var separations = page.fz_page_separations(); + + if (separations == null || separations.m_internal_value() == 0) + { + report.Append("separations=none\n"); + return; + } + + int count = separations.fz_count_separations(); + report.Append("separationCount=").Append(count).Append('\n'); + for (int i = 0; i < count; i++) + { + report.Append("sep[").Append(i).Append("]=").Append(separations.fz_separation_name(i)).Append('\n'); + separations.fz_set_separation_behavior(i, fz_separation_behavior.FZ_SEPARATION_COMPOSITE); + } + + using var matrix = new FzMatrix(1, 0, 0, 1, 0, 0); + using var rgb = new FzColorspace(FzColorspace.Fixed.Fixed_RGB); + using var pixmap = page.fz_new_pixmap_from_page_with_separations(matrix, rgb, separations, alpha: 0); + string output = Path.Combine(outputDir, "separations-composited.png"); + pixmap.fz_save_pixmap_as_png(output); + check.FileSha256(output, "separations-composited.png.sha256"); + } + + /// Detect overprint use and show the parameters used for simulation. + static void DemoOverprint(string inputPdf, StringBuilder report) + { + Section("Overprint simulation", report); + using var document = new FzDocument(inputPdf); + using var page = document.fz_load_page(0); + bool usesOverprint = page.fz_page_uses_overprint() != 0; + + using var parameters = new FzColorParams + { + op = 1, + opm = 1, + bp = 1, + ri = 1, // FZ_RI_RELATIVE_COLORIMETRIC + }; + + report.Append("usesOverprint=").Append(usesOverprint).Append('\n'); + report.Append("sim op=").Append(parameters.op) + .Append(" opm=").Append(parameters.opm) + .Append(" bp=").Append(parameters.bp) + .Append(" ri=").Append(parameters.ri) + .Append('\n'); + } + + /// + /// Apply color parameters to a CMYK-to-display conversion and save comparable images. + /// + static void DemoRenderingIntentAndColorParameters( + string inputPdf, + string displayProfile, + string proofProfile, + string outputDir, + ResultCheck check, + StringBuilder report) + { + Section("Rendering intent and color parameters", report); + using var document = new FzDocument(inputPdf); + using var page = document.fz_load_page(0); + using var matrix = new FzMatrix(1, 0, 0, 1, 0, 0); + using var cmyk = new FzColorspace(FzColorspace.Fixed.Fixed_CMYK); + using var display = LoadIccColorspace(displayProfile); + using var proof = LoadIccColorspace(proofProfile); + using var defaults = new FzDefaultColorspaces(); + using var source = page.fz_new_pixmap_from_page(matrix, cmyk, alpha: 0); + + using var perceptualParameters = new FzColorParams + { + ri = 0, // FZ_RI_PERCEPTUAL + bp = 0, + op = 0, + opm = 0, + }; + using var perceptual = source.fz_convert_pixmap( + display, proof, defaults, perceptualParameters, keep_alpha: 0); + string perceptualOutput = Path.Combine(outputDir, "rendering-intent-perceptual.png"); + perceptual.fz_save_pixmap_as_png(perceptualOutput); + + using var relativeParameters = new FzColorParams + { + ri = 1, // FZ_RI_RELATIVE_COLORIMETRIC + bp = 1, + op = 1, + opm = 1, + }; + using var relative = source.fz_convert_pixmap( + display, proof, defaults, relativeParameters, keep_alpha: 0); + string relativeOutput = Path.Combine(outputDir, "rendering-intent-relative-bpc-overprint.png"); + relative.fz_save_pixmap_as_png(relativeOutput); + + report.Append("renderingIntent perceptual=ok relative=ok\n"); + report.Append("ri=").Append(relativeParameters.ri) + .Append(" bp=").Append(relativeParameters.bp) + .Append(" op=").Append(relativeParameters.op) + .Append(" opm=").Append(relativeParameters.opm) + .Append('\n'); + check.FileSha256(perceptualOutput, "rendering-intent-perceptual.png.sha256"); + check.FileSha256(relativeOutput, "rendering-intent-relative-bpc-overprint.png.sha256"); + } + + /// Show the primitives available for future prepress checks. + static void DemoPrepressAnalysis(string inputPdf, StringBuilder report) + { + Section("Prepress analysis foundation", report); + using var document = new FzDocument(inputPdf); + using var outputIntent = document.fz_document_output_intent(); + bool hasOutputIntent = outputIntent != null && outputIntent.m_internal_value() != 0; + int pagesWithOverprint = 0; + int pagesWithSeparations = 0; + int pageCount = document.fz_count_pages(); + + for (int pageNumber = 0; pageNumber < pageCount; pageNumber++) + { + using var page = document.fz_load_page(pageNumber); + if (page.fz_page_uses_overprint() != 0) + pagesWithOverprint++; + + using var separations = page.fz_page_separations(); + if (separations != null + && separations.m_internal_value() != 0 + && separations.fz_count_separations() > 0) + { + pagesWithSeparations++; + } + } + + report.Append("prepress outputIntent=").Append(hasOutputIntent) + .Append(" overprintPages=").Append(pagesWithOverprint) + .Append(" separationPages=").Append(pagesWithSeparations) + .Append(" pageCount=").Append(pageCount) + .Append('\n'); + } + + /// + /// Render page 1 through an optional proof profile into an optional display profile. + /// + static void DemoSoftProof( + string inputPdf, + string displayProfile, + string proofProfile, + string outputDir, + ResultCheck check, + StringBuilder report) + { + Section("ICC soft proof", report); + + // ICC enable/disable affects MuPDF's shared context. A production wrapper + // should serialize this setting with all rendering operations. + mupdf.mupdf.fz_enable_icc(); + + string output = Path.Combine(outputDir, "soft-proof.png"); + RenderIccPage(inputPdf, displayProfile, proofProfile, output); + report.Append("soft-proof=ok\n"); + check.FileSha256(output, "soft-proof.png.sha256"); + } + + // ── helpers (also useful to copy) ───────────────────────────────── + + static void RenderIccPage( + string inputPdf, + string displayProfile, + string? proofProfile, + string output) + { + using var document = new FzDocument(inputPdf); + using var page = document.fz_load_page(0); + using var display = LoadIccColorspace(displayProfile); + using var proof = proofProfile == null ? null : LoadIccColorspace(proofProfile); + using var pageBounds = page.fz_bound_page(); + using var transform = new FzMatrix(2, 0, 0, 2, 0, 0); + using var transformedBounds = new FzRect(pageBounds, transform); + using var pixelBounds = new FzIrect(transformedBounds); + using var noSeparations = new FzSeparations(0); + using var pixmap = display.fz_new_pixmap_with_bbox(pixelBounds, noSeparations, alpha: 0); + pixmap.fz_clear_pixmap_with_value(255); + + // Draw device converts document colors into the display ICC space, + // optionally applying the proof ICC profile as the intermediate proof space. + using var device = proof == null + ? new FzDevice(transform, pixmap, pixelBounds) + : new FzDevice(transform, pixmap, pixelBounds, proof); + using var identity = new FzMatrix(1, 0, 0, 1, 0, 0); + using var cookie = new FzCookie(); + page.fz_run_page(device, identity, cookie); + + pixmap.fz_save_pixmap_as_png(output); + } + + static FzColorspace LoadIccColorspace(string profilePath) + { + // FZ_COLORSPACE_NONE (0) asks MuPDF to infer the profile type. + using var profile = new FzBuffer(profilePath); + return new FzColorspace( + (SWIGTYPE_fz_colorspace_type)0, + flags: 0, + name: Path.GetFileName(profilePath), + profile); + } + + static void AppendNativeColorspace(StringBuilder report, string label, FzColorspace colorspace) + { + report.Append(label) + .Append(" name=").Append(colorspace.fz_colorspace_name()) + .Append(" n=").Append(colorspace.fz_colorspace_n()) + .Append(" icc=").Append(colorspace.fz_colorspace_is_icc() != 0) + .Append(" deviceN=").Append(colorspace.fz_colorspace_is_device_n() != 0) + .Append('\n'); + for (int i = 0; i < colorspace.fz_colorspace_n(); i++) + report.Append(label).Append(".c").Append(i).Append('=').Append(colorspace.fz_colorspace_colorant(i)).Append('\n'); + } + + static void Section(string title, StringBuilder report) + { + report.Append("--- ").Append(title).Append(" ---\n"); + ConsoleEx.Info(title); + } +} diff --git a/MuPDF.NET/19-ColorManagement/README.md b/MuPDF.NET/19-ColorManagement/README.md new file mode 100644 index 0000000..22a56fe --- /dev/null +++ b/MuPDF.NET/19-ColorManagement/README.md @@ -0,0 +1,46 @@ +# 19-ColorManagement + +**ICC / print** color samples (soft proof, separations, overprint, device RGB/Gray/CMYK renders). + +For simple device-colorspace conversion of page content, see [`05-Recolor`](../05-Recolor/) (`Document.Recolor`). + +- High-DPI viewer render +- DeviceRGB / DeviceGray / DeviceCMYK +- PDF object / OutputIntents inspection +- Zoom + links / TOC +- ICC display & proof profiles +- Color-managed rendering +- Output Intent +- DeviceN / spot separations +- Overprint simulation parameters +- Rendering intent + black-point / overprint flags +- Prepress analysis foundation +- ICC soft proof + +## Sample methods + +`ColorManagement()` in `Program.cs` orchestrates the run. Copy any `Demo*` method (and the helpers at the bottom) into your project. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) (managed API + `mupdf` low-level types) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/color/test.pdf`, `NULL.icc`, `Proof.icc` | +| Output | PNGs / PAM under `Output/MuPDF.NET/19-ColorManagement/` | +| Expected | `color-report.txt` + SHA-256 files for key renders | + +## Run + +```powershell +dotnet run --project MuPDF.NET\19-ColorManagement +``` + +## Related + +| Example | Topic | +|---------|--------| +| [`05-Recolor`](../05-Recolor/) | Device colorspace conversion via `Document.Recolor` | diff --git a/MuPDF.NET/20-RewriteImages/20-RewriteImages.csproj b/MuPDF.NET/20-RewriteImages/20-RewriteImages.csproj new file mode 100644 index 0000000..dbe14fc --- /dev/null +++ b/MuPDF.NET/20-RewriteImages/20-RewriteImages.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.RewriteImages + 20-RewriteImages + + + + + + diff --git a/MuPDF.NET/20-RewriteImages/Expected/rewritten.summary.txt b/MuPDF.NET/20-RewriteImages/Expected/rewritten.summary.txt new file mode 100644 index 0000000..fab53af --- /dev/null +++ b/MuPDF.NET/20-RewriteImages/Expected/rewritten.summary.txt @@ -0,0 +1,5 @@ +inputBytes=660663 +outputBytes=19874 +pageCount=1 +reductionPct=97 +textSha256=01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b diff --git a/MuPDF.NET/20-RewriteImages/Program.cs b/MuPDF.NET/20-RewriteImages/Program.cs new file mode 100644 index 0000000..348ec0d --- /dev/null +++ b/MuPDF.NET/20-RewriteImages/Program.cs @@ -0,0 +1,48 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.RewriteImages; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 20-RewriteImages"); + RewriteImages(); + } + + /// + /// Downsample / recompress embedded images (PDF only). Same as PyMuPDF Document.rewrite_images. + /// + static void RewriteImages() + { + string input = ExamplePaths.MuPdfNetInput("test-rewrite-images.pdf"); + string output = ExamplePaths.Output("MuPDF.NET", "20-RewriteImages", "rewritten.pdf"); + var check = new ResultCheck("MuPDF.NET", "20-RewriteImages"); + + long size0 = new System.IO.FileInfo(input).Length; + + using (var doc = Document.Open(input)) + { + // Subsample images above 100 DPI down to 72 DPI; JPEG quality 33. + doc.RewriteImages(dpiThreshold: 100, dpiTarget: 72, quality: 33); + doc.Save(output, garbage: 3, deflate: 1); + } + + long size1 = new System.IO.FileInfo(output).Length; + double reduction = 1.0 - (size1 / (double)size0); + + ConsoleEx.Info($"Opened: {input}"); + ConsoleEx.Info($"Input size: {size0} bytes"); + ConsoleEx.Info($"Output size: {size1} bytes ({reduction:P1} smaller)"); + ConsoleEx.Info($"Wrote: {output}"); + + var props = PdfFingerprint.FromFile(output); + props["inputBytes"] = size0.ToString(); + props["outputBytes"] = size1.ToString(); + props["reductionPct"] = ((int)Math.Round(reduction * 100)).ToString(); + check.Properties(props, "rewritten.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/20-RewriteImages/README.md b/MuPDF.NET/20-RewriteImages/README.md new file mode 100644 index 0000000..3b92558 --- /dev/null +++ b/MuPDF.NET/20-RewriteImages/README.md @@ -0,0 +1,36 @@ +# 20-RewriteImages + +Downsample and recompress images in a PDF (`Document.RewriteImages`). Same API as PyMuPDF `Document.rewrite_images`. + +## Sample method + +`RewriteImages()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/test-rewrite-images.pdf` | +| Output | `Output/MuPDF.NET/20-RewriteImages/rewritten.pdf` | +| Expected | `Expected/rewritten.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\20-RewriteImages +``` + +## APIs used + +- `Document.RewriteImages` + +## Related + +| Example | Topic | +|---------|--------| +| [`09-InsertImage`](../09-InsertImage/) | Place a new image on a page | +| [`17-ReplaceImage`](../17-ReplaceImage/) | Replace an existing image by xref | diff --git a/MuPDF.NET/21-ExtractImages/21-ExtractImages.csproj b/MuPDF.NET/21-ExtractImages/21-ExtractImages.csproj new file mode 100644 index 0000000..b7f6735 --- /dev/null +++ b/MuPDF.NET/21-ExtractImages/21-ExtractImages.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.ExtractImages + 21-ExtractImages + + + + + + diff --git a/MuPDF.NET/21-ExtractImages/Expected/extract-images.txt b/MuPDF.NET/21-ExtractImages/Expected/extract-images.txt new file mode 100644 index 0000000..64cc582 --- /dev/null +++ b/MuPDF.NET/21-ExtractImages/Expected/extract-images.txt @@ -0,0 +1,2 @@ +imageCount=1 +xref=9 ext=jpeg w=632 h=132 n=3 bytes=7850 sha=48a83fd8541e25e74224352bc0c441ac6644abee75b9713a4a7ea94eb25c7d04 diff --git a/MuPDF.NET/21-ExtractImages/Expected/img-00-xref9.jpeg.sha256 b/MuPDF.NET/21-ExtractImages/Expected/img-00-xref9.jpeg.sha256 new file mode 100644 index 0000000..0a905c4 --- /dev/null +++ b/MuPDF.NET/21-ExtractImages/Expected/img-00-xref9.jpeg.sha256 @@ -0,0 +1 @@ +48a83fd8541e25e74224352bc0c441ac6644abee75b9713a4a7ea94eb25c7d04 diff --git a/MuPDF.NET/21-ExtractImages/Program.cs b/MuPDF.NET/21-ExtractImages/Program.cs new file mode 100644 index 0000000..4da8828 --- /dev/null +++ b/MuPDF.NET/21-ExtractImages/Program.cs @@ -0,0 +1,60 @@ +using System.Linq; +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.ExtractImages; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 21-ExtractImages"); + ExtractImages(); + } + + /// + /// List images on page 1 and write each embedded image stream to disk. + /// + static void ExtractImages() + { + string input = ExamplePaths.MuPdfNetInput("Color.pdf"); + string outDir = Path.GetDirectoryName( + ExamplePaths.Output("MuPDF.NET", "21-ExtractImages", "_"))!; + var check = new ResultCheck("MuPDF.NET", "21-ExtractImages"); + var report = new StringBuilder(); + + using var doc = Document.Open(input); + using Page page = doc[0]; + + // GetImages(full: true) returns xref + colorspace / filter metadata. + List images = page.GetImages(full: true); + report.Append("imageCount=").Append(images.Count).Append('\n'); + ConsoleEx.Info($"Page 1 images: {images.Count}"); + + int index = 0; + foreach (Entry entry in images.OrderBy(e => e.Xref)) + { + ImageInfo extracted = doc.ExtractImage(entry.Xref); + string ext = string.IsNullOrWhiteSpace(extracted.Ext) ? "bin" : extracted.Ext; + string fileName = $"img-{index:D2}-xref{entry.Xref}.{ext}"; + string path = Path.Combine(outDir, fileName); + File.WriteAllBytes(path, extracted.Image ?? Array.Empty()); + + report.Append("xref=").Append(entry.Xref) + .Append(" ext=").Append(ext) + .Append(" w=").Append((int)extracted.Width) + .Append(" h=").Append((int)extracted.Height) + .Append(" n=").Append(extracted.ColorSpace) + .Append(" bytes=").Append(extracted.Image?.Length ?? 0) + .Append(" sha=").Append(ResultCheck.Sha256HexBytes(extracted.Image ?? Array.Empty())) + .Append('\n'); + check.FileSha256(path, fileName + ".sha256"); + index++; + } + + check.Text(report.ToString(), "extract-images.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/21-ExtractImages/README.md b/MuPDF.NET/21-ExtractImages/README.md new file mode 100644 index 0000000..57eca8c --- /dev/null +++ b/MuPDF.NET/21-ExtractImages/README.md @@ -0,0 +1,30 @@ +# 21-ExtractImages + +List images on a page (`Page.GetImages`) and write each stream to disk (`Document.ExtractImage`). + +## Sample method + +`ExtractImages()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/Color.pdf` | +| Output | `Output/MuPDF.NET/21-ExtractImages/img-*.png` (etc.) | +| Expected | `extract-images.txt` + per-file `.sha256` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\21-ExtractImages +``` + +## APIs used + +- `Page.GetImages` +- `Document.ExtractImage` diff --git a/MuPDF.NET/22-GetDrawings/22-GetDrawings.csproj b/MuPDF.NET/22-GetDrawings/22-GetDrawings.csproj new file mode 100644 index 0000000..71e3d12 --- /dev/null +++ b/MuPDF.NET/22-GetDrawings/22-GetDrawings.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.GetDrawings + 22-GetDrawings + + + + + + diff --git a/MuPDF.NET/22-GetDrawings/Expected/drawings.txt b/MuPDF.NET/22-GetDrawings/Expected/drawings.txt new file mode 100644 index 0000000..f526c20 --- /dev/null +++ b/MuPDF.NET/22-GetDrawings/Expected/drawings.txt @@ -0,0 +1,3 @@ +pathCount=2 +type.s=2 +redrawnRects=1 diff --git a/MuPDF.NET/22-GetDrawings/Expected/drawn.summary.txt b/MuPDF.NET/22-GetDrawings/Expected/drawn.summary.txt new file mode 100644 index 0000000..907809d --- /dev/null +++ b/MuPDF.NET/22-GetDrawings/Expected/drawn.summary.txt @@ -0,0 +1,2 @@ +pageCount=1 +textSha256=01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b diff --git a/MuPDF.NET/22-GetDrawings/Expected/redrawn.summary.txt b/MuPDF.NET/22-GetDrawings/Expected/redrawn.summary.txt new file mode 100644 index 0000000..907809d --- /dev/null +++ b/MuPDF.NET/22-GetDrawings/Expected/redrawn.summary.txt @@ -0,0 +1,2 @@ +pageCount=1 +textSha256=01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b diff --git a/MuPDF.NET/22-GetDrawings/Program.cs b/MuPDF.NET/22-GetDrawings/Program.cs new file mode 100644 index 0000000..5a1dc1b --- /dev/null +++ b/MuPDF.NET/22-GetDrawings/Program.cs @@ -0,0 +1,75 @@ +using System.Linq; +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.GetDrawings; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 22-GetDrawings"); + GetDrawings(); + } + + /// + /// Draw vector shapes, then extract path info with . + /// + static void GetDrawings() + { + string drawn = ExamplePaths.Output("MuPDF.NET", "22-GetDrawings", "drawn.pdf"); + string redrawn = ExamplePaths.Output("MuPDF.NET", "22-GetDrawings", "redrawn.pdf"); + var check = new ResultCheck("MuPDF.NET", "22-GetDrawings"); + var report = new StringBuilder(); + + // 1) Create a page with known vector content. + using (var doc = Document.Open()) + { + using Page page = doc.NewPage(); + page.DrawLine(new Point(72, 100), new Point(500, 100), width: 1f, dashes: "[5] 0"); + page.DrawRect(new Rect(72, 180, 220, 280), color: new[] { 0f, 0f, 1f }, width: 1.5f); + page.DrawCircle( + new Point(350, 230), + radius: 40, + color: new[] { 0f, 0f, 0f }, + fill: new[] { 0f, 0.6f, 0f }, + width: 1f); + doc.Save(drawn); + } + + // 2) Extract drawings and rewrite a subset onto a blank page. + using (var src = Document.Open(drawn)) + using (Page page = src[0]) + { + List paths = page.GetDrawings(); + report.Append("pathCount=").Append(paths.Count).Append('\n'); + + var typeCounts = paths + .GroupBy(p => p.Type ?? "?") + .OrderBy(g => g.Key, StringComparer.Ordinal); + foreach (var g in typeCounts) + report.Append("type.").Append(g.Key).Append('=').Append(g.Count()).Append('\n'); + + using var outDoc = Document.Open(); + using Page outPage = outDoc.NewPage(); + int redrawnRects = 0; + foreach (PathInfo path in paths) + { + if (path.Rect == null || path.Rect.IsEmpty) + continue; + // Stroke each path's bounding box so the sample stays short and stable. + outPage.DrawRect(path.Rect, color: new[] { 1f, 0f, 0f }, width: 0.5f); + redrawnRects++; + } + report.Append("redrawnRects=").Append(redrawnRects).Append('\n'); + outDoc.Save(redrawn); + } + + check.Text(report.ToString(), "drawings.txt"); + check.Properties(PdfFingerprint.FromFile(drawn), "drawn.summary.txt"); + check.Properties(PdfFingerprint.FromFile(redrawn), "redrawn.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/22-GetDrawings/README.md b/MuPDF.NET/22-GetDrawings/README.md new file mode 100644 index 0000000..5af52a7 --- /dev/null +++ b/MuPDF.NET/22-GetDrawings/README.md @@ -0,0 +1,29 @@ +# 22-GetDrawings + +Draw vector shapes, extract path dictionaries with `Page.GetDrawings`, then redraw path bounds. + +## Sample method + +`GetDrawings()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Output | `Output/MuPDF.NET/22-GetDrawings/drawn.pdf`, `redrawn.pdf` | +| Expected | `drawings.txt` + PDF fingerprints | + +## Run + +```powershell +dotnet run --project MuPDF.NET\22-GetDrawings +``` + +## APIs used + +- `Page.DrawLine` / `DrawRect` / `DrawCircle` +- `Page.GetDrawings` diff --git a/MuPDF.NET/23-RotateCrop/23-RotateCrop.csproj b/MuPDF.NET/23-RotateCrop/23-RotateCrop.csproj new file mode 100644 index 0000000..4a471e5 --- /dev/null +++ b/MuPDF.NET/23-RotateCrop/23-RotateCrop.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.RotateCrop + 23-RotateCrop + + + + + + diff --git a/MuPDF.NET/23-RotateCrop/Expected/cropped.summary.txt b/MuPDF.NET/23-RotateCrop/Expected/cropped.summary.txt new file mode 100644 index 0000000..cf1296b --- /dev/null +++ b/MuPDF.NET/23-RotateCrop/Expected/cropped.summary.txt @@ -0,0 +1,2 @@ +pageCount=3 +textSha256=9ce4083eed8bdba09b8dd3ae5a780258e1d18fe12756e325562c13393fdb94c2 diff --git a/MuPDF.NET/23-RotateCrop/Expected/rotate-crop.txt b/MuPDF.NET/23-RotateCrop/Expected/rotate-crop.txt new file mode 100644 index 0000000..f87bb76 --- /dev/null +++ b/MuPDF.NET/23-RotateCrop/Expected/rotate-crop.txt @@ -0,0 +1,6 @@ +before.rotation=270 +before.rect=0,0,842,595 +before.cropbox=0,0,595,842 +after.rotation=90 +crop.rect=0,0,421,297.5 +after.cropbox=0,0,421,297.5 diff --git a/MuPDF.NET/23-RotateCrop/Expected/rotated.summary.txt b/MuPDF.NET/23-RotateCrop/Expected/rotated.summary.txt new file mode 100644 index 0000000..cf4c51b --- /dev/null +++ b/MuPDF.NET/23-RotateCrop/Expected/rotated.summary.txt @@ -0,0 +1,2 @@ +pageCount=3 +textSha256=31804ecc4c72047b41012fce71e3e0a203864fe1587c3283ad6ea34ee1ec41dc diff --git a/MuPDF.NET/23-RotateCrop/Program.cs b/MuPDF.NET/23-RotateCrop/Program.cs new file mode 100644 index 0000000..f1f69eb --- /dev/null +++ b/MuPDF.NET/23-RotateCrop/Program.cs @@ -0,0 +1,64 @@ +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.RotateCrop; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 23-RotateCrop"); + RotateCrop(); + } + + /// + /// Rotate page 1 and set a CropBox, then save. + /// + static void RotateCrop() + { + string input = ExamplePaths.MuPdfNetInput("sample.pdf"); + string rotated = ExamplePaths.Output("MuPDF.NET", "23-RotateCrop", "rotated.pdf"); + string cropped = ExamplePaths.Output("MuPDF.NET", "23-RotateCrop", "cropped.pdf"); + var check = new ResultCheck("MuPDF.NET", "23-RotateCrop"); + var report = new StringBuilder(); + + using (var doc = Document.Open(input)) + using (Page page = doc[0]) + { + report.Append("before.rotation=").Append(page.Rotation).Append('\n'); + report.Append("before.rect=").Append(Fmt(page.Rect)).Append('\n'); + report.Append("before.cropbox=").Append(Fmt(page.CropBox)).Append('\n'); + + // Rotate clockwise 90 degrees (PDF /Rotate). + page.SetRotation(90); + doc.Save(rotated); + report.Append("after.rotation=").Append(page.Rotation).Append('\n'); + } + + using (var doc = Document.Open(input)) + using (Page page = doc[0]) + { + // Crop to the upper-left quadrant of the media box. + Rect media = page.Rect; + var crop = new Rect( + media.X0, + media.Y0, + media.X0 + media.Width / 2f, + media.Y0 + media.Height / 2f); + page.SetCropBox(crop); + doc.Save(cropped); + report.Append("crop.rect=").Append(Fmt(crop)).Append('\n'); + report.Append("after.cropbox=").Append(Fmt(page.CropBox)).Append('\n'); + } + + check.Text(report.ToString(), "rotate-crop.txt"); + check.Properties(PdfFingerprint.FromFile(rotated), "rotated.summary.txt"); + check.Properties(PdfFingerprint.FromFile(cropped), "cropped.summary.txt"); + check.Finish(); + } + + static string Fmt(Rect r) => + r == null ? "null" : $"{r.X0:0.###},{r.Y0:0.###},{r.X1:0.###},{r.Y1:0.###}"; +} diff --git a/MuPDF.NET/23-RotateCrop/README.md b/MuPDF.NET/23-RotateCrop/README.md new file mode 100644 index 0000000..a09ac98 --- /dev/null +++ b/MuPDF.NET/23-RotateCrop/README.md @@ -0,0 +1,31 @@ +# 23-RotateCrop + +Rotate a page (`Page.SetRotation`) and set a crop box (`Page.SetCropBox`). + +## Sample method + +`RotateCrop()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/sample.pdf` | +| Output | `rotated.pdf`, `cropped.pdf` | +| Expected | `rotate-crop.txt` + PDF fingerprints | + +## Run + +```powershell +dotnet run --project MuPDF.NET\23-RotateCrop +``` + +## APIs used + +- `Page.SetRotation` +- `Page.SetCropBox` +- `Page.Rotation` / `CropBox` diff --git a/MuPDF.NET/24-EncryptDecrypt/24-EncryptDecrypt.csproj b/MuPDF.NET/24-EncryptDecrypt/24-EncryptDecrypt.csproj new file mode 100644 index 0000000..d3878de --- /dev/null +++ b/MuPDF.NET/24-EncryptDecrypt/24-EncryptDecrypt.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.EncryptDecrypt + 24-EncryptDecrypt + + + + + + diff --git a/MuPDF.NET/24-EncryptDecrypt/Expected/decrypted.summary.txt b/MuPDF.NET/24-EncryptDecrypt/Expected/decrypted.summary.txt new file mode 100644 index 0000000..cf4c51b --- /dev/null +++ b/MuPDF.NET/24-EncryptDecrypt/Expected/decrypted.summary.txt @@ -0,0 +1,2 @@ +pageCount=3 +textSha256=31804ecc4c72047b41012fce71e3e0a203864fe1587c3283ad6ea34ee1ec41dc diff --git a/MuPDF.NET/24-EncryptDecrypt/Expected/encrypt-decrypt.txt b/MuPDF.NET/24-EncryptDecrypt/Expected/encrypt-decrypt.txt new file mode 100644 index 0000000..168c526 --- /dev/null +++ b/MuPDF.NET/24-EncryptDecrypt/Expected/encrypt-decrypt.txt @@ -0,0 +1,9 @@ +encrypted=ok +needsPass=True +isEncrypted=True +authenticate=2 +pages=3 +decrypted=ok +decrypted.needsPass=False +decrypted.isEncrypted=False +decrypted.pages=3 diff --git a/MuPDF.NET/24-EncryptDecrypt/Program.cs b/MuPDF.NET/24-EncryptDecrypt/Program.cs new file mode 100644 index 0000000..d71efd4 --- /dev/null +++ b/MuPDF.NET/24-EncryptDecrypt/Program.cs @@ -0,0 +1,69 @@ +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.EncryptDecrypt; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 24-EncryptDecrypt"); + EncryptDecrypt(); + } + + /// + /// Save an AES-256 encrypted PDF, open it with a password, then save decrypted. + /// + static void EncryptDecrypt() + { + string input = ExamplePaths.MuPdfNetInput("sample.pdf"); + string encrypted = ExamplePaths.Output("MuPDF.NET", "24-EncryptDecrypt", "encrypted.pdf"); + string decrypted = ExamplePaths.Output("MuPDF.NET", "24-EncryptDecrypt", "decrypted.pdf"); + var check = new ResultCheck("MuPDF.NET", "24-EncryptDecrypt"); + var report = new StringBuilder(); + const string userPassword = "user-secret"; + const string ownerPassword = "owner-secret"; + + using (var doc = Document.Open(input)) + { + doc.Save( + encrypted, + encryption: Constants.PDF_ENCRYPT_AES_256, + ownerPW: ownerPassword, + userPW: userPassword); + report.Append("encrypted=ok\n"); + } + + using (var doc = Document.Open(encrypted)) + { + report.Append("needsPass=").Append(doc.NeedsPass).Append('\n'); + report.Append("isEncrypted=").Append(doc.IsEncrypted).Append('\n'); + + // Authenticate with the user password before reading / saving. + int auth = doc.Authenticate(userPassword); + report.Append("authenticate=").Append(auth).Append('\n'); + report.Append("pages=").Append(doc.PageCount).Append('\n'); + + doc.Save( + decrypted, + encryption: Constants.PDF_ENCRYPT_NONE, + garbage: 3, + deflate: 1); + report.Append("decrypted=ok\n"); + } + + using (var doc = Document.Open(decrypted)) + { + report.Append("decrypted.needsPass=").Append(doc.NeedsPass).Append('\n'); + report.Append("decrypted.isEncrypted=").Append(doc.IsEncrypted).Append('\n'); + report.Append("decrypted.pages=").Append(doc.PageCount).Append('\n'); + } + + check.Text(report.ToString(), "encrypt-decrypt.txt"); + // Encrypted PDFs are binary-unstable; fingerprint the decrypted output only. + check.Properties(PdfFingerprint.FromFile(decrypted), "decrypted.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/24-EncryptDecrypt/README.md b/MuPDF.NET/24-EncryptDecrypt/README.md new file mode 100644 index 0000000..f2c024f --- /dev/null +++ b/MuPDF.NET/24-EncryptDecrypt/README.md @@ -0,0 +1,31 @@ +# 24-EncryptDecrypt + +Save a PDF with AES-256 encryption, authenticate, then save without encryption. + +## Sample method + +`EncryptDecrypt()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/sample.pdf` | +| Output | `encrypted.pdf`, `decrypted.pdf` | +| Expected | `encrypt-decrypt.txt` + decrypted fingerprint | + +## Run + +```powershell +dotnet run --project MuPDF.NET\24-EncryptDecrypt +``` + +## APIs used + +- `Document.Save` (`encryption`, `ownerPW`, `userPW`) +- `Document.Authenticate` +- `Constants.PDF_ENCRYPT_AES_256` / `PDF_ENCRYPT_NONE` diff --git a/MuPDF.NET/25-CompressSave/25-CompressSave.csproj b/MuPDF.NET/25-CompressSave/25-CompressSave.csproj new file mode 100644 index 0000000..6b76e13 --- /dev/null +++ b/MuPDF.NET/25-CompressSave/25-CompressSave.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.CompressSave + 25-CompressSave + + + + + + diff --git a/MuPDF.NET/25-CompressSave/Expected/compress.txt b/MuPDF.NET/25-CompressSave/Expected/compress.txt new file mode 100644 index 0000000..af46765 --- /dev/null +++ b/MuPDF.NET/25-CompressSave/Expected/compress.txt @@ -0,0 +1,2 @@ +smallerOrEqual=true +savedWith=garbage4+deflate+objstms diff --git a/MuPDF.NET/25-CompressSave/Expected/compressed.summary.txt b/MuPDF.NET/25-CompressSave/Expected/compressed.summary.txt new file mode 100644 index 0000000..cf4c51b --- /dev/null +++ b/MuPDF.NET/25-CompressSave/Expected/compressed.summary.txt @@ -0,0 +1,2 @@ +pageCount=3 +textSha256=31804ecc4c72047b41012fce71e3e0a203864fe1587c3283ad6ea34ee1ec41dc diff --git a/MuPDF.NET/25-CompressSave/Expected/plain.summary.txt b/MuPDF.NET/25-CompressSave/Expected/plain.summary.txt new file mode 100644 index 0000000..cf4c51b --- /dev/null +++ b/MuPDF.NET/25-CompressSave/Expected/plain.summary.txt @@ -0,0 +1,2 @@ +pageCount=3 +textSha256=31804ecc4c72047b41012fce71e3e0a203864fe1587c3283ad6ea34ee1ec41dc diff --git a/MuPDF.NET/25-CompressSave/Program.cs b/MuPDF.NET/25-CompressSave/Program.cs new file mode 100644 index 0000000..8b4402e --- /dev/null +++ b/MuPDF.NET/25-CompressSave/Program.cs @@ -0,0 +1,57 @@ +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.CompressSave; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 25-CompressSave"); + CompressSave(); + } + + /// + /// Compare a plain save vs a compressed save (garbage + deflate + object streams). + /// + static void CompressSave() + { + string input = ExamplePaths.MuPdfNetInput("sample.pdf"); + string plain = ExamplePaths.Output("MuPDF.NET", "25-CompressSave", "plain.pdf"); + string compressed = ExamplePaths.Output("MuPDF.NET", "25-CompressSave", "compressed.pdf"); + var check = new ResultCheck("MuPDF.NET", "25-CompressSave"); + var report = new StringBuilder(); + + using (var doc = Document.Open(input)) + doc.Save(plain); + + using (var doc = Document.Open(input)) + { + // Docs: https://mupdfnet.readthedocs.io/en/latest/compressing-files.html + doc.Save( + compressed, + garbage: 4, + deflate: 1, + deflateImages: 1, + deflateFonts: 1, + useObjstms: 1); + } + + long plainBytes = new System.IO.FileInfo(plain).Length; + long compressedBytes = new System.IO.FileInfo(compressed).Length; + bool smallerOrEqual = compressedBytes <= plainBytes; + report.Append("smallerOrEqual=").Append(smallerOrEqual ? "true" : "false").Append('\n'); + report.Append("savedWith=garbage4+deflate+objstms\n"); + + ConsoleEx.Info($"Plain: {plainBytes} bytes"); + ConsoleEx.Info($"Compressed: {compressedBytes} bytes"); + + check.Equal(smallerOrEqual, true, "compressed <= plain"); + check.Text(report.ToString(), "compress.txt"); + check.Properties(PdfFingerprint.FromFile(plain), "plain.summary.txt"); + check.Properties(PdfFingerprint.FromFile(compressed), "compressed.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/25-CompressSave/README.md b/MuPDF.NET/25-CompressSave/README.md new file mode 100644 index 0000000..b6b27fb --- /dev/null +++ b/MuPDF.NET/25-CompressSave/README.md @@ -0,0 +1,33 @@ +# 25-CompressSave + +Save with compression options (`garbage`, `deflate`, `useObjstms`) per the Compressing Files guide. + +## Sample method + +`CompressSave()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/sample.pdf` | +| Output | `plain.pdf`, `compressed.pdf` | +| Expected | `compress.txt` + PDF fingerprints | + +## Run + +```powershell +dotnet run --project MuPDF.NET\25-CompressSave +``` + +## APIs used + +- `Document.Save` (`garbage`, `deflate`, `deflateImages`, `deflateFonts`, `useObjstms`) + +## Related + +- [`20-RewriteImages`](../20-RewriteImages/) — downsample / recompress *embedded images* diff --git a/MuPDF.NET/26-Watermark/26-Watermark.csproj b/MuPDF.NET/26-Watermark/26-Watermark.csproj new file mode 100644 index 0000000..890f59e --- /dev/null +++ b/MuPDF.NET/26-Watermark/26-Watermark.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.Watermark + 26-Watermark + + + + + + diff --git a/MuPDF.NET/26-Watermark/Expected/watermarked.summary.txt b/MuPDF.NET/26-Watermark/Expected/watermarked.summary.txt new file mode 100644 index 0000000..134ee8d --- /dev/null +++ b/MuPDF.NET/26-Watermark/Expected/watermarked.summary.txt @@ -0,0 +1,3 @@ +pageCount=3 +textSha256=31804ecc4c72047b41012fce71e3e0a203864fe1587c3283ad6ea34ee1ec41dc +watermark=logo.png diff --git a/MuPDF.NET/26-Watermark/Program.cs b/MuPDF.NET/26-Watermark/Program.cs new file mode 100644 index 0000000..bbc6f17 --- /dev/null +++ b/MuPDF.NET/26-Watermark/Program.cs @@ -0,0 +1,53 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.Watermark; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 26-Watermark"); + Watermark(); + } + + /// + /// Insert a logo image as a watermark on every page. + /// + static void Watermark() + { + string input = ExamplePaths.MuPdfNetInput("sample.pdf"); + string logo = ExamplePaths.MuPdfNetInput("logo.png"); + string output = ExamplePaths.Output("MuPDF.NET", "26-Watermark", "watermarked.pdf"); + var check = new ResultCheck("MuPDF.NET", "26-Watermark"); + + using var doc = Document.Open(input); + // Insert once as a reusable xref, then place on each page (docs watermark tip). + int imageXref = -1; + for (int i = 0; i < doc.PageCount; i++) + { + using Page page = doc[i]; + Rect bounds = page.Rect; + // Centered watermark rectangle (~40% of page width). + float w = bounds.Width * 0.4f; + float h = bounds.Height * 0.4f; + float x0 = bounds.X0 + (bounds.Width - w) / 2f; + float y0 = bounds.Y0 + (bounds.Height - h) / 2f; + var rect = new Rect(x0, y0, x0 + w, y0 + h); + + if (imageXref < 0) + imageXref = page.InsertImage(rect, filename: logo, keepProportion: true, overlay: "true"); + else + page.InsertImage(rect, xref: imageXref, keepProportion: true, overlay: "true"); + } + + ConsoleEx.Info($"Watermark xref={imageXref} on {doc.PageCount} page(s)"); + doc.Save(output); + + var props = PdfFingerprint.FromFile(output); + props["watermark"] = Path.GetFileName(logo); + check.Properties(props, "watermarked.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/26-Watermark/README.md b/MuPDF.NET/26-Watermark/README.md new file mode 100644 index 0000000..4da11dd --- /dev/null +++ b/MuPDF.NET/26-Watermark/README.md @@ -0,0 +1,29 @@ +# 26-Watermark + +Add a centered image watermark on each page (`Page.InsertImage`). + +## Sample method + +`Watermark()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `sample.pdf`, `logo.png` | +| Output | `watermarked.pdf` | +| Expected | `watermarked.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\26-Watermark +``` + +## APIs used + +- `Page.InsertImage` (`filename` / `xref`, `overlay`) diff --git a/MuPDF.NET/27-ImageToPdf/27-ImageToPdf.csproj b/MuPDF.NET/27-ImageToPdf/27-ImageToPdf.csproj new file mode 100644 index 0000000..4820f23 --- /dev/null +++ b/MuPDF.NET/27-ImageToPdf/27-ImageToPdf.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.ImageToPdf + 27-ImageToPdf + + + + + + diff --git a/MuPDF.NET/27-ImageToPdf/Expected/apple.summary.txt b/MuPDF.NET/27-ImageToPdf/Expected/apple.summary.txt new file mode 100644 index 0000000..45e36cd --- /dev/null +++ b/MuPDF.NET/27-ImageToPdf/Expected/apple.summary.txt @@ -0,0 +1,5 @@ +height=400 +pageCount=1 +source=apple.png +textSha256=01ba4719c80b6fe911b091a7c05124b64eeece964e09c058ef8f9805daca546b +width=400 diff --git a/MuPDF.NET/27-ImageToPdf/Program.cs b/MuPDF.NET/27-ImageToPdf/Program.cs new file mode 100644 index 0000000..0cd00dc --- /dev/null +++ b/MuPDF.NET/27-ImageToPdf/Program.cs @@ -0,0 +1,43 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.ImageToPdf; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 27-ImageToPdf"); + ImageToPdf(); + } + + /// + /// Create a one-page PDF sized to an image and place the image full-bleed. + /// + static void ImageToPdf() + { + string imagePath = ExamplePaths.MuPdfNetInput("apple.png"); + string output = ExamplePaths.Output("MuPDF.NET", "27-ImageToPdf", "apple.pdf"); + var check = new ResultCheck("MuPDF.NET", "27-ImageToPdf"); + + using var pixmap = new Pixmap(imagePath); + // PDF page size in points: map 72 dpi → 1 point per pixel. + float width = pixmap.Width; + float height = pixmap.Height; + + using var doc = Document.Open(); + using Page page = doc.NewPage(width: width, height: height); + page.InsertImage(page.Rect, filename: imagePath); + doc.Save(output); + + ConsoleEx.Info($"Page {width}x{height} pt from {Path.GetFileName(imagePath)}"); + + var props = PdfFingerprint.FromFile(output); + props["source"] = Path.GetFileName(imagePath); + props["width"] = ((int)width).ToString(); + props["height"] = ((int)height).ToString(); + check.Properties(props, "apple.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/27-ImageToPdf/README.md b/MuPDF.NET/27-ImageToPdf/README.md new file mode 100644 index 0000000..3f6f352 --- /dev/null +++ b/MuPDF.NET/27-ImageToPdf/README.md @@ -0,0 +1,31 @@ +# 27-ImageToPdf + +Convert a PNG to a single-page PDF sized to the image. + +## Sample method + +`ImageToPdf()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/apple.png` | +| Output | `apple.pdf` | +| Expected | `apple.summary.txt` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\27-ImageToPdf +``` + +## APIs used + +- `Pixmap` (from image file) +- `Document.NewPage` +- `Page.InsertImage` diff --git a/MuPDF.NET/28-ImageFilters/28-ImageFilters.csproj b/MuPDF.NET/28-ImageFilters/28-ImageFilters.csproj new file mode 100644 index 0000000..b4dda9b --- /dev/null +++ b/MuPDF.NET/28-ImageFilters/28-ImageFilters.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.ImageFilters + 28-ImageFilters + + + + + + diff --git a/MuPDF.NET/28-ImageFilters/Expected/filtered.png.sha256 b/MuPDF.NET/28-ImageFilters/Expected/filtered.png.sha256 new file mode 100644 index 0000000..6cadf96 --- /dev/null +++ b/MuPDF.NET/28-ImageFilters/Expected/filtered.png.sha256 @@ -0,0 +1 @@ +36985832a404c205878733006046ec2ce0ef7f7f52c7156707e170e57b7759ad diff --git a/MuPDF.NET/28-ImageFilters/Program.cs b/MuPDF.NET/28-ImageFilters/Program.cs new file mode 100644 index 0000000..566629b --- /dev/null +++ b/MuPDF.NET/28-ImageFilters/Program.cs @@ -0,0 +1,42 @@ +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.ImageFilters; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 28-ImageFilters"); + ImageFilters(); + } + + /// + /// Apply a median filter pipeline to a pixmap and save the result. + /// + static void ImageFilters() + { + string input = ExamplePaths.MuPdfNetInput("apple.png"); + string output = ExamplePaths.Output("MuPDF.NET", "28-ImageFilters", "filtered.png"); + var check = new ResultCheck("MuPDF.NET", "28-ImageFilters"); + + using var source = new Pixmap(input); + int width = source.Width; + int height = source.Height; + + var pipeline = new ImageFilterPipeline(); + // Light denoise — stable, docs-friendly filter. + pipeline.AddMedian(3); + + // ApplyImageFilters may consume/dispose the source pixmap — do not use source afterward. + using Pixmap filtered = Pixmap.ApplyImageFilters(source, pipeline); + filtered.Save(output); + + ConsoleEx.Info($"Filtered {width}x{height} → {output}"); + check.FileSha256(output, "filtered.png.sha256"); + check.Equal(filtered.Width, width, "width preserved"); + check.Equal(filtered.Height, height, "height preserved"); + check.Finish(); + } +} diff --git a/MuPDF.NET/28-ImageFilters/README.md b/MuPDF.NET/28-ImageFilters/README.md new file mode 100644 index 0000000..f8506f0 --- /dev/null +++ b/MuPDF.NET/28-ImageFilters/README.md @@ -0,0 +1,30 @@ +# 28-ImageFilters + +Apply an `ImageFilterPipeline` (median) to a pixmap and save a PNG. + +## Sample method + +`ImageFilters()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) (includes SkiaSharp filters) + +## Input / output + +| | Path | +|--|------| +| Input | `Input/MuPDF.NET/apple.png` | +| Output | `filtered.png` | +| Expected | `filtered.png.sha256` | + +## Run + +```powershell +dotnet run --project MuPDF.NET\28-ImageFilters +``` + +## APIs used + +- `ImageFilterPipeline` / `Pixmap.ApplyImageFilters` +- `ImageProcessingFilterType.Median` diff --git a/MuPDF.NET/29-PageOps/29-PageOps.csproj b/MuPDF.NET/29-PageOps/29-PageOps.csproj new file mode 100644 index 0000000..a5eeb96 --- /dev/null +++ b/MuPDF.NET/29-PageOps/29-PageOps.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.PageOps + 29-PageOps + + + + + + diff --git a/MuPDF.NET/29-PageOps/Expected/built.summary.txt b/MuPDF.NET/29-PageOps/Expected/built.summary.txt new file mode 100644 index 0000000..7ebd979 --- /dev/null +++ b/MuPDF.NET/29-PageOps/Expected/built.summary.txt @@ -0,0 +1,2 @@ +pageCount=4 +textSha256=5f172bb604eeace2f2eb91d6c40cc0ff5562c50810552297fc8cc90a74b6ac96 diff --git a/MuPDF.NET/29-PageOps/Expected/page-ops.txt b/MuPDF.NET/29-PageOps/Expected/page-ops.txt new file mode 100644 index 0000000..b7815c1 --- /dev/null +++ b/MuPDF.NET/29-PageOps/Expected/page-ops.txt @@ -0,0 +1,4 @@ +built.pages=5 +afterMove.pages=5 +afterDelete.pages=4 +afterSelect.pages=1 diff --git a/MuPDF.NET/29-PageOps/Expected/selected.summary.txt b/MuPDF.NET/29-PageOps/Expected/selected.summary.txt new file mode 100644 index 0000000..9c73e9c --- /dev/null +++ b/MuPDF.NET/29-PageOps/Expected/selected.summary.txt @@ -0,0 +1,2 @@ +pageCount=1 +textSha256=84066220d566932ab9a20ca02d148b1189b51a8a61b2ea175859add46c3cdd30 diff --git a/MuPDF.NET/29-PageOps/Program.cs b/MuPDF.NET/29-PageOps/Program.cs new file mode 100644 index 0000000..c861218 --- /dev/null +++ b/MuPDF.NET/29-PageOps/Program.cs @@ -0,0 +1,61 @@ +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.PageOps; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 29-PageOps"); + PageOps(); + } + + /// + /// Delete, move, and select pages in a multi-page PDF. + /// + static void PageOps() + { + string a = ExamplePaths.MuPdfNetInput("sample.pdf"); + string b = ExamplePaths.MuPdfNetInput("Blank.pdf"); + string built = ExamplePaths.Output("MuPDF.NET", "29-PageOps", "built.pdf"); + string selected = ExamplePaths.Output("MuPDF.NET", "29-PageOps", "selected.pdf"); + var check = new ResultCheck("MuPDF.NET", "29-PageOps"); + var report = new StringBuilder(); + + // Build a 3-page document: sample page + blank + sample page again. + using (var doc = Document.Open(a)) + using (var blank = Document.Open(b)) + using (var again = Document.Open(a)) + { + doc.InsertPdf(blank, fromPage: 0, toPage: 0, startAt: 1); + doc.InsertPdf(again, fromPage: 0, toPage: 0, startAt: 2); + report.Append("built.pages=").Append(doc.PageCount).Append('\n'); + + // Move last page to the front. + doc.MovePage(doc.PageCount - 1, 0); + report.Append("afterMove.pages=").Append(doc.PageCount).Append('\n'); + + // Delete the middle page (index 1). + doc.DeletePage(1); + report.Append("afterDelete.pages=").Append(doc.PageCount).Append('\n'); + + doc.Save(built); + } + + // Select keeps only listed page numbers (0-based), in that order. + using (var doc = Document.Open(built)) + { + doc.Select(new[] { 0 }); + report.Append("afterSelect.pages=").Append(doc.PageCount).Append('\n'); + doc.Save(selected); + } + + check.Text(report.ToString(), "page-ops.txt"); + check.Properties(PdfFingerprint.FromFile(built), "built.summary.txt"); + check.Properties(PdfFingerprint.FromFile(selected), "selected.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/29-PageOps/README.md b/MuPDF.NET/29-PageOps/README.md new file mode 100644 index 0000000..278248d --- /dev/null +++ b/MuPDF.NET/29-PageOps/README.md @@ -0,0 +1,36 @@ +# 29-PageOps + +Delete, move, and select pages (`DeletePage`, `MovePage`, `Select`). + +## Sample method + +`PageOps()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `sample.pdf`, `Blank.pdf` | +| Output | `built.pdf`, `selected.pdf` | +| Expected | `page-ops.txt` + fingerprints | + +## Run + +```powershell +dotnet run --project MuPDF.NET\29-PageOps +``` + +## APIs used + +- `Document.InsertPdf` +- `Document.MovePage` +- `Document.DeletePage` +- `Document.Select` + +## Related + +- [`02-PagesMergeSplit`](../02-PagesMergeSplit/) — merge / extract via `InsertPdf` only diff --git a/MuPDF.NET/30-FileAnnot/30-FileAnnot.csproj b/MuPDF.NET/30-FileAnnot/30-FileAnnot.csproj new file mode 100644 index 0000000..dababb3 --- /dev/null +++ b/MuPDF.NET/30-FileAnnot/30-FileAnnot.csproj @@ -0,0 +1,11 @@ + + + Exe + MuPDF.NET.Examples.MuPDFNet.FileAnnot + 30-FileAnnot + + + + + + diff --git a/MuPDF.NET/30-FileAnnot/Expected/file-annot.txt b/MuPDF.NET/30-FileAnnot/Expected/file-annot.txt new file mode 100644 index 0000000..d3bdc7d --- /dev/null +++ b/MuPDF.NET/30-FileAnnot/Expected/file-annot.txt @@ -0,0 +1,5 @@ +annotType=FileAttachment +filename=note.txt +size=42 +annotCount=1 +type=FileAttachment diff --git a/MuPDF.NET/30-FileAnnot/Expected/with-file-annot.summary.txt b/MuPDF.NET/30-FileAnnot/Expected/with-file-annot.summary.txt new file mode 100644 index 0000000..75896d0 --- /dev/null +++ b/MuPDF.NET/30-FileAnnot/Expected/with-file-annot.summary.txt @@ -0,0 +1,2 @@ +pageCount=1 +textSha256=16647954c146404e2b7418ad84f45ea5430a2b844ae367ccb8ad371b8b8465f5 diff --git a/MuPDF.NET/30-FileAnnot/Program.cs b/MuPDF.NET/30-FileAnnot/Program.cs new file mode 100644 index 0000000..50eb280 --- /dev/null +++ b/MuPDF.NET/30-FileAnnot/Program.cs @@ -0,0 +1,62 @@ +using System.Linq; +using System.Text; +using MuPDF.NET; +using MuPDF.NET.Examples.Common; + +namespace MuPDF.NET.Examples.MuPDFNet.FileAnnot; + +internal static class Program +{ + static void Main(string[] args) + { + ExampleArgs.Parse(args); + ConsoleEx.Title("MuPDF.NET / 30-FileAnnot"); + FileAnnot(); + } + + /// + /// Attach a file as a page annotation (paperclip), distinct from EmbeddedFiles. + /// + static void FileAnnot() + { + string blank = ExamplePaths.MuPdfNetInput("Blank.pdf"); + string note = ExamplePaths.MuPdfNetInput("note.txt"); + string output = ExamplePaths.Output("MuPDF.NET", "30-FileAnnot", "with-file-annot.pdf"); + var check = new ResultCheck("MuPDF.NET", "30-FileAnnot"); + var report = new StringBuilder(); + + byte[] payload = File.ReadAllBytes(note); + + using (var doc = Document.Open(blank)) + using (Page page = doc[0]) + { + // Page-level file attachment annotation (vs Document.AddEmbeddedFile). + Annot annot = page.AddFileAnnot( + point: new Point(72, 72), + buffer_: payload, + filename: "note.txt", + uFileName: "note.txt", + desc: "Example file annotation"); + annot.Update(); + + report.Append("annotType=").Append(annot.TypeString).Append('\n'); + var fileInfo = annot.GetFileInfo(); + report.Append("filename=").Append(fileInfo.GetValueOrDefault("filename") ?? "").Append('\n'); + report.Append("size=").Append(fileInfo.GetValueOrDefault("size") ?? payload.Length).Append('\n'); + doc.Save(output, garbage: 3, deflate: 1); + } + + using (var doc = Document.Open(output)) + using (Page page = doc[0]) + { + var annots = page.Annots().ToList(); + report.Append("annotCount=").Append(annots.Count).Append('\n'); + foreach (Annot a in annots) + report.Append("type=").Append(a.TypeString).Append('\n'); + } + + check.Text(report.ToString(), "file-annot.txt"); + check.Properties(PdfFingerprint.FromFile(output), "with-file-annot.summary.txt"); + check.Finish(); + } +} diff --git a/MuPDF.NET/30-FileAnnot/README.md b/MuPDF.NET/30-FileAnnot/README.md new file mode 100644 index 0000000..566e629 --- /dev/null +++ b/MuPDF.NET/30-FileAnnot/README.md @@ -0,0 +1,30 @@ +# 30-FileAnnot + +Attach a file as a **page annotation** (`Page.AddFileAnnot`). For document-level EmbeddedFiles, see [`13-EmbeddedFiles`](../13-EmbeddedFiles/). + +## Sample method + +`FileAnnot()` in `Program.cs`. + +## Package + +- [MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET) + +## Input / output + +| | Path | +|--|------| +| Input | `Blank.pdf`, `note.txt` | +| Output | `with-file-annot.pdf` | +| Expected | `file-annot.txt` + fingerprint | + +## Run + +```powershell +dotnet run --project MuPDF.NET\30-FileAnnot +``` + +## APIs used + +- `Page.AddFileAnnot` +- `Page.GetAnnots` diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..0589deb --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/Output/.gitignore b/Output/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/Output/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/README.md b/README.md index 09038d3..292f3d6 100644 --- a/README.md +++ b/README.md @@ -1 +1,107 @@ -# MuPDF.NET.Examples \ No newline at end of file +# MuPDF.NET.Examples + +Customer-facing sample apps for **[MuPDF.NET](https://www.nuget.org/packages/MuPDF.NET)**, **[MuPDF.NET.PDF4LLM](https://www.nuget.org/packages/MuPDF.NET.PDF4LLM)**, and **[MuPDF.NET.Office](https://www.nuget.org/packages/MuPDF.NET.Office)**. + +Each feature is a small console project. Packages are referenced via **NuGet only**. + +Every `Program.cs` uses `Main` plus one named sample method (for example `OpenSave()`) with line comments so you can copy that method into your own project. `Main` only parses args and calls the sample. + +Every example writes results under `Output/` and **compares them to golden files** in that project’s `Expected/` folder. Look for **`PASS —`** / **`FAIL —`** / **`SKIP —`** in the console. Use `run-all.cmd`, `.\run-all.ps1`, or `./run-all.sh` to batch-test after NuGet upgrades (they key off those lines; Office samples may AV during process teardown after a successful PASS). + +## Clone and restore + +```powershell +git clone https://github.com/ArtifexSoftware/MuPDF.NET.Examples.git +cd MuPDF.NET.Examples +dotnet restore +``` + +Package versions are pinned in [`Versions.props`](Versions.props). [`NuGet.Config`](NuGet.Config) uses a repo-local **`LocalNuget/`** folder (optional pre-release packages) and **nuget.org**. On Windows you can symlink `LocalNuget` → `D:\Artifex\LocalNuget` if you pack there. + +## Layout + +| Path | Role | +|------|------| +| `Common/` | Paths, `ResultCheck`, PDF fingerprints | +| `Input/` | Fixtures (committed) | +| `Output/` | Generated files (gitignored) | +| `{product}/{NN-Name}/Expected/` | Golden baselines (committed) | +| `run-all.cmd` / `run-all.ps1` / `run-all.sh` | Batch PASS/FAIL/SKIP runner | +| `LocalNuget/` | Optional local `.nupkg` drop folder | +| `LICENSE.md` | Artifex Community License | +| `SECURITY.md` | Vulnerability reporting | + +## Prerequisites + +- .NET 8 SDK (Windows, Linux, or macOS) +- Packages from [nuget.org](https://www.nuget.org/) (see `Versions.props`) +- Optional `MUPDF_OFFICE_KEY` for Office unlock +- `02-ToJsonLayout` / `03-ToText`: optional [pymupdf-layout](https://pypi.org/project/pymupdf-layout/) for layout mode + +## Run (compare against Expected/) + +```powershell +dotnet restore +.\run-all.cmd # Windows +# or: .\run-all.ps1 +# or one project: +dotnet run --project MuPDF.NET\01-OpenSave +``` + +```bash +# Linux / macOS (requires pwsh: https://aka.ms/powershell) +dotnet restore +chmod +x ./run-all.sh +./run-all.sh +# or: pwsh ./run-all.ps1 +dotnet run --project MuPDF.NET/01-OpenSave +``` + +## Refresh baselines after a trusted package upgrade + +```powershell +.\run-all.cmd --update-expected +# or: .\run-all.ps1 -UpdateExpected +# or: +dotnet run --project MuPDF.NET\01-OpenSave -- --update-expected +``` + +## What is compared + +| Kind | Method | +|------|--------| +| Markdown / JSON / text | Exact text (LF-normalized) | +| PNG | SHA-256 | +| PDF | `pageCount` + SHA-256 of extracted text (not raw PDF bytes) | +| Unlock / page counts | Small `*.summary.txt` property files | + +## Example projects + +Each project folder has its own `README.md` (what it shows, inputs, how to run, APIs). + +**Naming tip — color vs images** + +| Want to… | Use | +|----------|-----| +| Convert page to DeviceCMYK/RGB/Gray | `05-Recolor` | +| ICC soft proof, separations, overprint | `19-ColorManagement` | +| Place a new image on a page | `09-InsertImage` | +| Swap an existing image xref | `17-ReplaceImage` | +| Extract embedded images to files | `21-ExtractImages` | +| Downsample / recompress images | `20-RewriteImages` | +| Compress PDF streams on save | `25-CompressSave` | +| Page file attachment annot | `30-FileAnnot` (vs `13-EmbeddedFiles`) | + +| Product | Projects | +|---------|----------| +| MuPDF.NET | Open/Save, Pages, Render, Text, Recolor, Story/HTML, Annotations, Widgets, Insert/Replace/Extract images, Outline/Links, Tables, Barcodes, Embedded files, Metadata, TextWriter, Draw shapes, ZUGFeRD, Color management, Rewrite images, GetDrawings, Rotate/Crop, Encrypt, Compress, Watermark, Image→PDF, Image filters, Page ops, File annot | +| MuPDF.NET.PDF4LLM | Markdown, JSON layout, Plain text, OCR, Tables→CSV, Llama markdown reader, GetKeyValues, Markdown→PDF | +| MuPDF.NET.Office | Unlock/fonts, Open HWPX/DOCX, Export PDF, Export MD/JSON, With PDF4LLM | + +## Versions + +Edit `Versions.props` only. + +## License + +See [`LICENSE.md`](LICENSE.md) (Artifex Community License). The NuGet packages these samples use have their own license terms on nuget.org / artifex.com. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..cd7673f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Supported versions + +Use the NuGet package versions pinned in [`Versions.props`](Versions.props) with these examples. + +## Reporting a vulnerability + +Please do **not** open a public GitHub issue for security-sensitive reports. + +Contact Artifex Software, Inc.: + +- https://artifex.com/contact +- Or your existing Artifex support channel if you are a commercial licensee + +Include the example project name, package versions from `Versions.props`, OS/.NET version, and steps to reproduce when possible. diff --git a/Versions.props b/Versions.props new file mode 100644 index 0000000..2bad24f --- /dev/null +++ b/Versions.props @@ -0,0 +1,11 @@ + + + + 3.28.2 + 1.28.2 + 1.28.2 + + diff --git a/run-all.cmd b/run-all.cmd new file mode 100644 index 0000000..1789b35 --- /dev/null +++ b/run-all.cmd @@ -0,0 +1,16 @@ +@echo off +setlocal +cd /d "%~dp0" + +rem Run all MuPDF.NET.Examples NN-* projects and report PASS/FAIL vs Expected/. +rem Usage: +rem run-all.cmd +rem run-all.cmd --update-expected + +set "PSARGS=" +if /I "%~1"=="--update-expected" set "PSARGS=-UpdateExpected" +if /I "%~1"=="-UpdateExpected" set "PSARGS=-UpdateExpected" +if /I "%~1"=="/UpdateExpected" set "PSARGS=-UpdateExpected" + +powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0run-all.ps1" %PSARGS% +exit /b %ERRORLEVEL% diff --git a/run-all.ps1 b/run-all.ps1 new file mode 100644 index 0000000..c5eaf7f --- /dev/null +++ b/run-all.ps1 @@ -0,0 +1,86 @@ +#Requires -Version 5.1 +<# +.SYNOPSIS + Run all MuPDF.NET.Examples projects and report PASS/FAIL/SKIP vs Expected/ baselines. + +.PARAMETER UpdateExpected + Refresh Expected/ baselines from the current NuGet packages. +#> +param( + [switch] $UpdateExpected, + [string] $Configuration = 'Release' +) + +$root = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $root + +# Do not use $IsWindows — it is a read-only automatic variable in PowerShell 6+. +$useCmdHost = [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform( + [System.Runtime.InteropServices.OSPlatform]::Windows) + +dotnet build -c $Configuration +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + +$projects = @(Get-ChildItem -Recurse -Filter *.csproj | + Where-Object { $_.Directory.Name -match '^\d{2}-' } | + Sort-Object FullName) + +$fail = 0 +$skip = 0 +$pass = 0 + +foreach ($p in $projects) { + $name = $p.Directory.Name + + $argList = @( + 'run', '--project', $p.FullName, + '-c', $Configuration, '--no-build' + ) + if ($UpdateExpected) { + $argList += '--' + $argList += '--update-expected' + } + + $tmpName = "mupdf-examples-" + [guid]::NewGuid().ToString('N') + ".log" + $tmp = if ($env:TEMP) { Join-Path $env:TEMP $tmpName } else { Join-Path ([IO.Path]::GetTempPath()) $tmpName } + + if ($useCmdHost) { + # cmd so native AVs / stderr do not abort the PowerShell driver on Windows. + $joined = ($argList | ForEach-Object { + if ($_ -match '\s') { '"' + $_ + '"' } else { $_ } + }) -join ' ' + cmd /c "dotnet $joined > `"$tmp`" 2>&1" + } + else { + & dotnet @argList *> $tmp + } + + $out = Get-Content -Raw -ErrorAction SilentlyContinue $tmp + Remove-Item $tmp -Force -ErrorAction SilentlyContinue + + if ($out -match 'SKIP —') { + Write-Host "SKIP $name" + $skip++ + continue + } + + $ok = if ($UpdateExpected) { + $out -match 'Baselines updated' + } else { + $out -match 'PASS —' + } + + if ($ok) { + Write-Host "PASS $name" + $pass++ + } + else { + Write-Host "FAIL $name" + if ($out) { Write-Host $out } + $fail++ + } +} + +Write-Host "" +Write-Host "PASS: $pass SKIP: $skip FAIL: $fail / $($projects.Count) projects" +exit $(if ($fail -eq 0) { 0 } else { 1 }) diff --git a/run-all.sh b/run-all.sh new file mode 100644 index 0000000..63b890d --- /dev/null +++ b/run-all.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Run all MuPDF.NET.Examples projects (Linux/macOS). +set -euo pipefail +ROOT="$(cd "$(dirname "$0")" && pwd)" +cd "$ROOT" + +UPDATE_EXPECTED=0 +CONFIGURATION=Release + +for arg in "$@"; do + case "$arg" in + --update-expected|-UpdateExpected) UPDATE_EXPECTED=1 ;; + --configuration=*) CONFIGURATION="${arg#*=}" ;; + esac +done + +PSARGS=(-Configuration "$CONFIGURATION") +if [[ "$UPDATE_EXPECTED" -eq 1 ]]; then + PSARGS+=(-UpdateExpected) +fi + +if command -v pwsh >/dev/null 2>&1; then + exec pwsh -NoProfile -File "$ROOT/run-all.ps1" "${PSARGS[@]}" +elif command -v powershell >/dev/null 2>&1; then + exec powershell -NoProfile -File "$ROOT/run-all.ps1" "${PSARGS[@]}" +fi + +echo "PowerShell (pwsh) is required. Install: https://aka.ms/powershell" +echo "Or run projects individually with: dotnet run --project MuPDF.NET/01-OpenSave" +exit 1