From 0abe5e50c8b328c2a8e88fd60fb8352e399b3e1f Mon Sep 17 00:00:00 2001 From: Jaben Cargman Date: Sat, 28 Mar 2026 12:19:36 -0400 Subject: [PATCH 1/3] Add Chromium screenshot routes with full request/builder hierarchy Introduce screenshot support for HTML and URL content with new request hierarchy: ScreenshotRequest (base) -> ScreenshotHtmlRequest, ScreenshotUrlRequest. Parallel builder hierarchy with BaseScreenshotBuilder, ScreenshotHtmlRequestBuilder, ScreenshotUrlRequestBuilder. Add DDD value objects: ScreenshotFormat (png/jpeg/webp), CompressionQuality (0-100), ScreenDimension (positive pixels). Add ScreenshotProperties facet and ScreenshotPropertyBuilder. Add ScreenshotHtmlAsync and ScreenshotUrlAsync methods to GotenbergSharpClient. Include Screenshot example project and README sections. --- README.md | 36 ++++ examples/Screenshot/Program.cs | 87 ++++++++ examples/Screenshot/Screenshot.csproj | 2 + .../Domain/Builders/BaseScreenshotBuilder.cs | 88 ++++++++ .../Faceted/ScreenshotPropertyBuilder.cs | 93 ++++++++ .../Builders/ScreenshotHtmlRequestBuilder.cs | 51 +++++ .../Builders/ScreenshotUrlRequestBuilder.cs | 40 ++++ .../Domain/Requests/Facets/FacetBase.cs | 3 + .../Requests/Facets/ScreenshotProperties.cs | 46 ++++ .../Domain/Requests/ScreenshotHtmlRequest.cs | 49 +++++ .../Domain/Requests/ScreenshotRequest.cs | 35 +++ .../Domain/Requests/ScreenshotUrlRequest.cs | 53 +++++ .../Domain/ValueObjects/CompressionQuality.cs | 60 ++++++ .../Domain/ValueObjects/ScreenDimension.cs | 57 +++++ .../Domain/ValueObjects/ScreenshotFormat.cs | 37 ++++ .../GotenbergSharpClient.cs | 60 ++++++ .../Infrastructure/Constants.cs | 17 ++ .../ScreenshotTests.cs | 200 ++++++++++++++++++ 18 files changed, 1014 insertions(+) create mode 100644 examples/Screenshot/Program.cs create mode 100644 examples/Screenshot/Screenshot.csproj create mode 100644 src/Gotenberg.Sharp.Api.Client/Domain/Builders/BaseScreenshotBuilder.cs create mode 100644 src/Gotenberg.Sharp.Api.Client/Domain/Builders/Faceted/ScreenshotPropertyBuilder.cs create mode 100644 src/Gotenberg.Sharp.Api.Client/Domain/Builders/ScreenshotHtmlRequestBuilder.cs create mode 100644 src/Gotenberg.Sharp.Api.Client/Domain/Builders/ScreenshotUrlRequestBuilder.cs create mode 100644 src/Gotenberg.Sharp.Api.Client/Domain/Requests/Facets/ScreenshotProperties.cs create mode 100644 src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotHtmlRequest.cs create mode 100644 src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotRequest.cs create mode 100644 src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotUrlRequest.cs create mode 100644 src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/CompressionQuality.cs create mode 100644 src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/ScreenDimension.cs create mode 100644 src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/ScreenshotFormat.cs create mode 100644 test/GotenbergSharpClient.Tests/ScreenshotTests.cs diff --git a/README.md b/README.md index 1e4e5c6..22a7b41 100644 --- a/README.md +++ b/README.md @@ -492,6 +492,42 @@ public async Task FastConversion() } ``` +### Screenshot from HTML +*Capture a screenshot of HTML content as PNG, JPEG, or WebP:* + +```csharp +public async Task ScreenshotHtml() +{ + var builder = new ScreenshotHtmlRequestBuilder() + .AddDocument(doc => doc.SetBody("

Screenshot!

")) + .WithScreenshotProperties(p => p + .SetSize(1280, 720) + .SetFormat(ScreenshotFormat.Png)); + + var request = builder.Build(); + return await _sharpClient.ScreenshotHtmlAsync(request); +} +``` + +### Screenshot from URL +*Capture a screenshot of any URL:* + +```csharp +public async Task ScreenshotUrl() +{ + var builder = new ScreenshotUrlRequestBuilder() + .SetUrl("https://example.com") + .WithScreenshotProperties(p => p + .SetSize(1024, 768) + .SetFormat(ScreenshotFormat.Jpeg) + .SetQuality(90) + .SetClip()); + + var request = builder.Build(); + return await _sharpClient.ScreenshotUrlAsync(request); +} +``` + ### Custom Page Properties *Fine-tune page dimensions and properties:* diff --git a/examples/Screenshot/Program.cs b/examples/Screenshot/Program.cs new file mode 100644 index 0000000..6ea3138 --- /dev/null +++ b/examples/Screenshot/Program.cs @@ -0,0 +1,87 @@ +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); + +// Screenshot from HTML +var htmlPath = await ScreenshotFromHtml(destinationDirectory, options); +Console.WriteLine($"HTML screenshot: {htmlPath}"); + +// Screenshot from URL +var urlPath = await ScreenshotFromUrl(destinationDirectory, options); +Console.WriteLine($"URL screenshot: {urlPath}"); + +static async Task ScreenshotFromHtml(string destinationDirectory, GotenbergSharpClientOptions options) +{ + var sharpClient = CreateClient(options); + + var builder = new ScreenshotHtmlRequestBuilder() + .AddDocument(doc => doc.SetBody(@" + + +

Screenshot Demo

+

Captured with Gotenberg + GotenbergSharpApiClient

+ + ")) + .WithScreenshotProperties(p => p + .SetSize(1280, 720) + .SetFormat(ScreenshotFormat.Png)); + + var response = await sharpClient.ScreenshotHtmlAsync(builder); + + var resultPath = Path.Combine(destinationDirectory, $"ScreenshotHtml-{DateTime.Now:yyyyMMddHHmmss}.png"); + await using var file = File.Create(resultPath); + await response.CopyToAsync(file); + return resultPath; +} + +static async Task ScreenshotFromUrl(string destinationDirectory, GotenbergSharpClientOptions options) +{ + var sharpClient = CreateClient(options); + + var builder = new ScreenshotUrlRequestBuilder() + .SetUrl("https://example.com") + .WithScreenshotProperties(p => p + .SetSize(1024, 768) + .SetFormat(ScreenshotFormat.Jpeg) + .SetQuality(90) + .SetClip()); + + var response = await sharpClient.ScreenshotUrlAsync(builder); + + var resultPath = Path.Combine(destinationDirectory, $"ScreenshotUrl-{DateTime.Now:yyyyMMddHHmmss}.jpg"); + await using var file = File.Create(resultPath); + await response.CopyToAsync(file); + return resultPath; +} + +static GotenbergSharpClient CreateClient(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 }; + + var httpClient = new HttpClient(effectiveHandler) + { + BaseAddress = options.ServiceUrl, + Timeout = options.TimeOut + }; + + return new GotenbergSharpClient(httpClient); +} diff --git a/examples/Screenshot/Screenshot.csproj b/examples/Screenshot/Screenshot.csproj new file mode 100644 index 0000000..35e3d84 --- /dev/null +++ b/examples/Screenshot/Screenshot.csproj @@ -0,0 +1,2 @@ + + diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/Builders/BaseScreenshotBuilder.cs b/src/Gotenberg.Sharp.Api.Client/Domain/Builders/BaseScreenshotBuilder.cs new file mode 100644 index 0000000..f7cd4e4 --- /dev/null +++ b/src/Gotenberg.Sharp.Api.Client/Domain/Builders/BaseScreenshotBuilder.cs @@ -0,0 +1,88 @@ +// 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. + +namespace Gotenberg.Sharp.API.Client.Domain.Builders; + +/// +/// Base builder for all Chromium screenshot requests. Provides screenshot properties, +/// conversion behaviors, and asset management shared across URL and HTML screenshot builders. +/// +public abstract class BaseScreenshotBuilder(TRequest request) + : BaseBuilder(request) + where TRequest : ScreenshotRequest + where TBuilder : BaseScreenshotBuilder +{ + /// + /// Configures screenshot-specific properties (device dimensions, format, quality). + /// + public TBuilder WithScreenshotProperties(Action action) + { + if (action == null) throw new ArgumentNullException(nameof(action)); + + action(new ScreenshotPropertyBuilder(this.Request.ScreenshotProperties)); + + return (TBuilder)this; + } + + /// + /// Configures Chromium rendering behaviors (wait conditions, cookies, headers, error handling). + /// + public TBuilder SetConversionBehaviors(Action action) + { + if (action == null) throw new ArgumentNullException(nameof(action)); + + action(new HtmlConversionBehaviorBuilder(this.Request.ConversionBehaviors)); + + return (TBuilder)this; + } + + /// + /// Sets pre-configured conversion behaviors. + /// + public TBuilder SetConversionBehaviors(HtmlConversionBehaviors behaviors) + { + this.Request.ConversionBehaviors = behaviors ?? throw new ArgumentNullException(nameof(behaviors)); + + return (TBuilder)this; + } + + /// + /// Adds embedded assets (images, fonts, CSS, JS) referenced by the HTML content. + /// + public TBuilder WithAssets(Action action) + { + if (action == null) throw new ArgumentNullException(nameof(action)); + + this.Request.Assets ??= new AssetDictionary(); + + action(new AssetBuilder(this.Request.Assets)); + + return (TBuilder)this; + } + + /// + /// Adds embedded assets asynchronously (e.g., from streams or files). + /// + public TBuilder WithAsyncAssets(Func asyncAction) + { + if (asyncAction == null) throw new ArgumentNullException(nameof(asyncAction)); + + this.Request.Assets ??= new AssetDictionary(); + + this.BuildTasks.Add(asyncAction(new AssetBuilder(this.Request.Assets))); + + return (TBuilder)this; + } +} diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/Builders/Faceted/ScreenshotPropertyBuilder.cs b/src/Gotenberg.Sharp.Api.Client/Domain/Builders/Faceted/ScreenshotPropertyBuilder.cs new file mode 100644 index 0000000..0ffabbe --- /dev/null +++ b/src/Gotenberg.Sharp.Api.Client/Domain/Builders/Faceted/ScreenshotPropertyBuilder.cs @@ -0,0 +1,93 @@ +// 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; + +/// +/// Configures screenshot-specific properties (device dimensions, format, quality). +/// +public sealed class ScreenshotPropertyBuilder +{ + private readonly ScreenshotProperties _properties; + + internal ScreenshotPropertyBuilder(ScreenshotProperties properties) + { + _properties = properties; + } + + public ScreenshotPropertyBuilder SetWidth(ScreenDimension width) + { + _properties.Width = width ?? throw new ArgumentNullException(nameof(width)); + return this; + } + + public ScreenshotPropertyBuilder SetWidth(int pixels) + { + return SetWidth(ScreenDimension.Create(pixels)); + } + + public ScreenshotPropertyBuilder SetHeight(ScreenDimension height) + { + _properties.Height = height ?? throw new ArgumentNullException(nameof(height)); + return this; + } + + public ScreenshotPropertyBuilder SetHeight(int pixels) + { + return SetHeight(ScreenDimension.Create(pixels)); + } + + public ScreenshotPropertyBuilder SetSize(int width, int height) + { + return SetWidth(width).SetHeight(height); + } + + public ScreenshotPropertyBuilder SetClip(bool clip = true) + { + _properties.Clip = clip; + return this; + } + + public ScreenshotPropertyBuilder SetFormat(ScreenshotFormat format) + { + _properties.Format = format; + return this; + } + + public ScreenshotPropertyBuilder SetQuality(CompressionQuality quality) + { + _properties.Quality = quality ?? throw new ArgumentNullException(nameof(quality)); + return this; + } + + public ScreenshotPropertyBuilder SetQuality(int quality) + { + return SetQuality(CompressionQuality.Create(quality)); + } + + public ScreenshotPropertyBuilder SetOmitBackground(bool omit = true) + { + _properties.OmitBackground = omit; + return this; + } + + public ScreenshotPropertyBuilder SetOptimizeForSpeed(bool optimize = true) + { + _properties.OptimizeForSpeed = optimize; + return this; + } +} diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/Builders/ScreenshotHtmlRequestBuilder.cs b/src/Gotenberg.Sharp.Api.Client/Domain/Builders/ScreenshotHtmlRequestBuilder.cs new file mode 100644 index 0000000..041e5c2 --- /dev/null +++ b/src/Gotenberg.Sharp.Api.Client/Domain/Builders/ScreenshotHtmlRequestBuilder.cs @@ -0,0 +1,51 @@ +// 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. + +namespace Gotenberg.Sharp.API.Client.Domain.Builders; + +/// +/// Builds requests for capturing screenshots of HTML content using Chromium. +/// +public sealed class ScreenshotHtmlRequestBuilder() + : BaseScreenshotBuilder(new ScreenshotHtmlRequest()) +{ + /// + /// Configures the HTML document content for the screenshot. + /// + public ScreenshotHtmlRequestBuilder AddDocument(Action action) + { + if (action == null) throw new ArgumentNullException(nameof(action)); + + this.Request.Content ??= new FullDocument(); + + action(new DocumentBuilder(this.Request.Content, _ => { })); + + return this; + } + + /// + /// Configures the HTML document content asynchronously. + /// + public ScreenshotHtmlRequestBuilder AddAsyncDocument(Func asyncAction) + { + if (asyncAction == null) throw new ArgumentNullException(nameof(asyncAction)); + + this.Request.Content ??= new FullDocument(); + + this.BuildTasks.Add(asyncAction(new DocumentBuilder(this.Request.Content, _ => { }))); + + return this; + } +} diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/Builders/ScreenshotUrlRequestBuilder.cs b/src/Gotenberg.Sharp.Api.Client/Domain/Builders/ScreenshotUrlRequestBuilder.cs new file mode 100644 index 0000000..742e5d9 --- /dev/null +++ b/src/Gotenberg.Sharp.Api.Client/Domain/Builders/ScreenshotUrlRequestBuilder.cs @@ -0,0 +1,40 @@ +// 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. + +namespace Gotenberg.Sharp.API.Client.Domain.Builders; + +/// +/// Builds requests for capturing screenshots of URLs using Chromium. +/// +public sealed class ScreenshotUrlRequestBuilder() + : BaseScreenshotBuilder(new ScreenshotUrlRequest()) +{ + /// + /// Sets the URL to screenshot. + /// + public ScreenshotUrlRequestBuilder SetUrl(string url) + { + return SetUrl(new Uri(url)); + } + + /// + /// Sets the URL to screenshot. + /// + public ScreenshotUrlRequestBuilder SetUrl(Uri url) + { + this.Request.Url = url ?? throw new ArgumentNullException(nameof(url)); + return this; + } +} diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/Requests/Facets/FacetBase.cs b/src/Gotenberg.Sharp.Api.Client/Domain/Requests/Facets/FacetBase.cs index fa7ba59..9f8be4b 100644 --- a/src/Gotenberg.Sharp.Api.Client/Domain/Requests/Facets/FacetBase.cs +++ b/src/Gotenberg.Sharp.Api.Client/Domain/Requests/Facets/FacetBase.cs @@ -15,6 +15,8 @@ using System.Globalization; +using Gotenberg.Sharp.API.Client.Domain.ValueObjects; + namespace Gotenberg.Sharp.API.Client.Domain.Requests.Facets; public abstract class FacetBase : IConvertToHttpContent @@ -78,6 +80,7 @@ public virtual IEnumerable ToHttpContent() LibrePdfFormats format => format.ToFormDataValue(), ConversionPdfFormats format => format.ToFormDataValue(), List cookies => JsonConvert.SerializeObject(cookies), + ScreenshotFormat screenshotFormat => screenshotFormat.ToFormValue(), float f => f.ToString(cultureInfo), double d => d.ToString(cultureInfo), decimal c => c.ToString(cultureInfo), diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/Requests/Facets/ScreenshotProperties.cs b/src/Gotenberg.Sharp.Api.Client/Domain/Requests/Facets/ScreenshotProperties.cs new file mode 100644 index 0000000..fb7965d --- /dev/null +++ b/src/Gotenberg.Sharp.Api.Client/Domain/Requests/Facets/ScreenshotProperties.cs @@ -0,0 +1,46 @@ +// 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.Requests.Facets; + +/// +/// Screenshot-specific properties for Chromium screenshot routes. +/// Controls device dimensions, image format, quality, and rendering options. +/// +public class ScreenshotProperties : FacetBase +{ + [MultiFormHeader(Constants.Gotenberg.Chromium.Screenshot.Width)] + public ScreenDimension? Width { get; set; } + + [MultiFormHeader(Constants.Gotenberg.Chromium.Screenshot.Height)] + public ScreenDimension? Height { get; set; } + + [MultiFormHeader(Constants.Gotenberg.Chromium.Screenshot.Clip)] + public bool? Clip { get; set; } + + [MultiFormHeader(Constants.Gotenberg.Chromium.Screenshot.Format)] + public ScreenshotFormat? Format { get; set; } + + [MultiFormHeader(Constants.Gotenberg.Chromium.Screenshot.Quality)] + public CompressionQuality? Quality { get; set; } + + [MultiFormHeader(Constants.Gotenberg.Chromium.Screenshot.OmitBackground)] + public bool? OmitBackground { get; set; } + + [MultiFormHeader(Constants.Gotenberg.Chromium.Screenshot.OptimizeForSpeed)] + public bool? OptimizeForSpeed { get; set; } +} diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotHtmlRequest.cs b/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotHtmlRequest.cs new file mode 100644 index 0000000..cbbd958 --- /dev/null +++ b/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotHtmlRequest.cs @@ -0,0 +1,49 @@ +// 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. + +namespace Gotenberg.Sharp.API.Client.Domain.Requests; + +/// +/// Captures a screenshot of HTML content using Chromium. +/// +public sealed class ScreenshotHtmlRequest : ScreenshotRequest +{ + protected override string ApiPath => Constants.Gotenberg.Chromium.ApiPaths.ScreenshotHtml; + + public FullDocument? Content { get; set; } + + protected override void Validate() + { + if (this.Content?.Body == null) + throw new InvalidOperationException("HTML body content is required for screenshot."); + + base.Validate(); + } + + protected override IEnumerable ToHttpContent() + { + if (this.Content != null) + { + foreach (var item in this.Content.ToHttpContent()) + yield return item; + } + + foreach (var item in this.Assets.IfNullEmptyContent()) + yield return item; + + foreach (var item in base.ToHttpContent()) + yield return item; + } +} diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotRequest.cs b/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotRequest.cs new file mode 100644 index 0000000..b80ab0a --- /dev/null +++ b/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotRequest.cs @@ -0,0 +1,35 @@ +// 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. + +namespace Gotenberg.Sharp.API.Client.Domain.Requests; + +/// +/// Base class for Chromium screenshot requests. Shares HtmlConversionBehaviors +/// with PDF routes but uses ScreenshotProperties instead of PageProperties. +/// +public abstract class ScreenshotRequest : BuildRequestBase +{ + public ScreenshotProperties ScreenshotProperties { get; set; } = new(); + + public HtmlConversionBehaviors ConversionBehaviors { get; set; } = new(); + + protected override IEnumerable ToHttpContent() + { + return this.ScreenshotProperties.ToHttpContent() + .Concat(this.ConversionBehaviors.ToHttpContent()) + .Concat(this.Config.IfNullEmptyContent()) + .Concat(base.ToHttpContent()); + } +} diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotUrlRequest.cs b/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotUrlRequest.cs new file mode 100644 index 0000000..01cf274 --- /dev/null +++ b/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotUrlRequest.cs @@ -0,0 +1,53 @@ +// 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. + +namespace Gotenberg.Sharp.API.Client.Domain.Requests; + +/// +/// Captures a screenshot of a URL using Chromium. +/// +public sealed class ScreenshotUrlRequest : ScreenshotRequest +{ + protected override string ApiPath => Constants.Gotenberg.Chromium.ApiPaths.ScreenshotUrl; + + private Uri? _url; + + public Uri? Url + { + get => _url; + set + { + if (value != null && !value.IsAbsoluteUri) + throw new ArgumentException("URL must be absolute.", nameof(value)); + _url = value; + } + } + + protected override void Validate() + { + if (this.Url == null || !this.Url.IsAbsoluteUri) + throw new InvalidOperationException("An absolute URL is required for screenshot."); + + base.Validate(); + } + + protected override IEnumerable ToHttpContent() + { + yield return CreateFormDataItem(this.Url!, Constants.Gotenberg.Chromium.Routes.Url.RemoteUrl); + + foreach (var item in base.ToHttpContent()) + yield return item; + } +} diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/CompressionQuality.cs b/src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/CompressionQuality.cs new file mode 100644 index 0000000..f05e670 --- /dev/null +++ b/src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/CompressionQuality.cs @@ -0,0 +1,60 @@ +// 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 System.Globalization; + +namespace Gotenberg.Sharp.API.Client.Domain.ValueObjects; + +/// +/// Represents a validated compression quality value (0-100) for screenshot output. +/// Only applies to JPEG format screenshots. +/// +public sealed class CompressionQuality : IEquatable +{ + public const int MinValue = 0; + public const int MaxValue = 100; + + public int Value { get; } + + private CompressionQuality(int value) + { + Value = value; + } + + public static CompressionQuality Create(int quality) + { + if (quality < MinValue || quality > MaxValue) + throw new ArgumentOutOfRangeException( + nameof(quality), + quality, + $"Compression quality must be between {MinValue} and {MaxValue}."); + + return new CompressionQuality(quality); + } + + public override string ToString() => Value.ToString(CultureInfo.InvariantCulture); + + public bool Equals(CompressionQuality? other) => other is not null && Value == other.Value; + + public override bool Equals(object? obj) => Equals(obj as CompressionQuality); + + public override int GetHashCode() => Value; + + public static implicit operator int(CompressionQuality quality) => quality.Value; + + public static bool operator ==(CompressionQuality? left, CompressionQuality? right) => Equals(left, right); + + public static bool operator !=(CompressionQuality? left, CompressionQuality? right) => !Equals(left, right); +} diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/ScreenDimension.cs b/src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/ScreenDimension.cs new file mode 100644 index 0000000..d3e0f98 --- /dev/null +++ b/src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/ScreenDimension.cs @@ -0,0 +1,57 @@ +// 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 System.Globalization; + +namespace Gotenberg.Sharp.API.Client.Domain.ValueObjects; + +/// +/// Represents a validated screen dimension (width or height) in pixels for screenshots. +/// Must be a positive integer. +/// +public sealed class ScreenDimension : IEquatable +{ + public int Value { get; } + + private ScreenDimension(int value) + { + Value = value; + } + + public static ScreenDimension Create(int pixels) + { + if (pixels <= 0) + throw new ArgumentOutOfRangeException( + nameof(pixels), + pixels, + "Screen dimension must be a positive integer."); + + return new ScreenDimension(pixels); + } + + public override string ToString() => Value.ToString(CultureInfo.InvariantCulture); + + public bool Equals(ScreenDimension? other) => other is not null && Value == other.Value; + + public override bool Equals(object? obj) => Equals(obj as ScreenDimension); + + public override int GetHashCode() => Value; + + public static implicit operator int(ScreenDimension dim) => dim.Value; + + public static bool operator ==(ScreenDimension? left, ScreenDimension? right) => Equals(left, right); + + public static bool operator !=(ScreenDimension? left, ScreenDimension? right) => !Equals(left, right); +} diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/ScreenshotFormat.cs b/src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/ScreenshotFormat.cs new file mode 100644 index 0000000..b80219c --- /dev/null +++ b/src/Gotenberg.Sharp.Api.Client/Domain/ValueObjects/ScreenshotFormat.cs @@ -0,0 +1,37 @@ +// 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. + +namespace Gotenberg.Sharp.API.Client.Domain.ValueObjects; + +/// +/// Represents the output image format for Chromium screenshot routes. +/// +public enum ScreenshotFormat +{ + Png, + Jpeg, + Webp +} + +internal static class ScreenshotFormatExtensions +{ + internal static string ToFormValue(this ScreenshotFormat format) => format switch + { + ScreenshotFormat.Png => "png", + ScreenshotFormat.Jpeg => "jpeg", + ScreenshotFormat.Webp => "webp", + _ => throw new ArgumentOutOfRangeException(nameof(format)) + }; +} diff --git a/src/Gotenberg.Sharp.Api.Client/GotenbergSharpClient.cs b/src/Gotenberg.Sharp.Api.Client/GotenbergSharpClient.cs index 385acd1..1cc0878 100644 --- a/src/Gotenberg.Sharp.Api.Client/GotenbergSharpClient.cs +++ b/src/Gotenberg.Sharp.Api.Client/GotenbergSharpClient.cs @@ -242,6 +242,66 @@ public virtual async Task ConvertPdfDocumentsAsync( .ConfigureAwait(false); } + /// + /// Captures a screenshot of HTML content using Gotenberg's Chromium module. + /// + /// The HTML screenshot request. + /// Cancellation token for the async operation. + /// A stream containing the screenshot image (PNG, JPEG, or WebP). + /// Gotenberg Screenshot HTML Documentation + public virtual Task ScreenshotHtmlAsync( + ScreenshotHtmlRequest request, + CancellationToken cancelToken = default) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + + return this.ExecuteRequestAsync(request.CreateApiRequest(), cancelToken); + } + + /// + /// Captures a screenshot of HTML content using a builder pattern. + /// + public virtual async Task ScreenshotHtmlAsync( + ScreenshotHtmlRequestBuilder builder, + CancellationToken cancelToken = default) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + var request = await builder.BuildAsync().ConfigureAwait(false); + + return await this.ScreenshotHtmlAsync(request, cancelToken).ConfigureAwait(false); + } + + /// + /// Captures a screenshot of a URL using Gotenberg's Chromium module. + /// + /// The URL screenshot request. + /// Cancellation token for the async operation. + /// A stream containing the screenshot image (PNG, JPEG, or WebP). + /// Gotenberg Screenshot URL Documentation + public virtual Task ScreenshotUrlAsync( + ScreenshotUrlRequest request, + CancellationToken cancelToken = default) + { + if (request == null) throw new ArgumentNullException(nameof(request)); + + return this.ExecuteRequestAsync(request.CreateApiRequest(), cancelToken); + } + + /// + /// Captures a screenshot of a URL using a builder pattern. + /// + public virtual async Task ScreenshotUrlAsync( + ScreenshotUrlRequestBuilder builder, + CancellationToken cancelToken = default) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + var request = await builder.BuildAsync().ConfigureAwait(false); + + return await this.ScreenshotUrlAsync(request, cancelToken).ConfigureAwait(false); + } + /// /// Gets the current version of Gotenberg. /// Custom variants of Gotenberg may not print a strict semver version. diff --git a/src/Gotenberg.Sharp.Api.Client/Infrastructure/Constants.cs b/src/Gotenberg.Sharp.Api.Client/Infrastructure/Constants.cs index 06050f1..b04d4b6 100644 --- a/src/Gotenberg.Sharp.Api.Client/Infrastructure/Constants.cs +++ b/src/Gotenberg.Sharp.Api.Client/Infrastructure/Constants.cs @@ -205,6 +205,23 @@ public static class ApiPaths public const string ConvertHtml = $"{Root}/convert/html"; public const string ConvertMarkdown = $"{Root}/convert/markdown"; + + public const string ScreenshotUrl = $"{Root}/screenshot/url"; + + public const string ScreenshotHtml = $"{Root}/screenshot/html"; + + public const string ScreenshotMarkdown = $"{Root}/screenshot/markdown"; + } + + public static class Screenshot + { + public const string Width = "width"; + public const string Height = "height"; + public const string Clip = "clip"; + public const string Format = "format"; + public const string Quality = "quality"; + public const string OmitBackground = "omitBackground"; + public const string OptimizeForSpeed = "optimizeForSpeed"; } public static class Routes diff --git a/test/GotenbergSharpClient.Tests/ScreenshotTests.cs b/test/GotenbergSharpClient.Tests/ScreenshotTests.cs new file mode 100644 index 0000000..2ab075f --- /dev/null +++ b/test/GotenbergSharpClient.Tests/ScreenshotTests.cs @@ -0,0 +1,200 @@ +using Gotenberg.Sharp.API.Client.Domain.Builders; +using Gotenberg.Sharp.API.Client.Domain.Requests; +using Gotenberg.Sharp.API.Client.Domain.Requests.Facets; +using Gotenberg.Sharp.API.Client.Domain.Settings; +using Gotenberg.Sharp.API.Client.Domain.ValueObjects; +using Gotenberg.Sharp.API.Client.Extensions; +using Microsoft.Extensions.DependencyInjection; + +namespace GotenbergSharpClient.Tests; + +[TestFixture] +public class ScreenshotTests +{ + #region Value Object Tests + + [TestCase(1)] + [TestCase(800)] + [TestCase(3840)] + public void ScreenDimension_Create_WithValidValue_Succeeds(int pixels) + { + var dim = ScreenDimension.Create(pixels); + dim.Value.Should().Be(pixels); + } + + [TestCase(0)] + [TestCase(-1)] + public void ScreenDimension_Create_WithInvalidValue_Throws(int pixels) + { + var act = () => ScreenDimension.Create(pixels); + act.Should().ThrowExactly(); + } + + [TestCase(0)] + [TestCase(50)] + [TestCase(100)] + public void CompressionQuality_Create_WithValidValue_Succeeds(int quality) + { + var q = CompressionQuality.Create(quality); + q.Value.Should().Be(quality); + } + + [TestCase(-1)] + [TestCase(101)] + public void CompressionQuality_Create_WithInvalidValue_Throws(int quality) + { + var act = () => CompressionQuality.Create(quality); + act.Should().ThrowExactly(); + } + + #endregion + + #region Builder Tests + + [Test] + public void ScreenshotHtmlBuilder_SetsAllProperties() + { + var builder = new ScreenshotHtmlRequestBuilder() + .AddDocument(doc => doc.SetBody("test")) + .WithScreenshotProperties(p => p + .SetSize(1920, 1080) + .SetClip() + .SetFormat(ScreenshotFormat.Jpeg) + .SetQuality(80) + .SetOmitBackground() + .SetOptimizeForSpeed()) + .SetConversionBehaviors(b => b.SkipNetworkIdleEvent()); + + var request = builder.Build(); + + request.ScreenshotProperties.Width!.Value.Should().Be(1920); + request.ScreenshotProperties.Height!.Value.Should().Be(1080); + request.ScreenshotProperties.Clip.Should().BeTrue(); + request.ScreenshotProperties.Format.Should().Be(ScreenshotFormat.Jpeg); + request.ScreenshotProperties.Quality!.Value.Should().Be(80); + request.ScreenshotProperties.OmitBackground.Should().BeTrue(); + request.ScreenshotProperties.OptimizeForSpeed.Should().BeTrue(); + } + + [Test] + public void ScreenshotUrlBuilder_SetsUrl() + { + var builder = new ScreenshotUrlRequestBuilder() + .SetUrl("https://example.com") + .WithScreenshotProperties(p => p.SetFormat(ScreenshotFormat.Png)); + + var request = builder.Build(); + + request.Url!.ToString().Should().Be("https://example.com/"); + request.ScreenshotProperties.Format.Should().Be(ScreenshotFormat.Png); + } + + [Test] + public void ScreenshotUrlBuilder_WithRelativeUrl_Throws() + { + var builder = new ScreenshotUrlRequestBuilder(); + + var act = () => builder.SetUrl(new Uri("/relative", UriKind.Relative)); + + act.Should().ThrowExactly(); + } + + #endregion + + #region Serialization Tests + + [Test] + public void ScreenshotProperties_SerializesCorrectly() + { + var props = new ScreenshotProperties + { + Width = ScreenDimension.Create(1920), + Height = ScreenDimension.Create(1080), + Clip = true, + Format = ScreenshotFormat.Jpeg, + Quality = CompressionQuality.Create(80), + OptimizeForSpeed = true + }; + + var httpContents = props.ToHttpContent().ToList(); + + httpContents.FirstOrDefault(c => + c.Headers.ContentDisposition?.Name == "width")! + .ReadAsStringAsync().Result.Should().Be("1920"); + httpContents.FirstOrDefault(c => + c.Headers.ContentDisposition?.Name == "height")! + .ReadAsStringAsync().Result.Should().Be("1080"); + httpContents.FirstOrDefault(c => + c.Headers.ContentDisposition?.Name == "format")! + .ReadAsStringAsync().Result.Should().Be("jpeg"); + httpContents.FirstOrDefault(c => + c.Headers.ContentDisposition?.Name == "quality")! + .ReadAsStringAsync().Result.Should().Be("80"); + } + + #endregion + + #region Integration Tests + + [Test] + public async Task ScreenshotHtml_ReturnsImage() + { + var client = CreateAuthenticatedClient(); + + var builder = new ScreenshotHtmlRequestBuilder() + .AddDocument(doc => doc.SetBody( + "

Screenshot!

")) + .WithScreenshotProperties(p => p + .SetSize(800, 600) + .SetFormat(ScreenshotFormat.Png)); + + var result = await client.ScreenshotHtmlAsync(builder); + + result.Should().NotBeNull(); + result.Length.Should().BeGreaterThan(0); + + // Verify it's a PNG (starts with PNG magic bytes) + result.Position = 0; + var header = new byte[8]; + await result.ReadAsync(header, 0, 8); + header[0].Should().Be(0x89); // PNG signature + header[1].Should().Be(0x50); // 'P' + header[2].Should().Be(0x4E); // 'N' + header[3].Should().Be(0x47); // 'G' + } + + [Test] + public async Task ScreenshotUrl_ReturnsImage() + { + var client = CreateAuthenticatedClient(); + + var builder = new ScreenshotUrlRequestBuilder() + .SetUrl("https://example.com") + .WithScreenshotProperties(p => p + .SetSize(1024, 768) + .SetFormat(ScreenshotFormat.Jpeg) + .SetQuality(90)); + + var result = await client.ScreenshotUrlAsync(builder); + + result.Should().NotBeNull(); + result.Length.Should().BeGreaterThan(0); + } + + private static Gotenberg.Sharp.API.Client.GotenbergSharpClient CreateAuthenticatedClient() + { + var services = new ServiceCollection(); + services.AddOptions() + .Configure(options => + { + options.ServiceUrl = new Uri("http://localhost:3000"); + options.BasicAuthUsername = "testuser"; + options.BasicAuthPassword = "testpass"; + }); + services.AddGotenbergSharpClient(); + return services.BuildServiceProvider() + .GetRequiredService(); + } + + #endregion +} From 46d9eecec80204a09e8fb72ac6351fd4235e3c26 Mon Sep 17 00:00:00 2001 From: Jaben Cargman Date: Sat, 28 Mar 2026 22:19:08 -0400 Subject: [PATCH 2/3] Address CodeRabbit review feedback --- .../ScreenshotTests.cs | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/test/GotenbergSharpClient.Tests/ScreenshotTests.cs b/test/GotenbergSharpClient.Tests/ScreenshotTests.cs index 2ab075f..1459e82 100644 --- a/test/GotenbergSharpClient.Tests/ScreenshotTests.cs +++ b/test/GotenbergSharpClient.Tests/ScreenshotTests.cs @@ -104,7 +104,7 @@ public void ScreenshotUrlBuilder_WithRelativeUrl_Throws() #region Serialization Tests [Test] - public void ScreenshotProperties_SerializesCorrectly() + public async Task ScreenshotProperties_SerializesCorrectly() { var props = new ScreenshotProperties { @@ -118,24 +118,25 @@ public void ScreenshotProperties_SerializesCorrectly() var httpContents = props.ToHttpContent().ToList(); - httpContents.FirstOrDefault(c => + (await httpContents.FirstOrDefault(c => c.Headers.ContentDisposition?.Name == "width")! - .ReadAsStringAsync().Result.Should().Be("1920"); - httpContents.FirstOrDefault(c => + .ReadAsStringAsync()).Should().Be("1920"); + (await httpContents.FirstOrDefault(c => c.Headers.ContentDisposition?.Name == "height")! - .ReadAsStringAsync().Result.Should().Be("1080"); - httpContents.FirstOrDefault(c => + .ReadAsStringAsync()).Should().Be("1080"); + (await httpContents.FirstOrDefault(c => c.Headers.ContentDisposition?.Name == "format")! - .ReadAsStringAsync().Result.Should().Be("jpeg"); - httpContents.FirstOrDefault(c => + .ReadAsStringAsync()).Should().Be("jpeg"); + (await httpContents.FirstOrDefault(c => c.Headers.ContentDisposition?.Name == "quality")! - .ReadAsStringAsync().Result.Should().Be("80"); + .ReadAsStringAsync()).Should().Be("80"); } #endregion #region Integration Tests + [Category("Integration")] [Test] public async Task ScreenshotHtml_ReturnsImage() { @@ -163,6 +164,7 @@ public async Task ScreenshotHtml_ReturnsImage() header[3].Should().Be(0x47); // 'G' } + [Category("Integration")] [Test] public async Task ScreenshotUrl_ReturnsImage() { From 817ba64b975e132e15875a0d4a94d944a9fdcf89 Mon Sep 17 00:00:00 2001 From: Jaben Cargman Date: Sat, 28 Mar 2026 23:22:02 -0400 Subject: [PATCH 3/3] Address CodeRabbit review feedback (round 2) --- README.md | 1 + examples/Screenshot/Program.cs | 18 ++++++++---------- .../Domain/Requests/ScreenshotRequest.cs | 3 +-- .../ScreenshotTests.cs | 4 ++-- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 22a7b41..613c9a4 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ using Gotenberg.Sharp.API.Client; using Gotenberg.Sharp.API.Client.Domain.Builders; using Gotenberg.Sharp.API.Client.Domain.Builders.Faceted; using Gotenberg.Sharp.API.Client.Domain.Requests.Facets; // For Cookie, etc. +using Gotenberg.Sharp.API.Client.Domain.ValueObjects; // For ScreenshotFormat, etc. ``` ### HTML To PDF diff --git a/examples/Screenshot/Program.cs b/examples/Screenshot/Program.cs index 6ea3138..9ea39b9 100644 --- a/examples/Screenshot/Program.cs +++ b/examples/Screenshot/Program.cs @@ -17,18 +17,18 @@ var destinationDirectory = args.Length > 0 ? args[0] : Path.Combine(Directory.GetCurrentDirectory(), "output"); Directory.CreateDirectory(destinationDirectory); +var sharpClient = CreateClient(options); + // Screenshot from HTML -var htmlPath = await ScreenshotFromHtml(destinationDirectory, options); +var htmlPath = await ScreenshotFromHtml(destinationDirectory, sharpClient); Console.WriteLine($"HTML screenshot: {htmlPath}"); // Screenshot from URL -var urlPath = await ScreenshotFromUrl(destinationDirectory, options); +var urlPath = await ScreenshotFromUrl(destinationDirectory, sharpClient); Console.WriteLine($"URL screenshot: {urlPath}"); -static async Task ScreenshotFromHtml(string destinationDirectory, GotenbergSharpClientOptions options) +static async Task ScreenshotFromHtml(string destinationDirectory, GotenbergSharpClient sharpClient) { - var sharpClient = CreateClient(options); - var builder = new ScreenshotHtmlRequestBuilder() .AddDocument(doc => doc.SetBody(@" @@ -41,7 +41,7 @@ static async Task ScreenshotFromHtml(string destinationDirectory, Gotenb .SetSize(1280, 720) .SetFormat(ScreenshotFormat.Png)); - var response = await sharpClient.ScreenshotHtmlAsync(builder); + await using var response = await sharpClient.ScreenshotHtmlAsync(builder); var resultPath = Path.Combine(destinationDirectory, $"ScreenshotHtml-{DateTime.Now:yyyyMMddHHmmss}.png"); await using var file = File.Create(resultPath); @@ -49,10 +49,8 @@ static async Task ScreenshotFromHtml(string destinationDirectory, Gotenb return resultPath; } -static async Task ScreenshotFromUrl(string destinationDirectory, GotenbergSharpClientOptions options) +static async Task ScreenshotFromUrl(string destinationDirectory, GotenbergSharpClient sharpClient) { - var sharpClient = CreateClient(options); - var builder = new ScreenshotUrlRequestBuilder() .SetUrl("https://example.com") .WithScreenshotProperties(p => p @@ -61,7 +59,7 @@ static async Task ScreenshotFromUrl(string destinationDirectory, Gotenbe .SetQuality(90) .SetClip()); - var response = await sharpClient.ScreenshotUrlAsync(builder); + await using var response = await sharpClient.ScreenshotUrlAsync(builder); var resultPath = Path.Combine(destinationDirectory, $"ScreenshotUrl-{DateTime.Now:yyyyMMddHHmmss}.jpg"); await using var file = File.Create(resultPath); diff --git a/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotRequest.cs b/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotRequest.cs index b80ab0a..3c7fae9 100644 --- a/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotRequest.cs +++ b/src/Gotenberg.Sharp.Api.Client/Domain/Requests/ScreenshotRequest.cs @@ -29,7 +29,6 @@ protected override IEnumerable ToHttpContent() { return this.ScreenshotProperties.ToHttpContent() .Concat(this.ConversionBehaviors.ToHttpContent()) - .Concat(this.Config.IfNullEmptyContent()) - .Concat(base.ToHttpContent()); + .Concat(this.Config.IfNullEmptyContent()); } } diff --git a/test/GotenbergSharpClient.Tests/ScreenshotTests.cs b/test/GotenbergSharpClient.Tests/ScreenshotTests.cs index 1459e82..58625ee 100644 --- a/test/GotenbergSharpClient.Tests/ScreenshotTests.cs +++ b/test/GotenbergSharpClient.Tests/ScreenshotTests.cs @@ -149,7 +149,7 @@ public async Task ScreenshotHtml_ReturnsImage() .SetSize(800, 600) .SetFormat(ScreenshotFormat.Png)); - var result = await client.ScreenshotHtmlAsync(builder); + using var result = await client.ScreenshotHtmlAsync(builder); result.Should().NotBeNull(); result.Length.Should().BeGreaterThan(0); @@ -177,7 +177,7 @@ public async Task ScreenshotUrl_ReturnsImage() .SetFormat(ScreenshotFormat.Jpeg) .SetQuality(90)); - var result = await client.ScreenshotUrlAsync(builder); + using var result = await client.ScreenshotUrlAsync(builder); result.Should().NotBeNull(); result.Length.Should().BeGreaterThan(0);