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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ x64/
build/
[Bb]in/
[Oo]bj/
[Oo]utput/

# MSTest test Results
[Tt]est[Rr]esult*/
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

### [1.3.5] - 2026-08-28

- **Office files:** Word, Excel, and PowerPoint can be converted and rasterized when a licensed GhostPDL library is present. Without it, Office APIs throw `GhostscriptPdlLibraryNotFoundException` and point users to Artifex for a commercial Ghostscript.NET license.
- **Existing processor code:** `GhostscriptProcessor` detects an Office path in the argument list, loads `gpdldll` if it is on the search path, and treats `-dSAFER` as `-dNOSAFER` for that job. Callers can keep `GetLastInstalledVersion()`.
- **32-bit GhostPDL:** `gpdldll32.dll` exports stdcall names (`_gsapi_revision@8`). Ghostscript.NET now resolves those in x86 processes. NativeAssets `gsdll32.dll` already used undecorated names, so it was unaffected.
- GhostPDL/SmartOffice is **commercial**. It is not in `Ghostscript.NativeAssets` or on nuget.org. Licensed users copy the library from Ghostscript.NET.Office into their project.

### [1.3.4] - 2026-07-20

- **Bundled native library discovery:** `GhostscriptVersionInfo` can locate app-local Ghostscript binaries (for example from `Ghostscript.NativeAssets`) via `TryGetBundledVersion`, `GetBundledVersion`, and `GetPreferredVersion`. `GetLastInstalledVersion` now prefers bundled assets before falling back to a system installation.
Expand Down
4 changes: 2 additions & 2 deletions Description.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
**Ghostscript.NET** is the most completed managed wrapper library around the [Ghostscript](https://ghostscript.com) library - an interpreter for PDF and PostScript files.
**Ghostscript.NET** is the most completed managed wrapper library around the [Ghostscript](https://ghostscript.com) library - an interpreter for PDF and PostScript files. With a licensed GhostPDL library (`gpdldll` / `libgpdl`) it also converts Microsoft Office documents.

Ghostscript can be provided via a system install or the optional `Ghostscript.NativeAssets` NuGet package (app-local binaries).
Ghostscript can be provided via a system install or the optional `Ghostscript.NativeAssets` NuGet package (app-local binaries). Office/SmartOffice natives are **not** in that package; licensed users obtain them from Ghostscript.NET.Office.

### Features

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<Content Include="_res\ghostscript-dotnet.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Ghostscript.NativeAssets" Version="$(GhostscriptNativeAssetsVersion)" />
<PackageReference Include="SkiaSharp" Version="3.119.1" />
<PackageReference Include="SkiaSharp.Views.WindowsForms" Version="3.119.1" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Ghostscript.NativeAssets" Version="$(GhostscriptNativeAssetsVersion)" />
<PackageReference Include="ZUGFeRD-csharp" Version="14.1.0" />
</ItemGroup>

Expand Down
32 changes: 25 additions & 7 deletions Ghostscript.NET.PDFA3Converter.Samples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,36 @@ static void Main(string[] args)

if (!GhostscriptVersionInfo.IsGhostscriptInstalled)
{
throw new Exception("You don't have Ghostscript installed on this machine!");
throw new Exception("Ghostscript was not found. Install Ghostscript or reference Ghostscript.NativeAssets.");
}

ISample sample;
Console.WriteLine("Using Ghostscript: " + SamplePaths.ResolveGhostscriptDll());

sample = new FacturXWithMustangSample();
sample.Start();
int failed = 0;
ISample[] samples =
{
new FacturXWithMustangSample(),
new FacturXWithZUGFeRDcsharpSample()
};

sample = new FacturXWithZUGFeRDcsharpSample();
sample.Start();
foreach (ISample sample in samples)
{
string name = sample.GetType().Name;
try
{
sample.Start();
Console.WriteLine(name + " completed.");
}
catch (Exception ex)
{
failed++;
Console.WriteLine(name + " failed: " + ex.Message);
}
}

Console.ReadLine();
Console.WriteLine(failed == 0
? "PDFA3Converter samples completed."
: failed + " sample(s) failed.");
}
}
}
60 changes: 60 additions & 0 deletions Ghostscript.NET.PDFA3Converter.Samples/SamplePaths.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (C) 2024 Artifex Software, Inc.
//
// This file is part of Ghostscript.NET.
//
// Ghostscript.NET is free software: you can redistribute it and/or modify it
// under the terms of the GNU Affero General Public License as published by the
// Free Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// Ghostscript.NET is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Ghostscript.NET. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
//
// Alternative licensing terms are available from the licensor.
// For commercial licensing, see <https://www.artifex.com/> or contact
// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
// CA 94129, USA, for further information.

