Skip to content

Commit 24dfe86

Browse files
authored
Merge pull request #74 from ChangemakerStudios/feature/screenshot-routes
Add Chromium screenshot routes (HTML and URL)
2 parents d45780d + 1e192dc commit 24dfe86

18 files changed

Lines changed: 1003 additions & 13 deletions

README.md

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ using Gotenberg.Sharp.API.Client;
154154
using Gotenberg.Sharp.API.Client.Domain.Builders;
155155
using Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
156156
using Gotenberg.Sharp.API.Client.Domain.Requests.Facets; // For Cookie, etc.
157+
using Gotenberg.Sharp.API.Client.Domain.ValueObjects; // For ScreenshotFormat, etc.
157158
```
158159

159160
### HTML To PDF
@@ -529,27 +530,41 @@ public async Task<Stream> FastConversion()
529530
}
530531
```
531532

532-
### Wait For Selector & Emulated Media Features
533-
*Wait for a DOM element and emulate CSS media features like dark mode:*
533+
### Screenshot from HTML
534+
*Capture a screenshot of HTML content as PNG, JPEG, or WebP:*
534535

535536
```csharp
536-
public async Task<Stream> CreateWithChromiumFeatures()
537+
public async Task<Stream> ScreenshotHtml()
537538
{
538-
var builder = new HtmlRequestBuilder()
539-
.AddDocument(doc => doc.SetBody("<html><body><div id='app'>Ready</div></body></html>"))
540-
.SetConversionBehaviors(b => b
541-
.SetWaitForSelector("#app")
542-
.AddEmulatedMediaFeature("prefers-color-scheme", "dark")
543-
.SetFailOnHttpStatusCodes(499, 599)
544-
.FailOnResourceLoadingFailed()
545-
.AddIgnoreResourceHttpStatusDomains("cdn.example.com"))
546-
.WithPageProperties(pp => pp.UseChromeDefaults());
539+
var builder = new ScreenshotHtmlRequestBuilder()
540+
.AddDocument(doc => doc.SetBody("<html><body><h1>Screenshot!</h1></body></html>"))
541+
.WithScreenshotProperties(p => p
542+
.SetSize(1280, 720)
543+
.SetFormat(ScreenshotFormat.Png));
547544

548545
var request = builder.Build();
549-
return await _sharpClient.HtmlToPdfAsync(request);
546+
return await _sharpClient.ScreenshotHtmlAsync(request);
550547
}
551548
```
552549

550+
### Screenshot from URL
551+
*Capture a screenshot of any URL:*
552+
553+
```csharp
554+
public async Task<Stream> ScreenshotUrl()
555+
{
556+
var builder = new ScreenshotUrlRequestBuilder()
557+
.SetUrl("https://example.com")
558+
.WithScreenshotProperties(p => p
559+
.SetSize(1024, 768)
560+
.SetFormat(ScreenshotFormat.Jpeg)
561+
.SetQuality(90)
562+
.SetClip());
563+
564+
var request = builder.Build();
565+
return await _sharpClient.ScreenshotUrlAsync(request);
566+
}
567+
```
553568

554569
### Custom Page Properties
555570
*Fine-tune page dimensions and properties:*

