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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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
55 changes: 55 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
69 changes: 69 additions & 0 deletions Common/ConsoleEx.cs
Original file line number Diff line number Diff line change
@@ -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 + " ===");
}

/// <summary>
/// Print Artifex package versions used by this process.
/// Always prints MuPDF.NET + MuPDF; also Office / PDF4LLM when referenced.
/// </summary>
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<AssemblyInformationalVersionAttribute>()
?.InformationalVersion;
if (!string.IsNullOrWhiteSpace(informational))
return informational;

return asm.GetName().Version?.ToString();
}
catch
{
return null;
}
}
}
}
34 changes: 34 additions & 0 deletions Common/ExampleArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System;

namespace MuPDF.NET.Examples.Common
{
/// <summary>Command-line flags shared by all examples.</summary>
public static class ExampleArgs
{
/// <summary>
/// When true, write current results into <c>Expected/</c> instead of comparing.
/// Pass <c>--update-expected</c> after a trusted NuGet upgrade to refresh baselines.
/// </summary>
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);
}
}
}
}
}
70 changes: 70 additions & 0 deletions Common/ExamplePaths.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.IO;

namespace MuPDF.NET.Examples.Common
{
/// <summary>
/// Resolves shared <c>Input/</c> and <c>Output/</c> folders for example projects.
/// </summary>
public static class ExamplePaths
{
static readonly Lazy<string> RootLazy = new(FindRoot);

/// <summary>Solution root (folder that contains <c>Input/</c> and <c>Output/</c>).</summary>
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));

/// <summary>
/// Output path under <c>Output/{product}/{exampleName}/</c>. Creates the directory.
/// </summary>
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);
}

/// <summary>Directory <c>{product}/{exampleName}/Expected/</c> under the solution root.</summary>
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.");
}
}
}
14 changes: 14 additions & 0 deletions Common/Examples.Common.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>MuPDF.NET.Examples.Common</RootNamespace>
<AssemblyName>MuPDF.NET.Examples.Common</AssemblyName>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<!-- Needed for PdfFingerprint (stable PDF regression checks). -->
<PackageReference Include="MuPDF.NET" Version="$(ArtifexMuPDFNetVersion)" />
</ItemGroup>

</Project>
83 changes: 83 additions & 0 deletions Common/OfficeJsonFingerprint.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Portable checks for <c>MuPDFOffice.ToJson</c> 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.
/// </summary>
public static class OfficeJsonFingerprint
{
public static Dictionary<string, string> 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<string, string>
{
["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<string, string>
{
["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();
}
}
}
16 changes: 16 additions & 0 deletions Common/OfficeLicense.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;

namespace MuPDF.NET.Examples.Common
{
/// <summary>
/// Office license helpers. Prefer <c>MUPDF_OFFICE_KEY</c>; empty = restricted mode.
/// </summary>
public static class OfficeLicense
{
public static string? KeyFromEnvironment()
{
string? key = Environment.GetEnvironmentVariable("MUPDF_OFFICE_KEY");
return string.IsNullOrWhiteSpace(key) ? null : key.Trim();
}
}
}
Loading