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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 24 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,32 +529,36 @@ public async Task<Stream> FastConversion()
}
```

### Standalone PDF Engine Operations
*Flatten, rotate, encrypt, and manipulate existing PDFs:*
### Watermark & Rotation
*Add text watermarks and rotate PDF pages — available on all request types:*

```csharp
// Flatten form fields
var flattenResult = await _sharpClient.ExecutePdfEngineAsync(
PdfEngineBuilders.Flatten().WithPdfs(a => a.AddItem("form.pdf", pdfBytes)));
public async Task<Stream> CreateWatermarkedPdf()
{
var builder = new HtmlRequestBuilder()
.AddDocument(doc => doc.SetBody("<html><body><h1>Report</h1></body></html>"))
.SetWatermarkOptions(w => w.SetTextWatermark("DRAFT", "1-3"))
.SetRotationOptions(r => r.SetAngle(90).SetPages("2"))
.WithPageProperties(pp => pp.UseChromeDefaults());

// Rotate pages 90 degrees
var rotateResult = await _sharpClient.ExecutePdfEngineAsync(
PdfEngineBuilders.Rotate(90, "1-3").WithPdfs(a => a.AddItem("doc.pdf", pdfBytes)));
var request = builder.Build();
return await _sharpClient.HtmlToPdfAsync(request);
}
```

// Encrypt with passwords
var encrypted = await _sharpClient.ExecutePdfEngineAsync(
PdfEngineBuilders.Encrypt("reader123", "admin456").WithPdfs(a => a.AddItem("doc.pdf", pdfBytes)));
### Split PDFs
*Split generated PDFs into chunks or extract specific pages:*

// Read metadata (returns JSON)
var metadataJson = await _sharpClient.ReadPdfMetadataAsync(
PdfEngineBuilders.ReadMetadata().WithPdfs(a => a.AddItem("doc.pdf", pdfBytes)));
```csharp
public async Task<Stream> SplitPdf()
{
var builder = new HtmlRequestBuilder()
.AddDocument(doc => doc.SetBody("<html><body>Multi-page content</body></html>"))
.SetSplitOptions(s => s.SplitByPages("1-3,5", unify: true));

// Write metadata
var result = await _sharpClient.ExecutePdfEngineAsync(
PdfEngineBuilders.WriteMetadata(new Dictionary<string, object>
{
{ "Author", "John Doe" }, { "Title", "My Document" }
}).WithPdfs(a => a.AddItem("doc.pdf", pdfBytes)));
var request = builder.Build();
return await _sharpClient.HtmlToPdfAsync(request);
}
```

### Custom Page Properties
Expand Down
62 changes: 62 additions & 0 deletions examples/WatermarkAndRotate/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Gotenberg.Sharp.API.Client;
using Gotenberg.Sharp.API.Client.Domain.Builders;
using Gotenberg.Sharp.API.Client.Domain.ValueObjects;
using Gotenberg.Sharp.API.Client.Domain.Settings;
using Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;

using Microsoft.Extensions.Configuration;

var config = new ConfigurationBuilder()
.SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json")
.Build();

var options = new GotenbergSharpClientOptions();
config.GetSection(nameof(GotenbergSharpClient)).Bind(options);

var destinationDirectory = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "output");
Directory.CreateDirectory(destinationDirectory);

var path = await CreateWatermarkedAndRotatedPdf(destinationDirectory, options);
Console.WriteLine($"Watermarked & rotated PDF created: {path}");

static async Task<string> CreateWatermarkedAndRotatedPdf(string destinationDirectory, GotenbergSharpClientOptions options)
{
var handler = new HttpClientHandler();
HttpMessageHandler effectiveHandler = handler;

if (!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword))
effectiveHandler = new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler };

using var httpClient = new HttpClient(effectiveHandler, disposeHandler: true)
{
BaseAddress = options.ServiceUrl,
Timeout = options.TimeOut
};

var sharpClient = new GotenbergSharpClient(httpClient);

// Demonstrates watermark, stamp, rotation, and split options
var builder = new HtmlRequestBuilder()
.AddDocument(doc => doc.SetBody(@"
<html><body>
<h1>Cross-Cutting Features Demo</h1>
<p>This PDF has a text watermark and is rotated 90 degrees.</p>
<p>Page 2 content here...</p>
</body></html>"))
// Add a text watermark behind the content
.SetWatermarkOptions(w => w.SetTextWatermark("CONFIDENTIAL"))
// Rotate all pages 90 degrees
.SetRotationOptions(r => r.SetAngle(RotationAngle.Degrees90))
.WithPageProperties(pp => pp.UseChromeDefaults());

var request = builder.Build();
var response = await sharpClient.HtmlToPdfAsync(request);

var resultPath = Path.Combine(destinationDirectory, $"WatermarkRotate-{DateTime.Now:yyyyMMddHHmmss}.pdf");

await using var destinationStream = File.Create(resultPath);
await response.CopyToAsync(destinationStream, CancellationToken.None);

return resultPath;
}
2 changes: 2 additions & 0 deletions examples/WatermarkAndRotate/WatermarkAndRotate.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<Project Sdk="Microsoft.NET.Sdk">
</Project>
57 changes: 57 additions & 0 deletions src/Gotenberg.Sharp.Api.Client/Domain/Builders/BaseBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,63 @@ public TBuilder SetPdfOutputOptions(PdfOutputOptions options)
return (TBuilder)this;
}

/// <summary>
/// Configures rotation options for the resulting PDF.
/// </summary>
public TBuilder SetRotationOptions(Action<RotationOptionsBuilder> action)
{
if (action == null) throw new ArgumentNullException(nameof(action));

this.Request.RotationOptions ??= new RotationOptions();

action(new RotationOptionsBuilder(this.Request.RotationOptions));

return (TBuilder)this;
}

/// <summary>
/// Configures split options for the resulting PDF.
/// When splitting returns multiple files, Gotenberg returns a ZIP.
/// </summary>
public TBuilder SetSplitOptions(Action<SplitOptionsBuilder> action)
{
if (action == null) throw new ArgumentNullException(nameof(action));

this.Request.SplitOptions ??= new SplitOptions();

action(new SplitOptionsBuilder(this.Request.SplitOptions));

return (TBuilder)this;
}

/// <summary>
/// Configures watermark options (background overlay) for the resulting PDF.
/// </summary>
public TBuilder SetWatermarkOptions(Action<WatermarkOptionsBuilder> action)
{
if (action == null) throw new ArgumentNullException(nameof(action));

this.Request.WatermarkOptions ??= new WatermarkOptions();

action(new WatermarkOptionsBuilder(this.Request.WatermarkOptions));

return (TBuilder)this;
}

/// <summary>
/// Configures stamp options (foreground overlay) for the resulting PDF.
/// </summary>
public TBuilder SetStampOptions(Action<StampOptionsBuilder> action)
{
if (action == null) throw new ArgumentNullException(nameof(action));

this.Request.StampOptions ??= new StampOptions();

action(new StampOptionsBuilder(this.Request.StampOptions));

return (TBuilder)this;
}

/// <summary>
/// Builds the request synchronously. Use when all content is already in memory (no async operations).
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2019-2026 Chris Mohan, Jaben Cargman
// and GotenbergSharpApiClient Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Gotenberg.Sharp.API.Client.Domain.ValueObjects;

namespace Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;

public sealed class RotationOptionsBuilder
{
private readonly RotationOptions _options;

internal RotationOptionsBuilder(RotationOptions options)
{
_options = options;
}

public RotationOptionsBuilder SetAngle(RotationAngle angle)
{
_options.RotateAngle = angle ?? throw new ArgumentNullException(nameof(angle));
return this;
}

public RotationOptionsBuilder SetAngle(int degrees)
{
return SetAngle(RotationAngle.Create(degrees));
}

public RotationOptionsBuilder SetPages(PageRanges pages)
{
_options.RotatePages = pages ?? throw new ArgumentNullException(nameof(pages));
return this;
}

public RotationOptionsBuilder SetPages(string pages)
{
return SetPages(PageRanges.Create(pages));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2019-2026 Chris Mohan, Jaben Cargman
// and GotenbergSharpApiClient Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Gotenberg.Sharp.API.Client.Domain.ValueObjects;

namespace Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;

public sealed class SplitOptionsBuilder
{
private readonly SplitOptions _options;

internal SplitOptionsBuilder(SplitOptions options)
{
_options = options;
}

public SplitOptionsBuilder SetMode(SplitMode mode)
{
_options.Mode = mode;
return this;
}

public SplitOptionsBuilder SetSpan(string span)
{
if (string.IsNullOrWhiteSpace(span))
throw new ArgumentException("Split span must not be null or empty.", nameof(span));

_options.Span = span;
return this;
}

public SplitOptionsBuilder SetUnify(bool unify = true)
{
_options.Unify = unify;
return this;
}

/// <summary>
/// Configures interval-based splitting (e.g., split every N pages).
/// </summary>
public SplitOptionsBuilder SplitByIntervals(string span)
{
return SetMode(SplitMode.Intervals).SetSpan(span);
}

/// <summary>
/// Configures page-based splitting (e.g., extract specific page ranges).
/// </summary>
public SplitOptionsBuilder SplitByPages(string span, bool unify = false)
{
return SetMode(SplitMode.Pages).SetSpan(span).SetUnify(unify);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2019-2026 Chris Mohan, Jaben Cargman
// and GotenbergSharpApiClient Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Gotenberg.Sharp.API.Client.Domain.ValueObjects;

using Newtonsoft.Json.Linq;

namespace Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;

public sealed class StampOptionsBuilder
{
private readonly StampOptions _options;

internal StampOptionsBuilder(StampOptions options)
{
_options = options;
}

public StampOptionsBuilder SetSource(OverlaySource source)
{
_options.Source = source;
return this;
}

public StampOptionsBuilder SetExpression(string expression)
{
if (string.IsNullOrWhiteSpace(expression))
throw new ArgumentException("Expression must not be null or empty.", nameof(expression));

_options.Expression = expression;
return this;
}

public StampOptionsBuilder SetPages(PageRanges pages)
{
_options.Pages = pages ?? throw new ArgumentNullException(nameof(pages));
return this;
}

public StampOptionsBuilder SetPages(string pages)
{
return SetPages(PageRanges.Create(pages));
}

public StampOptionsBuilder SetOptions(JObject options)
{
_options.Options = options ?? throw new ArgumentNullException(nameof(options));
return this;
}

/// <summary>
/// Convenience method for a text stamp.
/// </summary>
public StampOptionsBuilder SetTextStamp(string text, string? pages = null)
{
SetSource(OverlaySource.Text);
SetExpression(text);

if (pages != null)
SetPages(pages);

return this;
}
}
Loading
Loading