examples/Screenshot/Program.cs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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 sharpClient = CreateClient(options);
21+
22+
// Screenshot from HTML
23+
var htmlPath = await ScreenshotFromHtml(destinationDirectory, sharpClient);
24+
Console.WriteLine($"HTML screenshot: {htmlPath}");
25+
26+
// Screenshot from URL
27+
var urlPath = await ScreenshotFromUrl(destinationDirectory, sharpClient);
28+
Console.WriteLine($"URL screenshot: {urlPath}");
29+
30+
static async Task<string> ScreenshotFromHtml(string destinationDirectory, GotenbergSharpClient sharpClient)
31+
{
32+
var builder = new ScreenshotHtmlRequestBuilder()
33+
.AddDocument(doc => doc.SetBody(@"
34+
<html>
35+
<body style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px;'>
36+
<h1 style='color: white; font-family: sans-serif;'>Screenshot Demo</h1>
37+
<p style='color: white;'>Captured with Gotenberg + GotenbergSharpApiClient</p>
38+
</body>
39+
</html>"))
40+
.WithScreenshotProperties(p => p
41+
.SetSize(1280, 720)
42+
.SetFormat(ScreenshotFormat.Png));
43+
44+
await using var response = await sharpClient.ScreenshotHtmlAsync(builder);
45+
46+
var resultPath = Path.Combine(destinationDirectory, $"ScreenshotHtml-{DateTime.Now:yyyyMMddHHmmss}.png");
47+
await using var file = File.Create(resultPath);
48+
await response.CopyToAsync(file);
49+
return resultPath;
50+
}
51+
52+
static async Task<string> ScreenshotFromUrl(string destinationDirectory, GotenbergSharpClient sharpClient)
53+
{
54+
var builder = new ScreenshotUrlRequestBuilder()
55+
.SetUrl("https://example.com")
56+
.WithScreenshotProperties(p => p
57+
.SetSize(1024, 768)
58+
.SetFormat(ScreenshotFormat.Jpeg)
59+
.SetQuality(90)
60+
.SetClip());
61+
62+
await using var response = await sharpClient.ScreenshotUrlAsync(builder);
63+
64+
var resultPath = Path.Combine(destinationDirectory, $"ScreenshotUrl-{DateTime.Now:yyyyMMddHHmmss}.jpg");
65+
await using var file = File.Create(resultPath);
66+
await response.CopyToAsync(file);
67+
return resultPath;
68+
}
69+
70+
static GotenbergSharpClient CreateClient(GotenbergSharpClientOptions options)
71+
{
72+
var handler = new HttpClientHandler();
73+
HttpMessageHandler effectiveHandler = handler;
74+
75+
if (!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword))
76+
effectiveHandler = new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler };
77+
78+
var httpClient = new HttpClient(effectiveHandler)
79+
{
80+
BaseAddress = options.ServiceUrl,
81+
Timeout = options.TimeOut
82+
};
83+
84+
return new GotenbergSharpClient(httpClient);
85+
}
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>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
namespace Gotenberg.Sharp.API.Client.Domain.Builders;
17+
18+
/// <summary>
19+
/// Base builder for all Chromium screenshot requests. Provides screenshot properties,
20+
/// conversion behaviors, and asset management shared across URL and HTML screenshot builders.
21+
/// </summary>
22+
public abstract class BaseScreenshotBuilder<TRequest, TBuilder>(TRequest request)
23+
: BaseBuilder<TRequest, TBuilder>(request)
24+
where TRequest : ScreenshotRequest
25+
where TBuilder : BaseScreenshotBuilder<TRequest, TBuilder>
26+
{
27+
/// <summary>
28+
/// Configures screenshot-specific properties (device dimensions, format, quality).
29+
/// </summary>
30+
public TBuilder WithScreenshotProperties(Action<ScreenshotPropertyBuilder> action)
31+
{
32+
if (action == null) throw new ArgumentNullException(nameof(action));
33+
34+
action(new ScreenshotPropertyBuilder(this.Request.ScreenshotProperties));
35+
36+
return (TBuilder)this;
37+
}
38+
39+
/// <summary>
40+
/// Configures Chromium rendering behaviors (wait conditions, cookies, headers, error handling).
41+
/// </summary>
42+
public TBuilder SetConversionBehaviors(Action<HtmlConversionBehaviorBuilder> action)
43+
{
44+
if (action == null) throw new ArgumentNullException(nameof(action));
45+
46+
action(new HtmlConversionBehaviorBuilder(this.Request.ConversionBehaviors));
47+
48+
return (TBuilder)this;
49+
}
50+
51+
/// <summary>
52+
/// Sets pre-configured conversion behaviors.
53+
/// </summary>
54+
public TBuilder SetConversionBehaviors(HtmlConversionBehaviors behaviors)
55+
{
56+
this.Request.ConversionBehaviors = behaviors ?? throw new ArgumentNullException(nameof(behaviors));
57+
58+
return (TBuilder)this;
59+
}
60+
61+
/// <summary>
62+
/// Adds embedded assets (images, fonts, CSS, JS) referenced by the HTML content.
63+
/// </summary>
64+
public TBuilder WithAssets(Action<AssetBuilder> action)
65+
{
66+
if (action == null) throw new ArgumentNullException(nameof(action));
67+
68+
this.Request.Assets ??= new AssetDictionary();
69+
70+
action(new AssetBuilder(this.Request.Assets));
71+
72+
return (TBuilder)this;
73+
}
74+
75+
/// <summary>
76+
/// Adds embedded assets asynchronously (e.g., from streams or files).
77+
/// </summary>
78+
public TBuilder WithAsyncAssets(Func<AssetBuilder, Task> asyncAction)
79+
{
80+
if (asyncAction == null) throw new ArgumentNullException(nameof(asyncAction));
81+
82+
this.Request.Assets ??= new AssetDictionary();
83+
84+
this.BuildTasks.Add(asyncAction(new AssetBuilder(this.Request.Assets)));
85+
86+
return (TBuilder)this;
87+
}
88+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
/// <summary>
21+
/// Configures screenshot-specific properties (device dimensions, format, quality).
22+
/// </summary>
23+
public sealed class ScreenshotPropertyBuilder
24+
{
25+
private readonly ScreenshotProperties _properties;
26+
27+
internal ScreenshotPropertyBuilder(ScreenshotProperties properties)
28+
{
29+
_properties = properties;
30+
}
31+
32+
public ScreenshotPropertyBuilder SetWidth(ScreenDimension width)
33+
{
34+
_properties.Width = width ?? throw new ArgumentNullException(nameof(width));
35+
return this;
36+
}
37+
38+
public ScreenshotPropertyBuilder SetWidth(int pixels)
39+
{
40+
return SetWidth(ScreenDimension.Create(pixels));
41+
}
42+
43+
public ScreenshotPropertyBuilder SetHeight(ScreenDimension height)
44+
{
45+
_properties.Height = height ?? throw new ArgumentNullException(nameof(height));
46+
return this;
47+
}
48+
49+
public ScreenshotPropertyBuilder SetHeight(int pixels)
50+
{
51+
return SetHeight(ScreenDimension.Create(pixels));
52+
}
53+
54+
public ScreenshotPropertyBuilder SetSize(int width, int height)
55+
{
56+
return SetWidth(width).SetHeight(height);
57+
}
58+
59+
public ScreenshotPropertyBuilder SetClip(bool clip = true)
60+
{
61+
_properties.Clip = clip;
62+
return this;
63+
}
64+
65+
public ScreenshotPropertyBuilder SetFormat(ScreenshotFormat format)
66+
{
67+
_properties.Format = format;
68+
return this;
69+
}
70+
71+
public ScreenshotPropertyBuilder SetQuality(CompressionQuality quality)
72+
{
73+
_properties.Quality = quality ?? throw new ArgumentNullException(nameof(quality));
74+
return this;
75+
}
76+
77+
public ScreenshotPropertyBuilder SetQuality(int quality)
78+
{
79+
return SetQuality(CompressionQuality.Create(quality));
80+
}
81+
82+
public ScreenshotPropertyBuilder SetOmitBackground(bool omit = true)
83+
{
84+
_properties.OmitBackground = omit;
85+
return this;
86+
}
87+
88+
public ScreenshotPropertyBuilder SetOptimizeForSpeed(bool optimize = true)
89+
{
90+
_properties.OptimizeForSpeed = optimize;
91+
return this;
92+
}
93+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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+
namespace Gotenberg.Sharp.API.Client.Domain.Builders;
17+
18+
/// <summary>
19+
/// Builds requests for capturing screenshots of HTML content using Chromium.
20+
/// </summary>
21+
public sealed class ScreenshotHtmlRequestBuilder()
22+
: BaseScreenshotBuilder<ScreenshotHtmlRequest, ScreenshotHtmlRequestBuilder>(new ScreenshotHtmlRequest())
23+
{
24+
/// <summary>
25+
/// Configures the HTML document content for the screenshot.
26+
/// </summary>
27+
public ScreenshotHtmlRequestBuilder AddDocument(Action<DocumentBuilder> action)
28+
{
29+
if (action == null) throw new ArgumentNullException(nameof(action));
30+
31+
this.Request.Content ??= new FullDocument();
32+
33+
action(new DocumentBuilder(this.Request.Content, _ => { }));
34+
35+
return this;
36+
}
37+
38+
/// <summary>
39+
/// Configures the HTML document content asynchronously.
40+
/// </summary>
41+
public ScreenshotHtmlRequestBuilder AddAsyncDocument(Func<DocumentBuilder, Task> asyncAction)
42+
{
43+
if (asyncAction == null) throw new ArgumentNullException(nameof(asyncAction));
44+
45+
this.Request.Content ??= new FullDocument();
46+
47+
this.BuildTasks.Add(asyncAction(new DocumentBuilder(this.Request.Content, _ => { })));
48+
49+
return this;
50+
}
51+
}

0 commit comments

Comments
 (0)