Skip to content

Commit 3f225a9

Browse files
authored
Merge pull request #73 from ChangemakerStudios/feature/cross-cutting-options
Add cross-cutting Watermark, Stamp, Rotation, and Split options
2 parents 4f9b981 + d058cb2 commit 3f225a9

17 files changed

Lines changed: 939 additions & 21 deletions

File tree

README.md

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -529,32 +529,36 @@ public async Task<Stream> FastConversion()
529529
}
530530
```
531531

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

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

540-
// Rotate pages 90 degrees
541-
var rotateResult = await _sharpClient.ExecutePdfEngineAsync(
542-
PdfEngineBuilders.Rotate(90, "1-3").WithPdfs(a => a.AddItem("doc.pdf", pdfBytes)));
544+
var request = builder.Build();
545+
return await _sharpClient.HtmlToPdfAsync(request);
546+
}
547+
```
543548

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

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

552-
// Write metadata
553-
var result = await _sharpClient.ExecutePdfEngineAsync(
554-
PdfEngineBuilders.WriteMetadata(new Dictionary<string, object>
555-
{
556-
{ "Author", "John Doe" }, { "Title", "My Document" }
557-
}).WithPdfs(a => a.AddItem("doc.pdf", pdfBytes)));
559+
var request = builder.Build();
560+
return await _sharpClient.HtmlToPdfAsync(request);
561+
}
558562
```
559563

560564
### Custom Page Properties
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using Gotenberg.Sharp.API.Client;
2+
using Gotenberg.Sharp.API.Client.Domain.Builders;
3+
using Gotenberg.Sharp.API.Client.Domain.ValueObjects;
4+
using Gotenberg.Sharp.API.Client.Domain.Settings;
5+
using Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
6+
7+
using Microsoft.Extensions.Configuration;
8+
9+
var config = new ConfigurationBuilder()
10+
.SetBasePath(AppContext.BaseDirectory)
11+
.AddJsonFile("appsettings.json")
12+
.Build();
13+
14+
var options = new GotenbergSharpClientOptions();
15+
config.GetSection(nameof(GotenbergSharpClient)).Bind(options);
16+
17+
var destinationDirectory = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "output");
18+
Directory.CreateDirectory(destinationDirectory);
19+
20+
var path = await CreateWatermarkedAndRotatedPdf(destinationDirectory, options);
21+
Console.WriteLine($"Watermarked & rotated PDF created: {path}");
22+
23+
static async Task<string> CreateWatermarkedAndRotatedPdf(string destinationDirectory, GotenbergSharpClientOptions options)
24+
{
25+
var handler = new HttpClientHandler();
26+
HttpMessageHandler effectiveHandler = handler;
27+
28+
if (!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword))
29+
effectiveHandler = new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler };
30+
31+
using var httpClient = new HttpClient(effectiveHandler, disposeHandler: true)
32+
{
33+
BaseAddress = options.ServiceUrl,
34+
Timeout = options.TimeOut
35+
};
36+
37+
var sharpClient = new GotenbergSharpClient(httpClient);
38+
39+
// Demonstrates watermark, stamp, rotation, and split options
40+
var builder = new HtmlRequestBuilder()
41+
.AddDocument(doc => doc.SetBody(@"
42+
<html><body>
43+
<h1>Cross-Cutting Features Demo</h1>
44+
<p>This PDF has a text watermark and is rotated 90 degrees.</p>
45+
<p>Page 2 content here...</p>
46+
</body></html>"))
47+
// Add a text watermark behind the content
48+
.SetWatermarkOptions(w => w.SetTextWatermark("CONFIDENTIAL"))
49+
// Rotate all pages 90 degrees
50+
.SetRotationOptions(r => r.SetAngle(RotationAngle.Degrees90))
51+
.WithPageProperties(pp => pp.UseChromeDefaults());
52+
53+
var request = builder.Build();
54+
var response = await sharpClient.HtmlToPdfAsync(request);
55+
56+
var resultPath = Path.Combine(destinationDirectory, $"WatermarkRotate-{DateTime.Now:yyyyMMddHHmmss}.pdf");
57+
58+
await using var destinationStream = File.Create(resultPath);
59+
await response.CopyToAsync(destinationStream, CancellationToken.None);
60+
61+
return resultPath;
62+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
</Project>

src/Gotenberg.Sharp.Api.Client/Domain/Builders/BaseBuilder.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,63 @@ public TBuilder SetPdfOutputOptions(PdfOutputOptions options)
8888
return (TBuilder)this;
8989
}
9090

91+
/// <summary>
92+
/// Configures rotation options for the resulting PDF.
93+
/// </summary>
94+
public TBuilder SetRotationOptions(Action<RotationOptionsBuilder> action)
95+
{
96+
if (action == null) throw new ArgumentNullException(nameof(action));
97+
98+
this.Request.RotationOptions ??= new RotationOptions();
99+
100+
action(new RotationOptionsBuilder(this.Request.RotationOptions));
101+
102+
return (TBuilder)this;
103+
}
104+
105+
/// <summary>
106+
/// Configures split options for the resulting PDF.
107+
/// When splitting returns multiple files, Gotenberg returns a ZIP.
108+
/// </summary>
109+
public TBuilder SetSplitOptions(Action<SplitOptionsBuilder> action)
110+
{
111+
if (action == null) throw new ArgumentNullException(nameof(action));
112+
113+
this.Request.SplitOptions ??= new SplitOptions();
114+
115+
action(new SplitOptionsBuilder(this.Request.SplitOptions));
116+
117+
return (TBuilder)this;
118+
}
119+
120+
/// <summary>
121+
/// Configures watermark options (background overlay) for the resulting PDF.
122+
/// </summary>
123+
public TBuilder SetWatermarkOptions(Action<WatermarkOptionsBuilder> action)
124+
{
125+
if (action == null) throw new ArgumentNullException(nameof(action));
126+
127+
this.Request.WatermarkOptions ??= new WatermarkOptions();
128+
129+
action(new WatermarkOptionsBuilder(this.Request.WatermarkOptions));
130+
131+
return (TBuilder)this;
132+
}
133+
134+
/// <summary>
135+
/// Configures stamp options (foreground overlay) for the resulting PDF.
136+
/// </summary>
137+
public TBuilder SetStampOptions(Action<StampOptionsBuilder> action)
138+
{
139+
if (action == null) throw new ArgumentNullException(nameof(action));
140+
141+
this.Request.StampOptions ??= new StampOptions();
142+
143+
action(new StampOptionsBuilder(this.Request.StampOptions));
144+
145+
return (TBuilder)this;
146+
}
147+
91148
/// <summary>
92149
/// Builds the request synchronously. Use when all content is already in memory (no async operations).
93150
/// </summary>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2019-2026 Chris Mohan, Jaben Cargman
2+
// and GotenbergSharpApiClient Contributors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
using Gotenberg.Sharp.API.Client.Domain.ValueObjects;
17+
18+
namespace Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
19+
20+
public sealed class RotationOptionsBuilder
21+
{
22+
private readonly RotationOptions _options;
23+
24+
internal RotationOptionsBuilder(RotationOptions options)
25+
{
26+
_options = options;
27+
}
28+
29+
public RotationOptionsBuilder SetAngle(RotationAngle angle)
30+
{
31+
_options.RotateAngle = angle ?? throw new ArgumentNullException(nameof(angle));
32+
return this;
33+
}
34+
35+
public RotationOptionsBuilder SetAngle(int degrees)
36+
{
37+
return SetAngle(RotationAngle.Create(degrees));
38+
}
39+
40+
public RotationOptionsBuilder SetPages(PageRanges pages)
41+
{
42+
_options.RotatePages = pages ?? throw new ArgumentNullException(nameof(pages));
43+
return this;
44+
}
45+
46+
public RotationOptionsBuilder SetPages(string pages)
47+
{
48+
return SetPages(PageRanges.Create(pages));
49+
}
50+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2019-2026 Chris Mohan, Jaben Cargman
2+
// and GotenbergSharpApiClient Contributors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
using Gotenberg.Sharp.API.Client.Domain.ValueObjects;
17+
18+
namespace Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
19+
20+
public sealed class SplitOptionsBuilder
21+
{
22+
private readonly SplitOptions _options;
23+
24+
internal SplitOptionsBuilder(SplitOptions options)
25+
{
26+
_options = options;
27+
}
28+
29+
public SplitOptionsBuilder SetMode(SplitMode mode)
30+
{
31+
_options.Mode = mode;
32+
return this;
33+
}
34+
35+
public SplitOptionsBuilder SetSpan(string span)
36+
{
37+
if (string.IsNullOrWhiteSpace(span))
38+
throw new ArgumentException("Split span must not be null or empty.", nameof(span));
39+
40+
_options.Span = span;
41+
return this;
42+
}
43+
44+
public SplitOptionsBuilder SetUnify(bool unify = true)
45+
{
46+
_options.Unify = unify;
47+
return this;
48+
}
49+
50+
/// <summary>
51+
/// Configures interval-based splitting (e.g., split every N pages).
52+
/// </summary>
53+
public SplitOptionsBuilder SplitByIntervals(string span)
54+
{
55+
return SetMode(SplitMode.Intervals).SetSpan(span);
56+
}
57+
58+
/// <summary>
59+
/// Configures page-based splitting (e.g., extract specific page ranges).
60+
/// </summary>
61+
public SplitOptionsBuilder SplitByPages(string span, bool unify = false)
62+
{
63+
return SetMode(SplitMode.Pages).SetSpan(span).SetUnify(unify);
64+
}
65+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright 2019-2026 Chris Mohan, Jaben Cargman
2+
// and GotenbergSharpApiClient Contributors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
using Gotenberg.Sharp.API.Client.Domain.ValueObjects;
17+
18+
using Newtonsoft.Json.Linq;
19+
20+
namespace Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
21+
22+
public sealed class StampOptionsBuilder
23+
{
24+
private readonly StampOptions _options;
25+
26+
internal StampOptionsBuilder(StampOptions options)
27+
{
28+
_options = options;
29+
}
30+
31+
public StampOptionsBuilder SetSource(OverlaySource source)
32+
{
33+
_options.Source = source;
34+
return this;
35+
}
36+
37+
public StampOptionsBuilder SetExpression(string expression)
38+
{
39+
if (string.IsNullOrWhiteSpace(expression))
40+
throw new ArgumentException("Expression must not be null or empty.", nameof(expression));
41+
42+
_options.Expression = expression;
43+
return this;
44+
}
45+
46+
public StampOptionsBuilder SetPages(PageRanges pages)
47+
{
48+
_options.Pages = pages ?? throw new ArgumentNullException(nameof(pages));
49+
return this;
50+
}
51+
52+
public StampOptionsBuilder SetPages(string pages)
53+
{
54+
return SetPages(PageRanges.Create(pages));
55+
}
56+
57+
public StampOptionsBuilder SetOptions(JObject options)
58+
{
59+
_options.Options = options ?? throw new ArgumentNullException(nameof(options));
60+
return this;
61+
}
62+
63+
/// <summary>
64+
/// Convenience method for a text stamp.
65+
/// </summary>
66+
public StampOptionsBuilder SetTextStamp(string text, string? pages = null)
67+
{
68+
SetSource(OverlaySource.Text);
69+
SetExpression(text);
70+
71+
if (pages != null)
72+
SetPages(pages);
73+
74+
return this;
75+
}
76+
}

0 commit comments

Comments
 (0)