using System;
using System.IO;

namespace Ghostscript.NET.PDFA3Converter.Samples
{
internal static class SamplePaths
{
public static string ResolveGhostscriptDll()
{
GhostscriptVersionInfo version = GhostscriptVersionInfo.GetPreferredVersion();
return version.DllPath;
}

public static string GetBlankPdf()
{
string[] candidates =
{
Path.Combine(AppContext.BaseDirectory, "Samples", "blank.pdf"),
Path.GetFullPath(Path.Combine("Samples", "blank.pdf"))
};

foreach (string candidate in candidates)
{
if (File.Exists(candidate))
{
return candidate;
}
}

throw new FileNotFoundException("Could not find Samples/blank.pdf.");
}

public static string GetOutputPath(string fileName)
{
return Path.Combine(AppContext.BaseDirectory, fileName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,21 @@ public void Start()
zf2p.generateXML(i);
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();

string outfilename = "my-factur-x-mustang-sample.xml";
string outfilename = SamplePaths.GetOutputPath("my-factur-x-mustang-sample.xml");
File.WriteAllBytes(outfilename, zf2p.getXML());

string gsFilePath = @"C:\Program Files\gs\gs10.06.0\bin\gsdll64.dll";
Console.WriteLine("Using Ghostscript filepath: " + gsFilePath);
Console.WriteLine("Ensure this is the filepath to your installed Ghostscript!");
string gsFilePath = SamplePaths.ResolveGhostscriptDll();
Console.WriteLine("Using Ghostscript: " + gsFilePath);

PDFA3Converter converter = new PDFA3Converter(gsFilePath);

converter.SetZUGFeRDProfile(Profiles.getByName("EN16931").getXMPName());
converter.SetZUGFeRDVersion("2.3");
converter.SetEmbeddedXMLFile(outfilename);

converter.ConvertToPDFA3(@"Samples/blank.pdf", @"sample-invoice-mustang.pdf");
string outputPdf = SamplePaths.GetOutputPath("sample-invoice-mustang.pdf");
converter.ConvertToPDFA3(SamplePaths.GetBlankPdf(), outputPdf);
Console.WriteLine("Wrote " + outputPdf);
System.IO.File.Delete(outfilename);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,19 @@ public void Start()
{
Console.WriteLine(Environment.NewLine);
Console.WriteLine("Running FacturXWithZUGFeRDcsharpSample");
string outFilename = "my-factur-x-zugferd-sample.xml";
string outFilename = SamplePaths.GetOutputPath("my-factur-x-zugferd-sample.xml");
InvoiceDescriptor invoice = CreateInvoice();
invoice.Save(outFilename, ZUGFeRDVersion.Version22, s2industries.ZUGFeRD.Profile.Comfort);

string gsFilePath = @"C:\Program Files\gs\gs10.06.0\bin\gsdll64.dll";
Console.WriteLine("Using Ghostscript filepath: "+ gsFilePath);
Console.WriteLine("Ensure this is the filepath to your installed Ghostscript!");
string gsFilePath = SamplePaths.ResolveGhostscriptDll();
Console.WriteLine("Using Ghostscript: " + gsFilePath);
PDFA3Converter converter = new PDFA3Converter(gsFilePath);
converter.SetZUGFeRDProfile("EN 16931");
converter.SetZUGFeRDVersion("2.3");
converter.SetEmbeddedXMLFile(outFilename);
converter.ConvertToPDFA3(@"Samples/blank.pdf", @"sample-invoice-zugferd.pdf");
string outputPdf = SamplePaths.GetOutputPath("sample-invoice-zugferd.pdf");
converter.ConvertToPDFA3(SamplePaths.GetBlankPdf(), outputPdf);
Console.WriteLine("Wrote " + outputPdf);
System.IO.File.Delete(outFilename);
}

Expand Down
3 changes: 3 additions & 0 deletions Ghostscript.NET.Samples/Ghostscript.NET.Samples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
<ItemGroup>
<ProjectReference Include="..\Ghostscript.NET\Ghostscript.NET.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="TestFiles\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Ghostscript.NativeAssets" Version="$(GhostscriptNativeAssetsVersion)" />
<PackageReference Include="IKVM" Version="8.1.5717.0" />
Expand Down
42 changes: 27 additions & 15 deletions Ghostscript.NET.Samples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,20 @@
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using Ghostscript.NET;
using Ghostscript.NET.Processor;
using Ghostscript.NET.Rasterizer;
using Ghostscript.NET.Samples;
using javax.print.attribute.standard;
using SkiaSharp;
using SixLabors.ImageSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

Console.WriteLine("Ghostscript.NET Samples");

if (!GhostscriptVersionInfo.IsGhostscriptInstalled)
{
throw new Exception("You don't have Ghostscript installed on this machine!");
throw new Exception("Ghostscript was not found. Install Ghostscript or reference Ghostscript.NativeAssets.");
}

Console.WriteLine("Using Ghostscript: " + GhostscriptVersionInfo.GetPreferredVersion().DllPath);

List<ISample> samples = new()
{
new GetInkCoverageSample(),
Expand All @@ -57,18 +53,34 @@
new DeviceUsageSample(),
new PipedOutputSample(),
new SendToPrinterSample(),
new UnicodeTestSample()
new UnicodeTestSample(),
new OfficeSupportSample()
};

string outputDir = SampleFiles.OutputDirectory;
Directory.CreateDirectory(outputDir);
Directory.CreateDirectory("Output");

int failed = 0;
foreach (ISample sample in samples)
{
string path = @"Output";

if (!Directory.Exists(path))
string name = sample.GetType().Name;
Console.WriteLine();
Console.WriteLine("--- " + name + " ---");
try
{
Directory.CreateDirectory(path);
sample.Start();
Console.WriteLine("Sample '" + name + "' completed.");
}
catch (Exception ex)
{
failed++;
Console.WriteLine("Sample '" + name + "' failed: " + ex.Message);
}
}

sample.Start();
Console.WriteLine($"Sample '{sample.GetType().Name}' run successful!");
}
Console.WriteLine();
Console.WriteLine(failed == 0
? "All samples completed. Exiting."
: failed + " sample(s) failed. Exiting.");
Environment.Exit(failed == 0 ? 0 : 1);
67 changes: 67 additions & 0 deletions Ghostscript.NET.Samples/SampleFiles.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// SampleFiles.cs
// This file is part of Ghostscript.NET.Samples project
//
// Author: Artifex Software Inc.
// Copyright (c) 2026 by Artifex Software Inc. All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;

namespace Ghostscript.NET.Samples
{
internal static class SampleFiles
{
public static string Get(string fileName)
{
string path;
if (TryGet(fileName, out path))
{
return path;
}

throw new FileNotFoundException("Sample file not found: " + fileName);
}

public static bool TryGet(string fileName, out string path)
{
path = null;

foreach (string directory in GetSearchDirectories())
{
string candidate = Path.Combine(directory, fileName);
if (File.Exists(candidate))
{
path = Path.GetFullPath(candidate);
return true;
}
}

return false;
}

public static string OutputDirectory
{
get
{
string directory = Path.Combine(AppContext.BaseDirectory, "Output");
Directory.CreateDirectory(directory);
return directory;
}
}

private static IEnumerable<string> GetSearchDirectories()
{
yield return Path.Combine(AppContext.BaseDirectory, "TestFiles");
yield return Path.GetFullPath("TestFiles");

string directory = AppContext.BaseDirectory;
for (int i = 0; i < 6 && !string.IsNullOrEmpty(directory); i++)
{
yield return Path.Combine(directory, "TestFiles");
directory = Path.GetDirectoryName(directory);
}
}
}
}
Loading