├── .editorconfig
├── .github
└── workflows
│ └── deploy.yml
├── .gitignore
├── GitVersion.yml
├── GotenbergSharpApiClient.DotSettings
├── GotenbergSharpApiClient.sln
├── GotenbergSharpApiClient.sln.DotSettings
├── LICENSE
├── README.md
├── appveyor.yml
├── lib
├── Domain
│ ├── Builders
│ │ ├── BaseBuilder.cs
│ │ ├── BaseChromiumBuilder.cs
│ │ ├── BaseMergeBuilder.cs
│ │ ├── Faceted
│ │ │ ├── AssetBuilder.cs
│ │ │ ├── ConfigBuilder.cs
│ │ │ ├── ConversionPdfFormats.cs
│ │ │ ├── DocumentBuilder.cs
│ │ │ ├── HtmlConversionBehaviorBuilder.cs
│ │ │ ├── LibrePdfFormats.cs
│ │ │ ├── Margins.cs
│ │ │ ├── PagePropertyBuilder.cs
│ │ │ ├── PaperSizes.cs
│ │ │ ├── UrlExtraResourcesBuilder.cs
│ │ │ ├── UrlHeaderFooterBuilder.cs
│ │ │ └── WebhookBuilder.cs
│ │ ├── HtmlRequestBuilder.cs
│ │ ├── MergeBuilder.cs
│ │ ├── MergeOfficeBuilder.cs
│ │ ├── PdfConversionBuilder.cs
│ │ └── UrlRequestBuilder.cs
│ ├── ContentTypes
│ │ └── IResolveContentType.cs
│ ├── Dimensions
│ │ ├── Dimension.cs
│ │ └── DimensionUnitType.cs
│ ├── Pages
│ │ └── PageRanges.cs
│ ├── Requests
│ │ ├── ApiRequests
│ │ │ ├── GetApiRequestImpl.cs
│ │ │ ├── IApiRequest.cs
│ │ │ └── PostApiRequestImpl.cs
│ │ ├── BuildRequestBase.cs
│ │ ├── ChromeRequest.cs
│ │ ├── Facets
│ │ │ ├── AssetDictionary.cs
│ │ │ ├── ContentItem.cs
│ │ │ ├── ExtraHttpHeaders.cs
│ │ │ ├── FacetBase.cs
│ │ │ ├── FullDocument.cs
│ │ │ ├── HeaderFooterDocument.cs
│ │ │ ├── HtmlConversionBehaviors.cs
│ │ │ ├── PageProperties.cs
│ │ │ ├── RequestConfig.cs
│ │ │ ├── UrlExtras
│ │ │ │ ├── ExtraUrlResourceItem.cs
│ │ │ │ ├── ExtraUrlResourceType.cs
│ │ │ │ └── ExtraUrlResources.cs
│ │ │ └── Webhook.cs
│ │ ├── HtmlRequest.cs
│ │ ├── IConvertToHttpContent.cs
│ │ ├── MergeOfficeConstants.cs
│ │ ├── MergeOfficeRequest.cs
│ │ ├── MergeRequest.cs
│ │ ├── PdfConversionRequest.cs
│ │ ├── PdfRequestBase.cs
│ │ └── UrlRequest.cs
│ └── Settings
│ │ ├── GotenbergSharpClientOptions.cs
│ │ └── RetryOptions.cs
├── Extensions
│ ├── DictionaryExtensions.cs
│ ├── DimensionHelpers.cs
│ ├── EnumExtensions.cs
│ ├── EnumerableExtensions.cs
│ ├── HttpRequestExtensions.cs
│ ├── IntExtensions.cs
│ ├── KeyValuePairExtensions.cs
│ ├── RequestInterfaceExtensions.cs
│ ├── StringExtensions.cs
│ ├── TypedClientServiceCollectionExtensions.cs
│ └── ValidOfficeMergeItemExtensions.cs
├── GlobalSuppressions.cs
├── GlobalUsings.cs
├── Gotenberg.Sharp.Api.Client.csproj
├── GotenbergSharpApiClient.DotSettings.csproj
├── GotenbergSharpApiClient.DotSettings.sln
├── GotenbergSharpClient.cs
├── Infrastructure
│ ├── Constants.cs
│ ├── ContentTypes
│ │ └── ResolveContentTypeImplementation.cs
│ ├── GotenbergApiException.cs
│ ├── MultiFormHeaderAttribute.cs
│ ├── MultiFormPropertyItem.cs
│ ├── MultiTargetHelpers
│ │ └── KeyValuePair.cs
│ ├── Pipeline
│ │ ├── PolicyFactory.cs
│ │ └── TimeoutHandler.cs
│ └── ValidOfficeMergeItem.cs
└── Resources
│ ├── gotenbergSharpClient-large.PNG
│ └── gotenbergSharpClient.PNG
└── linqpad
├── DI-Example.linq
├── HtmlConvert.linq
├── HtmlWithMarkdown.linq
├── OfficeMerge.linq
├── PdfMerge.linq
├── Resources
├── Html
│ ├── ConvertExample
│ │ ├── body.html
│ │ ├── ear-on-beach.jpg
│ │ └── footer.html
│ ├── UrlFooter.html
│ ├── UrlHeader.html
│ ├── font.woff
│ ├── footer.html
│ ├── header.html
│ ├── img.gif
│ ├── index.html
│ └── style.css
├── Markdown
│ ├── font.woff
│ ├── footer.html
│ ├── header.html
│ ├── img.gif
│ ├── index.html
│ ├── paragraph1.md
│ ├── paragraph2.md
│ ├── paragraph3.md
│ └── style.css
├── OfficeDocs
│ ├── LorumIpsem.txt
│ ├── Visual-Studio-NOTICE.docx
│ ├── document.docx
│ ├── document.rtf
│ └── document2.docx
├── Settings
│ └── appsettings.json
└── office
│ └── document.docx
├── UrlConvert.linq
├── UrlsToMergedPdf.linq
├── Webhook.linq
└── pdfConvert.linq
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.cs]
2 |
3 | # IDE0042: Deconstruct variable declaration
4 | dotnet_diagnostic.IDE0042.severity = none
5 | csharp_using_directive_placement = outside_namespace:silent
6 | csharp_prefer_simple_using_statement = true:suggestion
7 | csharp_prefer_braces = true:silent
8 | csharp_style_namespace_declarations = block_scoped:silent
9 | csharp_style_prefer_method_group_conversion = true:silent
10 | csharp_style_prefer_top_level_statements = true:silent
11 | csharp_style_prefer_primary_constructors = true:suggestion
12 | csharp_style_expression_bodied_methods = false:silent
13 | csharp_style_expression_bodied_constructors = false:silent
14 | csharp_style_expression_bodied_operators = false:silent
15 | csharp_style_expression_bodied_properties = true:silent
16 | csharp_style_expression_bodied_indexers = true:silent
17 | csharp_style_expression_bodied_accessors = true:silent
18 | csharp_style_expression_bodied_lambdas = true:silent
19 | csharp_style_expression_bodied_local_functions = false:silent
20 | csharp_indent_labels = one_less_than_current
21 |
22 | [*.{cs,vb}]
23 | #### Naming styles ####
24 |
25 | # Naming rules
26 |
27 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
28 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
29 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
30 |
31 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
32 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types
33 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
34 |
35 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
36 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
37 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
38 |
39 | # Symbol specifications
40 |
41 | dotnet_naming_symbols.interface.applicable_kinds = interface
42 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
43 | dotnet_naming_symbols.interface.required_modifiers =
44 |
45 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
46 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
47 | dotnet_naming_symbols.types.required_modifiers =
48 |
49 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
50 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
51 | dotnet_naming_symbols.non_field_members.required_modifiers =
52 |
53 | # Naming styles
54 |
55 | dotnet_naming_style.begins_with_i.required_prefix = I
56 | dotnet_naming_style.begins_with_i.required_suffix =
57 | dotnet_naming_style.begins_with_i.word_separator =
58 | dotnet_naming_style.begins_with_i.capitalization = pascal_case
59 |
60 | dotnet_naming_style.pascal_case.required_prefix =
61 | dotnet_naming_style.pascal_case.required_suffix =
62 | dotnet_naming_style.pascal_case.word_separator =
63 | dotnet_naming_style.pascal_case.capitalization = pascal_case
64 |
65 | dotnet_naming_style.pascal_case.required_prefix =
66 | dotnet_naming_style.pascal_case.required_suffix =
67 | dotnet_naming_style.pascal_case.word_separator =
68 | dotnet_naming_style.pascal_case.capitalization = pascal_case
69 | dotnet_style_coalesce_expression = true:suggestion
70 | dotnet_style_null_propagation = true:suggestion
71 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
72 | dotnet_style_prefer_auto_properties = true:silent
73 | dotnet_style_object_initializer = true:suggestion
74 | dotnet_style_collection_initializer = true:suggestion
75 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
76 | tab_width = 4
77 | indent_size = 4
78 | end_of_line = crlf
79 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Build and Push to Nuget
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | dotnet-version: [8.x]
11 |
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v4
15 | with:
16 | fetch-depth: 0
17 |
18 | - name: Setup Dotnet
19 | uses: actions/setup-dotnet@v4
20 | with:
21 | dotnet-version: ${{ matrix.dotnet-version }}
22 |
23 | - name: Install GitVersion
24 | uses: gittools/actions/gitversion/setup@v1.1.1
25 | with:
26 | versionSpec: '5.x'
27 |
28 | - name: GitVersion
29 | id: gitversion
30 | uses: gittools/actions/gitversion/execute@v1.1.1
31 | with:
32 | useConfigFile: true
33 |
34 | - name: Pack
35 | run: dotnet pack -c Release -p:PackageVersion=${{ steps.gitversion.outputs.nuGetVersion }} -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
36 |
37 | - name: Publish
38 | if: github.event_name != 'pull_request' && (github.ref_name == 'master')
39 | run: |
40 | dotnet nuget push **/*.nupkg --source 'https://api.nuget.org/v3/index.json' -k ${{ secrets.NUGETKEY }} --skip-duplicate
41 | dotnet nuget push **/*.snupkg --source 'https://api.nuget.org/v3/index.json' -k ${{ secrets.NUGETKEY }} --skip-duplicate
42 |
--------------------------------------------------------------------------------
/GitVersion.yml:
--------------------------------------------------------------------------------
1 | mode: Mainline
2 | branches: {}
3 | ignore:
4 | sha: []
5 | merge-message-formats: {}
--------------------------------------------------------------------------------
/GotenbergSharpApiClient.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
3 | True
4 |
5 | True
6 | True
7 | True
8 | True
9 |
10 | True
11 | True
12 | True
--------------------------------------------------------------------------------
/GotenbergSharpApiClient.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32112.339
5 | MinimumVisualStudioVersion = 15.0.26124.0
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gotenberg.Sharp.Api.Client", "lib\Gotenberg.Sharp.Api.Client.csproj", "{75F783A4-9392-412F-9DFE-00EE89527C10}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FE638D0D-6A76-4BF4-AF06-D8AAB9726723}"
9 | ProjectSection(SolutionItems) = preProject
10 | .editorconfig = .editorconfig
11 | EndProjectSection
12 | EndProject
13 | Global
14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
15 | Debug|Any CPU = Debug|Any CPU
16 | Debug|x64 = Debug|x64
17 | Debug|x86 = Debug|x86
18 | Release|Any CPU = Release|Any CPU
19 | Release|x64 = Release|x64
20 | Release|x86 = Release|x86
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {75F783A4-9392-412F-9DFE-00EE89527C10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {75F783A4-9392-412F-9DFE-00EE89527C10}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {75F783A4-9392-412F-9DFE-00EE89527C10}.Debug|x64.ActiveCfg = Debug|Any CPU
26 | {75F783A4-9392-412F-9DFE-00EE89527C10}.Debug|x64.Build.0 = Debug|Any CPU
27 | {75F783A4-9392-412F-9DFE-00EE89527C10}.Debug|x86.ActiveCfg = Debug|Any CPU
28 | {75F783A4-9392-412F-9DFE-00EE89527C10}.Debug|x86.Build.0 = Debug|Any CPU
29 | {75F783A4-9392-412F-9DFE-00EE89527C10}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {75F783A4-9392-412F-9DFE-00EE89527C10}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {75F783A4-9392-412F-9DFE-00EE89527C10}.Release|x64.ActiveCfg = Release|Any CPU
32 | {75F783A4-9392-412F-9DFE-00EE89527C10}.Release|x64.Build.0 = Release|Any CPU
33 | {75F783A4-9392-412F-9DFE-00EE89527C10}.Release|x86.ActiveCfg = Release|Any CPU
34 | {75F783A4-9392-412F-9DFE-00EE89527C10}.Release|x86.Build.0 = Release|Any CPU
35 | EndGlobalSection
36 | GlobalSection(SolutionProperties) = preSolution
37 | HideSolutionNode = FALSE
38 | EndGlobalSection
39 | GlobalSection(ExtensibilityGlobals) = postSolution
40 | SolutionGuid = {E732CE09-693A-4EB7-BC1C-34E0D4E2E19F}
41 | EndGlobalSection
42 | EndGlobal
43 |
--------------------------------------------------------------------------------
/GotenbergSharpApiClient.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | Copyright 2019-${CurrentDate.Year} Chris Mohan, Jaben Cargman
3 | and GotenbergSharpApiClient Contributors
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 | True
17 | True
18 | True
19 | True
20 | True
21 | True
22 | True
23 | True
24 | True
25 | True
26 | True
27 | True
28 | True
29 | True
30 | True
31 | True
32 | True
33 | True
34 | True
35 | True
36 | True
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 2.0.{build}
2 |
3 | image: Visual Studio 2022
4 |
5 | branches:
6 | except:
7 | - /feature\/.+/
8 |
9 | configuration: Release
10 |
11 | dotnet_csproj:
12 | patch: true
13 | file: '**\*.csproj'
14 | version: '{version}'
15 | version_prefix: '{version}'
16 | package_version: '{version}'
17 | assembly_version: '{version}'
18 | file_version: '{version}'
19 | informational_version: '{version}'
20 |
21 | build:
22 | project: .\lib\Gotenberg.Sharp.Api.Client.csproj
23 | verbosity: minimal
24 |
25 | before_build:
26 | - dotnet restore .\lib\Gotenberg.Sharp.Api.Client.csproj
27 |
28 | after_build:
29 | - dotnet pack .\lib\Gotenberg.Sharp.Api.Client.csproj --no-restore --configuration %CONFIGURATION%
30 | - appveyor PushArtifact .\lib\bin\%CONFIGURATION%\Gotenberg.Sharp.Api.Client.%APPVEYOR_BUILD_VERSION%.nupkg
31 |
--------------------------------------------------------------------------------
/lib/Domain/Builders/BaseBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 | public abstract class BaseBuilder(TRequest request)
19 | where TRequest : BuildRequestBase
20 | where TBuilder : BaseBuilder
21 | {
22 | protected const string CallBuildAsyncErrorMessage =
23 | "Request has asynchronous items. Call BuildAsync instead.";
24 |
25 | protected readonly List BuildTasks = new();
26 |
27 | protected virtual TRequest Request { get; } = request;
28 |
29 | public TBuilder ConfigureRequest(Action action)
30 | {
31 | if (action == null) throw new ArgumentNullException(nameof(action));
32 |
33 | this.Request.Config ??= new RequestConfig();
34 |
35 | action(new ConfigBuilder(this.Request.Config));
36 |
37 | return (TBuilder)this;
38 | }
39 |
40 | public TBuilder ConfigureRequest(RequestConfig config)
41 | {
42 | this.Request.Config = config ?? throw new ArgumentNullException(nameof(config));
43 |
44 | return (TBuilder)this;
45 | }
46 |
47 | public virtual TRequest Build()
48 | {
49 | if (this.BuildTasks.Any()) throw new InvalidOperationException(CallBuildAsyncErrorMessage);
50 |
51 | return this.Request;
52 | }
53 |
54 | public virtual async Task BuildAsync()
55 | {
56 | if (this.BuildTasks.Any()) await Task.WhenAll(this.BuildTasks).ConfigureAwait(false);
57 |
58 | return this.Request;
59 | }
60 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/BaseChromiumBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 | public abstract class BaseChromiumBuilder(TRequest request)
19 | : BaseBuilder(request)
20 | where TRequest : ChromeRequest
21 | where TBuilder : BaseChromiumBuilder
22 | {
23 | [Obsolete("Use WithPageProperties")]
24 | public TBuilder WithDimensions(Action action)
25 | {
26 | if (action == null) throw new ArgumentNullException(nameof(action));
27 |
28 | var builder = new PagePropertyBuilder(this.Request.PageProperties);
29 |
30 | action(builder);
31 |
32 | this.Request.PageProperties = builder.GetPageProperties();
33 |
34 | return (TBuilder)this;
35 | }
36 |
37 | [Obsolete("Use WithPageProperties")]
38 | public TBuilder WithDimensions(PageProperties pageProperties)
39 | {
40 | this.Request.PageProperties = pageProperties ?? throw new ArgumentNullException(nameof(pageProperties));
41 | return (TBuilder)this;
42 | }
43 |
44 | public TBuilder WithPageProperties(Action action)
45 | {
46 | if (action == null) throw new ArgumentNullException(nameof(action));
47 |
48 | var builder = new PagePropertyBuilder(this.Request.PageProperties);
49 |
50 | action(builder);
51 |
52 | this.Request.PageProperties = builder.GetPageProperties();
53 |
54 | return (TBuilder)this;
55 | }
56 |
57 | public TBuilder WithPageProperties(PageProperties pageProperties)
58 | {
59 | this.Request.PageProperties = pageProperties ?? throw new ArgumentNullException(nameof(pageProperties));
60 |
61 | return (TBuilder)this;
62 | }
63 |
64 | public TBuilder WithAssets(Action action)
65 | {
66 | if (action == null) throw new ArgumentNullException(nameof(action));
67 | action(new AssetBuilder(this.Request.Assets ??= new AssetDictionary()));
68 | return (TBuilder)this;
69 | }
70 |
71 | public TBuilder WithAsyncAssets(Func asyncAction)
72 | {
73 | if (asyncAction == null) throw new ArgumentNullException(nameof(asyncAction));
74 | this.BuildTasks.Add(
75 | asyncAction(new AssetBuilder(this.Request.Assets ??= new AssetDictionary())));
76 | return (TBuilder)this;
77 | }
78 |
79 | public TBuilder SetConversionBehaviors(Action action)
80 | {
81 | if (action == null) throw new ArgumentNullException(nameof(action));
82 | action(new HtmlConversionBehaviorBuilder(this.Request.ConversionBehaviors));
83 | return (TBuilder)this;
84 | }
85 |
86 | public TBuilder SetConversionBehaviors(HtmlConversionBehaviors behaviors)
87 | {
88 | this.Request.ConversionBehaviors =
89 | behaviors ?? throw new ArgumentNullException(nameof(behaviors));
90 | return (TBuilder)this;
91 | }
92 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/BaseMergeBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 | public abstract class BaseMergeBuilder(TRequest request) : BaseBuilder(request)
19 | where TRequest : BuildRequestBase where TBuilder : BaseMergeBuilder
20 | {
21 | public TBuilder WithAssets(Action action)
22 | {
23 | if (action == null) throw new ArgumentNullException(nameof(action));
24 |
25 | action(new AssetBuilder(this.Request.Assets ??= new AssetDictionary()));
26 |
27 | return (TBuilder)this;
28 | }
29 |
30 | public TBuilder WithAsyncAssets(Func asyncAction)
31 | {
32 | if (asyncAction == null) throw new ArgumentNullException(nameof(asyncAction));
33 |
34 | this.BuildTasks.Add(asyncAction(new AssetBuilder(this.Request.Assets ??= new AssetDictionary())));
35 |
36 | return (TBuilder)this;
37 | }
38 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/Faceted/AssetBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Faceted
17 | {
18 | public sealed class AssetBuilder
19 | {
20 | private readonly AssetDictionary _assets;
21 |
22 | internal AssetBuilder(AssetDictionary assets)
23 | {
24 | this._assets = assets;
25 | }
26 |
27 | public AssetBuilder AddItem(string name, ContentItem value)
28 | {
29 | // ReSharper disable once ComplexConditionExpression
30 | if (name.IsNotSet() || new FileInfo(name).Extension.IsNotSet() || name.LastIndexOf('/') >= 0)
31 | {
32 | throw new ArgumentOutOfRangeException(
33 | nameof(name),
34 | "Asset names must be relative file names with extensions");
35 | }
36 |
37 | this._assets.Add(name, value ?? throw new ArgumentNullException(nameof(value)));
38 |
39 | return this;
40 | }
41 |
42 | public AssetBuilder AddItem(string name, string value) => AddItem(name, new ContentItem(value));
43 |
44 | public AssetBuilder AddItem(string name, byte[] value) => AddItem(name, new ContentItem(value));
45 |
46 | public AssetBuilder AddItem(string name, Stream value) => AddItem(name, new ContentItem(value));
47 |
48 | #region 'n' assets
49 |
50 | #region from dictionaries
51 |
52 | public AssetBuilder AddItems(Dictionary? items)
53 | {
54 | foreach (var item in items.IfNullEmpty())
55 | {
56 | this.AddItem(item.Key, item.Value);
57 | }
58 |
59 | return this;
60 | }
61 |
62 | public AssetBuilder AddItems(Dictionary? assets) =>
63 | AddItems(assets?.ToDictionary(a => a.Key, a => new ContentItem(a.Value)));
64 |
65 | public AssetBuilder AddItems(Dictionary? assets) =>
66 | AddItems(assets?.ToDictionary(a => a.Key, a => new ContentItem(a.Value)));
67 |
68 | public AssetBuilder AddItems(Dictionary? assets) =>
69 | AddItems(assets?.ToDictionary(a => a.Key, a => new ContentItem(a.Value)));
70 |
71 | #endregion
72 |
73 | #region from KVP enumerables
74 |
75 | public AssetBuilder AddItems(IEnumerable> assets) =>
76 | AddItems(
77 | new Dictionary(
78 | assets?.ToDictionary(a => a.Key, a => a.Value) ?? throw new ArgumentNullException(nameof(assets))));
79 |
80 | public AssetBuilder AddItems(IEnumerable> assets) =>
81 | AddItems(
82 | new Dictionary(
83 | assets?.ToDictionary(a => a.Key, a => new ContentItem(a.Value))
84 | ?? throw new ArgumentNullException(nameof(assets))));
85 |
86 | public AssetBuilder AddItems(IEnumerable> assets) =>
87 | AddItems(
88 | assets?.ToDictionary(a => a.Key, a => new ContentItem(a.Value))
89 | ?? throw new ArgumentNullException(nameof(assets)));
90 |
91 | public AssetBuilder AddItems(IEnumerable> assets) =>
92 | AddItems(
93 | assets?.ToDictionary(s => s.Key, a => new ContentItem(a.Value))
94 | ?? throw new ArgumentNullException(nameof(assets)));
95 |
96 | #endregion
97 |
98 | #endregion
99 | }
100 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/Faceted/ConfigBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Pages;
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
19 |
20 | public sealed class ConfigBuilder
21 | {
22 | private readonly RequestConfig _requestConfig;
23 |
24 | internal ConfigBuilder(RequestConfig requestConfig)
25 | {
26 | this._requestConfig = requestConfig;
27 | }
28 |
29 | public ConfigBuilder SetPageRanges(string? pageRanges)
30 | {
31 | this._requestConfig.PageRanges = Pages.PageRanges.Create(pageRanges);
32 |
33 | return this;
34 | }
35 |
36 | public ConfigBuilder SetPageRanges(PageRanges? pageRanges)
37 | {
38 | this._requestConfig.PageRanges = pageRanges ?? Pages.PageRanges.All;
39 |
40 | return this;
41 | }
42 |
43 | [Obsolete("Renamed: Use SetPageRanges")]
44 | public ConfigBuilder PageRanges(string pageRanges)
45 | {
46 | return this.SetPageRanges(pageRanges);
47 | }
48 |
49 | public ConfigBuilder SetResultFileName(string resultFileName)
50 | {
51 | if (resultFileName.IsNotSet())
52 | throw new ArgumentException("Cannot be null or empty", nameof(resultFileName));
53 |
54 | this._requestConfig.ResultFileName = resultFileName;
55 |
56 | return this;
57 | }
58 |
59 | [Obsolete("Renamed: Use SetResultFileName")]
60 | public ConfigBuilder ResultFileName(string resultFileName)
61 | {
62 | return this.SetResultFileName(resultFileName);
63 | }
64 |
65 | public ConfigBuilder SetTrace(string trace)
66 | {
67 | if (trace.IsNotSet())
68 | throw new ArgumentException("Trace cannot be null or empty", nameof(trace));
69 |
70 | this._requestConfig.Trace = trace;
71 |
72 | return this;
73 | }
74 |
75 | public ConfigBuilder AddWebhook(Action action)
76 | {
77 | if (action == null) throw new ArgumentNullException(nameof(action));
78 |
79 | this._requestConfig.Webhook ??= new Webhook();
80 |
81 | action(new WebhookBuilder(this._requestConfig.Webhook));
82 |
83 | return this;
84 | }
85 |
86 | public ConfigBuilder SetWebhook(Webhook webhook)
87 | {
88 | this._requestConfig.Webhook = webhook ?? throw new ArgumentNullException(nameof(webhook));
89 |
90 | return this;
91 | }
92 |
93 | [Obsolete("Renamed: Use SetWebhook instead.")]
94 | public ConfigBuilder AddWebhook(Webhook webhook)
95 | {
96 | return this.SetWebhook(webhook);
97 | }
98 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/Faceted/ConversionPdfFormats.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Faceted
17 | {
18 | public enum ConversionPdfFormats
19 | {
20 | ///
21 | /// No PDF conformance format specified.
22 | ///
23 | None = 0,
24 |
25 | ///
26 | /// PDF/A-1b format.
27 | /// Long-term archiving conformance with basic visual reproducibility.
28 | ///
29 | A1b = 1,
30 |
31 | ///
32 | /// PDF/A-2b format.
33 | /// Similar to A-2a but without requiring logical structure or tagging.
34 | ///
35 | A2b = 2,
36 |
37 | ///
38 | /// PDF/A-3b format.
39 | /// Like A-3a but focused on visual fidelity, without logical tagging.
40 | ///
41 | A3b = 3,
42 | }
43 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/Faceted/DocumentBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Faceted;
17 |
18 | ///
19 | /// Note: If you don't specify any dimensions the client sets them to Chrome's defaults
20 | ///
21 | public sealed class DocumentBuilder
22 | {
23 | private readonly FullDocument _content;
24 |
25 | private readonly Action _setContainsMarkdown;
26 |
27 | public DocumentBuilder(FullDocument content, Action setContainsMarkdown)
28 | {
29 | if (content == null) throw new ArgumentNullException(nameof(content));
30 |
31 | this._content = content;
32 | this._setContainsMarkdown = setContainsMarkdown;
33 | }
34 |
35 | #region body
36 |
37 | [Obsolete("Use SetContainsMarkdown()")]
38 | public DocumentBuilder ContainsMarkdown(bool containsMarkdown = true)
39 | {
40 | this._setContainsMarkdown(containsMarkdown);
41 | return this;
42 | }
43 |
44 | public DocumentBuilder SetContainsMarkdown(bool containsMarkdown = true)
45 | {
46 | this._setContainsMarkdown(containsMarkdown);
47 | return this;
48 | }
49 |
50 | public DocumentBuilder SetBody(ContentItem body)
51 | {
52 | this._content.Body = body ?? throw new ArgumentNullException(nameof(body));
53 | return this;
54 | }
55 |
56 | public DocumentBuilder SetBody(string body)
57 | {
58 | return this.SetBody(new ContentItem(body));
59 | }
60 |
61 | public DocumentBuilder SetBody(byte[] body)
62 | {
63 | return this.SetBody(new ContentItem(body));
64 | }
65 |
66 | public DocumentBuilder SetBody(Stream body)
67 | {
68 | return this.SetBody(new ContentItem(body));
69 | }
70 |
71 | #endregion
72 |
73 | #region header
74 |
75 | public DocumentBuilder SetHeader(ContentItem header)
76 | {
77 | this._content.Header = header ?? throw new ArgumentNullException(nameof(header));
78 | return this;
79 | }
80 |
81 | public DocumentBuilder SetHeader(string header)
82 | {
83 | return this.SetHeader(new ContentItem(header));
84 | }
85 |
86 | public DocumentBuilder SetHeader(byte[] header)
87 | {
88 | return this.SetHeader(new ContentItem(header));
89 | }
90 |
91 | public DocumentBuilder SetHeader(Stream header)
92 | {
93 | return this.SetHeader(new ContentItem(header));
94 | }
95 |
96 | #endregion
97 |
98 | #region footer
99 |
100 | public DocumentBuilder SetFooter(ContentItem footer)
101 | {
102 | this._content.Footer = footer ?? throw new ArgumentNullException(nameof(footer));
103 | return this;
104 | }
105 |
106 | public DocumentBuilder SetFooter(string footer)
107 | {
108 | return this.SetFooter(new ContentItem(footer));
109 | }
110 |
111 | public DocumentBuilder SetFooter(byte[] footer)
112 | {
113 | return this.SetFooter(new ContentItem(footer));
114 | }
115 |
116 | public DocumentBuilder SetFooter(Stream footer)
117 | {
118 | return this.SetFooter(new ContentItem(footer));
119 | }
120 |
121 | #endregion
122 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/Faceted/LibrePdfFormats.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 System.Diagnostics.CodeAnalysis;
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
19 |
20 | [SuppressMessage("ReSharper", "InconsistentNaming")]
21 | public enum LibrePdfFormats
22 | {
23 | ///
24 | /// No PDF conformance format specified.
25 | ///
26 | None = 0,
27 |
28 | ///
29 | /// PDF/A-1a format.
30 | /// Ensures accessibility and document structure (tagged PDF).
31 | /// Obsolete as of Gotenberg 7.6 — LibreOffice no longer supports it.
32 | ///
33 | [Obsolete(
34 | "Beginning with version Gotenberg 7.6, LibreOffice has discontinued support for PDF/A-1a: https://gotenberg.dev/docs/troubleshooting#pdfa-1a")]
35 | A1a = 1,
36 |
37 | ///
38 | /// PDF/A-1b format.
39 | /// Long-term archiving conformance with basic visual reproducibility.
40 | ///
41 | A1b = 2,
42 |
43 | ///
44 | /// PDF/A-2a format.
45 | /// Builds on A-1a with improved compression and transparency support; includes tagged structure.
46 | ///
47 | A2a = 3,
48 |
49 | ///
50 | /// PDF/A-2b format.
51 | /// Similar to A-2a but without requiring logical structure or tagging.
52 | ///
53 | A2b = 4,
54 |
55 | ///
56 | /// PDF/A-2u format.
57 | /// Like A-2b but requires Unicode mapping for all text (for searchability).
58 | ///
59 | A2u = 5,
60 |
61 | ///
62 | /// PDF/A-3a format.
63 | /// Allows embedded files; includes full tagging for accessibility.
64 | /// Useful for workflows that require attaching source data (e.g., XML, spreadsheets).
65 | ///
66 | A3a = 6,
67 |
68 | ///
69 | /// PDF/A-3b format.
70 | /// Like A-3a but focused on visual fidelity, without logical tagging.
71 | ///
72 | A3b = 7,
73 |
74 | ///
75 | /// PDF/A-3u format.
76 | /// Combines A-3b conformance with Unicode text requirements.
77 | ///
78 | A3u = 8
79 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/Faceted/Margins.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Faceted;
17 |
18 | public enum Margins
19 | {
20 | None = 0,
21 |
22 | Default = 1,
23 |
24 | Normal = 2,
25 |
26 | Large = 3
27 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/Faceted/PaperSizes.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Faceted;
17 |
18 | public enum PaperSizes
19 | {
20 | ///
21 | /// No specified paper size.
22 | ///
23 | None = 0,
24 |
25 | ///
26 | /// A3 paper size (297 × 420 mm or 11.7 × 16.5 inches).
27 | /// Commonly used for large documents, drawings, and diagrams.
28 | ///
29 | A3 = 1,
30 |
31 | ///
32 | /// A4 paper size (210 × 297 mm or 8.3 × 11.7 inches).
33 | /// Standard paper size for letters, documents, and office printing.
34 | ///
35 | A4 = 2,
36 |
37 | ///
38 | /// A5 paper size (148 × 210 mm or 5.8 × 8.3 inches).
39 | /// Often used for notepads, booklets, and smaller documents.
40 | ///
41 | A5 = 3,
42 |
43 | ///
44 | /// A6 paper size (105 × 148 mm or 4.1 × 5.8 inches).
45 | /// Commonly used for postcards, flyers, and small booklets.
46 | ///
47 | A6 = 4,
48 |
49 | ///
50 | /// Letter paper size (8.5 × 11 inches or 216 × 279 mm).
51 | /// Standard size in North America for business and personal documents.
52 | ///
53 | Letter = 5,
54 |
55 | ///
56 | /// Legal paper size (8.5 × 14 inches or 216 × 356 mm).
57 | /// Common in North America for legal documents.
58 | ///
59 | Legal = 6,
60 |
61 | ///
62 | /// Tabloid paper size (11 × 17 inches or 279 × 432 mm).
63 | /// Used for newspapers, large-format documents, and posters.
64 | ///
65 | Tabloid = 7,
66 |
67 | ///
68 | /// ANSI D paper size (22 × 34 inches or 559 × 864 mm).
69 | /// Used for architectural and engineering drawings.
70 | ///
71 | D = 8,
72 |
73 | ///
74 | /// ANSI E paper size (34 × 44 inches or 864 × 1118 mm).
75 | /// Common for large-scale engineering and architectural plans.
76 | ///
77 | E = 9
78 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/Faceted/UrlExtraResourcesBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests.Facets.UrlExtras;
17 |
18 |
19 |
20 | namespace Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
21 |
22 | public sealed class UrlExtraResourcesBuilder(ExtraUrlResources extraUrlResources)
23 | {
24 | #region add one
25 |
26 | #region link tag
27 |
28 |
29 | public UrlExtraResourcesBuilder AddLinkTag(string url)
30 | {
31 | if (url.IsNotSet()) throw new InvalidOperationException(nameof(url));
32 |
33 | return this.AddLinkTag(new Uri(url));
34 | }
35 |
36 |
37 | public UrlExtraResourcesBuilder AddLinkTag(Uri url)
38 | {
39 | return this.AddItem(new ExtraUrlResourceItem(url, ExtraUrlResourceType.LinkTag));
40 | }
41 |
42 | #endregion
43 |
44 | #region script tag
45 |
46 |
47 | public UrlExtraResourcesBuilder AddScriptTag(string url)
48 | {
49 | if (url.IsNotSet()) throw new InvalidOperationException(nameof(url));
50 |
51 | return this.AddScriptTag(new Uri(url));
52 | }
53 |
54 |
55 | public UrlExtraResourcesBuilder AddScriptTag(Uri url)
56 | {
57 | return this.AddItem(new ExtraUrlResourceItem(url, ExtraUrlResourceType.ScriptTag));
58 | }
59 |
60 | #endregion
61 |
62 | #region caller specifies type
63 |
64 |
65 | public UrlExtraResourcesBuilder AddItem(ExtraUrlResourceItem item)
66 | {
67 | return this.AddItems(new[] { item ?? throw new ArgumentNullException(nameof(item)) });
68 | }
69 |
70 | #endregion
71 |
72 | #endregion
73 |
74 | #region add many
75 |
76 | #region link tags
77 |
78 |
79 | public UrlExtraResourcesBuilder AddLinkTags(IEnumerable urls)
80 | {
81 | return this.AddLinkTags(urls.IfNullEmpty().Select(u => new Uri(u)));
82 | }
83 |
84 |
85 | public UrlExtraResourcesBuilder AddLinkTags(IEnumerable urls)
86 | {
87 | return this.AddItems(
88 | urls.IfNullEmpty()
89 | .Select(u => new ExtraUrlResourceItem(u, ExtraUrlResourceType.LinkTag)));
90 | }
91 |
92 | #endregion
93 |
94 | #region script tags
95 |
96 |
97 | public UrlExtraResourcesBuilder AddScriptTags(IEnumerable urls)
98 | {
99 | return this.AddScriptTags(urls.IfNullEmpty().Select(u => new Uri(u)));
100 | }
101 |
102 |
103 | public UrlExtraResourcesBuilder AddScriptTags(IEnumerable urls)
104 | {
105 | return this.AddItems(
106 | urls.IfNullEmpty().Select(
107 | u => new ExtraUrlResourceItem(u, ExtraUrlResourceType.ScriptTag)));
108 | }
109 |
110 | #endregion
111 |
112 | #region caller specifies type
113 |
114 |
115 | public UrlExtraResourcesBuilder AddItems(IEnumerable items)
116 | {
117 | extraUrlResources.Items.AddRange(items.IfNullEmpty());
118 | return this;
119 | }
120 |
121 | #endregion
122 |
123 | #endregion
124 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/Faceted/UrlHeaderFooterBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 |
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
19 |
20 | public sealed class UrlHeaderFooterBuilder(HeaderFooterDocument headerFooterDocument)
21 | {
22 | #region header
23 |
24 |
25 | public UrlHeaderFooterBuilder SetHeader(ContentItem header)
26 | {
27 | headerFooterDocument.Header =
28 | header ?? throw new ArgumentNullException(nameof(header));
29 | return this;
30 | }
31 |
32 |
33 | public UrlHeaderFooterBuilder SetHeader(string header)
34 | {
35 | return this.SetHeader(new ContentItem(header));
36 | }
37 |
38 |
39 | public UrlHeaderFooterBuilder SetHeader(byte[] header)
40 | {
41 | return this.SetHeader(new ContentItem(header));
42 | }
43 |
44 |
45 | public UrlHeaderFooterBuilder SetHeader(Stream header)
46 | {
47 | return this.SetHeader(new ContentItem(header));
48 | }
49 |
50 | #endregion
51 |
52 | #region footer
53 |
54 |
55 | public UrlHeaderFooterBuilder SetFooter(ContentItem footer)
56 | {
57 | headerFooterDocument.Footer =
58 | footer ?? throw new ArgumentNullException(nameof(footer));
59 | return this;
60 | }
61 |
62 |
63 | public UrlHeaderFooterBuilder SetFooter(string footer)
64 | {
65 | return this.SetFooter(new ContentItem(footer));
66 | }
67 |
68 |
69 | public UrlHeaderFooterBuilder SetFooter(byte[] footer)
70 | {
71 | return this.SetFooter(new ContentItem(footer));
72 | }
73 |
74 |
75 | public UrlHeaderFooterBuilder SetFooter(Stream footer)
76 | {
77 | return this.SetFooter(new ContentItem(footer));
78 | }
79 |
80 | #endregion
81 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/Faceted/WebhookBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 |
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
19 |
20 | public sealed class WebhookBuilder
21 | {
22 | private readonly Webhook _webhook;
23 |
24 | internal WebhookBuilder(Webhook webhook)
25 | {
26 | this._webhook = webhook;
27 | }
28 |
29 | ///
30 | /// When testing web hooks against a local container and a service
31 | /// running on localhost, to receive the posts, use http://host.docker.internal:your-port
32 | /// Reference: https://docs.docker.com/docker-for-windows/networking/#use-cases-and-workarounds
33 | ///
34 | ///
35 | ///
36 | ///
37 |
38 | public WebhookBuilder SetUrl(string url, HttpMethod? method = null)
39 | {
40 | if (url.IsNotSet()) throw new ArgumentException("url is either null or empty");
41 |
42 | return this.SetUrl(new Uri(url), method);
43 | }
44 |
45 |
46 | public WebhookBuilder SetUrl(Uri url, HttpMethod? method = null)
47 | {
48 | if (url == null) throw new ArgumentNullException(nameof(url));
49 | if (!url.IsAbsoluteUri)
50 | throw new InvalidOperationException("Url base href is not absolute");
51 |
52 | this._webhook.TargetUrl = url;
53 | this._webhook.HttpMethod = method?.ToString();
54 |
55 | return this;
56 | }
57 |
58 |
59 | public WebhookBuilder SetErrorUrl(string errorUrl, HttpMethod? method = null)
60 | {
61 | if (errorUrl.IsNotSet()) throw new ArgumentException("url is either null or empty");
62 |
63 | return this.SetErrorUrl(new Uri(errorUrl), method);
64 | }
65 |
66 |
67 | public WebhookBuilder SetErrorUrl( Uri url, HttpMethod? method = null)
68 | {
69 | if (url == null) throw new ArgumentNullException(nameof(url));
70 | if (!url.IsAbsoluteUri)
71 | throw new InvalidOperationException("Url base href is not absolute");
72 |
73 | this._webhook.ErrorUrl = url;
74 | this._webhook.ErrorHttpMethod = method?.ToString();
75 |
76 | return this;
77 | }
78 |
79 |
80 | public WebhookBuilder AddExtraHeader(string name, string value)
81 | {
82 | return this.AddExtraHeader(name, new[] { value });
83 | }
84 |
85 |
86 | public WebhookBuilder AddExtraHeader(KeyValuePair header)
87 | {
88 | return this.AddExtraHeader(header.Key, new[] { header.Value });
89 | }
90 |
91 |
92 | public WebhookBuilder AddExtraHeader(string name, IEnumerable values)
93 | {
94 | if (name.IsNotSet())
95 | throw new ArgumentException("extra header name is null || empty", nameof(name));
96 |
97 | this._webhook.ExtraHttpHeaders.Add(name, values);
98 |
99 | return this;
100 | }
101 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/HtmlRequestBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 | public sealed class HtmlRequestBuilder(bool containsMarkdown)
19 | : BaseChromiumBuilder(new HtmlRequest(containsMarkdown))
20 | {
21 | public HtmlRequestBuilder()
22 | : this(false)
23 | {
24 | }
25 |
26 | public HtmlRequestBuilder AddDocument(Action action)
27 | {
28 | if (action == null) throw new ArgumentNullException(nameof(action));
29 |
30 | action(new DocumentBuilder(this.Request.Content, v => this.Request.ContainsMarkdown = v));
31 |
32 | return this;
33 | }
34 |
35 | public HtmlRequestBuilder AddAsyncDocument(Func asyncAction)
36 | {
37 | if (asyncAction == null) throw new ArgumentNullException(nameof(asyncAction));
38 |
39 | this.BuildTasks.Add(
40 | asyncAction(new DocumentBuilder(this.Request.Content, v => this.Request.ContainsMarkdown = v)));
41 |
42 | return this;
43 | }
44 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/MergeBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 | public sealed class MergeBuilder() : BaseMergeBuilder(new MergeRequest())
19 | {
20 | ///
21 | /// Convert the resulting PDF into the given PDF/A format.
22 | ///
23 | public MergeBuilder SetPdfFormat(LibrePdfFormats format)
24 | {
25 | this.Request.PdfFormat = format;
26 | return this;
27 | }
28 |
29 | ///
30 | /// Flatten the resulting PDF.
31 | ///
32 | public MergeBuilder SetFlatten(bool enableFlatten = true)
33 | {
34 | this.Request.EnableFlatten = enableFlatten;
35 | return this;
36 | }
37 |
38 | ///
39 | /// This tells gotenberg to enable Universal Access for the resulting PDF.
40 | ///
41 | public MergeBuilder SetPdfUa(bool enablePdfUa = true)
42 | {
43 | this.Request.EnablePdfUa = enablePdfUa;
44 | return this;
45 | }
46 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/MergeOfficeBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 | ///
19 | /// Any non office files sent in are just ignored.
20 | /// A nice surprise: Gotenberg/Chrome will merge in all sheets within a multi-sheet excel workbook.
21 | /// If you send in a csv file but with a xlsx extension, it will merge it in as text.
22 | ///
23 | public sealed class MergeOfficeBuilder()
24 | : BaseMergeBuilder(new MergeOfficeRequest())
25 | {
26 | public MergeOfficeBuilder PrintAsLandscape()
27 | {
28 | this.Request.PrintAsLandscape = true;
29 | return this;
30 | }
31 |
32 | ///
33 | /// If provided, the API will return a pdf containing the pages in the specified range.
34 | ///
35 | ///
36 | /// The format is the same as the one from the print options of Google Chrome, e.g. 1-5,8,11-13.
37 | /// This may move...
38 | ///
39 | public MergeOfficeBuilder SetPageRanges(string pageRanges)
40 | {
41 | this.Request.PageRanges = pageRanges;
42 | return this;
43 | }
44 |
45 | ///
46 | /// Convert the resulting PDF into the given PDF/A format.
47 | ///
48 | public MergeOfficeBuilder SetPdfFormat(LibrePdfFormats format)
49 | {
50 | this.Request.PdfFormat = format;
51 | return this;
52 | }
53 |
54 | ///
55 | /// Flatten the resulting PDF.
56 | ///
57 | public MergeOfficeBuilder SetFlatten(bool enableFlatten = true)
58 | {
59 | this.Request.EnableFlatten = enableFlatten;
60 | return this;
61 | }
62 |
63 | ///
64 | /// This tells gotenberg to enable Universal Access for the resulting PDF.
65 | ///
66 | public MergeOfficeBuilder SetPdfUa(bool enablePdfUa = true)
67 | {
68 | this.Request.EnablePdfUa = enablePdfUa;
69 | return this;
70 | }
71 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/PdfConversionBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 | public sealed class PdfConversionBuilder()
19 | : BaseBuilder(new PdfConversionRequest())
20 | {
21 | ///
22 | /// Convert the resulting PDF into the given PDF/A format.
23 | ///
24 | ///
25 | ///
26 | ///
27 | public PdfConversionBuilder SetPdfFormat(LibrePdfFormats format)
28 | {
29 | if (format == default) throw new ArgumentNullException(nameof(format));
30 |
31 | this.Request.PdfFormat = format;
32 |
33 | return this;
34 | }
35 |
36 | ///
37 | /// Flatten the resulting PDF.
38 | ///
39 | ///
40 | ///
41 | public PdfConversionBuilder EnableFlatten(bool enableFlatten = true)
42 | {
43 | this.Request.EnableFlatten = enableFlatten;
44 |
45 | return this;
46 | }
47 |
48 | ///
49 | /// Enable PDF for Universal Access for optimal accessibility.
50 | ///
51 | ///
52 | ///
53 | public PdfConversionBuilder EnablePdfUa(bool enablePdfUa = true)
54 | {
55 | this.Request.EnablePdfUa = enablePdfUa;
56 |
57 | return this;
58 | }
59 |
60 | public PdfConversionBuilder WithPdfs(Action action)
61 | {
62 | if (action == null) throw new ArgumentNullException(nameof(action));
63 |
64 | action(new AssetBuilder(this.Request.Assets ??= new AssetDictionary()));
65 |
66 | return this;
67 | }
68 |
69 | public PdfConversionBuilder WithPdfsAsync(Func asyncAction)
70 | {
71 | if (asyncAction == null) throw new ArgumentNullException(nameof(asyncAction));
72 |
73 | this.BuildTasks.Add(asyncAction(new AssetBuilder(this.Request.Assets ??= new AssetDictionary())));
74 |
75 | return this;
76 | }
77 | }
--------------------------------------------------------------------------------
/lib/Domain/Builders/UrlRequestBuilder.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests.Facets.UrlExtras;
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Builders;
19 |
20 | public sealed class UrlRequestBuilder() : BaseChromiumBuilder(new UrlRequest())
21 | {
22 | public UrlRequestBuilder SetUrl(string url)
23 | {
24 | if (url.IsNotSet()) throw new ArgumentException("url is either null or empty");
25 |
26 | return this.SetUrl(new Uri(url));
27 | }
28 |
29 | public UrlRequestBuilder SetUrl(Uri url)
30 | {
31 | this.Request.Url = url ?? throw new ArgumentNullException(nameof(url));
32 | if (!url.IsAbsoluteUri) throw new InvalidOperationException("url is not absolute");
33 |
34 | return this;
35 | }
36 |
37 | ///
38 | /// Sets the format of the resulting PDF document
39 | ///
40 | ///
41 | ///
42 | ///
43 | public UrlRequestBuilder SetPdfFormat(ConversionPdfFormats format)
44 | {
45 | if (format == default) throw new InvalidOperationException("Invalid PDF format specified");
46 |
47 | this.Request.PdfFormat = format;
48 |
49 | return this;
50 | }
51 |
52 | ///
53 | /// This tells gotenberg to enable Universal Access for the resulting PDF.
54 | ///
55 | public UrlRequestBuilder SetPdfUa(bool enablePdfUa = true)
56 | {
57 | this.Request.EnablePdfUa = enablePdfUa;
58 |
59 | return this;
60 | }
61 |
62 | public UrlRequestBuilder AddHeaderFooter(Action action)
63 | {
64 | if (action == null) throw new ArgumentNullException(nameof(action));
65 |
66 | action(new UrlHeaderFooterBuilder(this.Request.Content ??= new HeaderFooterDocument()));
67 | return this;
68 | }
69 |
70 | public UrlRequestBuilder AddAsyncHeaderFooter(Func asyncAction)
71 | {
72 | if (asyncAction == null) throw new ArgumentNullException(nameof(asyncAction));
73 |
74 | this.BuildTasks.Add(
75 | asyncAction(new UrlHeaderFooterBuilder(this.Request.Content ??= new HeaderFooterDocument())));
76 | return this;
77 | }
78 |
79 | public UrlRequestBuilder AddExtraResources(Action action)
80 | {
81 | if (action == null) throw new ArgumentNullException(nameof(action));
82 |
83 | action(new UrlExtraResourcesBuilder(this.Request.ExtraResources ??= new ExtraUrlResources()));
84 | return this;
85 | }
86 | }
--------------------------------------------------------------------------------
/lib/Domain/ContentTypes/IResolveContentType.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.ContentTypes;
17 |
18 | public interface IResolveContentType
19 | {
20 | string GetContentType(
21 | string fileName,
22 | string defaultContentType = "application/octet-stream");
23 | }
--------------------------------------------------------------------------------
/lib/Domain/Dimensions/DimensionUnitType.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 System.ComponentModel;
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Dimensions
19 | {
20 | public enum DimensionUnitType
21 | {
22 | [Description("pt")] Points, // Points
23 | [Description("px")] Pixels, // Pixels
24 | [Description("in")] Inches, // Inches
25 | [Description("mm")] Millimeters, // Millimeters
26 | [Description("cm")] Centimeters, // Centimeters
27 | [Description("pc")] Picas // Picas
28 | }
29 | }
--------------------------------------------------------------------------------
/lib/Domain/Pages/PageRanges.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 System.Globalization;
17 | using System.Text.RegularExpressions;
18 |
19 | namespace Gotenberg.Sharp.API.Client.Domain.Pages;
20 |
21 | public sealed class PageRanges : IEquatable
22 | {
23 | private static readonly Regex PageRangePattern =
24 | new(@"^\s*(\d+(-\d+)?)(\s*,\s*(\d+(-\d+)?))*\s*$");
25 |
26 | private PageRanges(IReadOnlyCollection pages)
27 | {
28 | Pages = pages.OrderBy(p => p).ToArray();
29 | }
30 |
31 | public static PageRanges All { get; } = new(Array.Empty());
32 |
33 | public IReadOnlyCollection Pages { get; }
34 |
35 | public bool Equals(PageRanges? other)
36 | {
37 | return other is not null && Pages.SequenceEqual(other.Pages);
38 | }
39 |
40 | public static PageRanges Create(string? input)
41 | {
42 | if (string.IsNullOrWhiteSpace(input))
43 | {
44 | return All;
45 | }
46 |
47 | if (!PageRangePattern.IsMatch(input))
48 | {
49 | throw new ArgumentOutOfRangeException(nameof(input),
50 | "Invalid page range format. Expected format: '1-5, 8, 11-13'.");
51 | }
52 |
53 | var pages = new SortedSet();
54 | foreach (var part in input!.Split([','], StringSplitOptions.RemoveEmptyEntries))
55 | {
56 | var trimmed = part.Trim();
57 | if (trimmed.Contains('-'))
58 | {
59 | var bounds = trimmed.Split('-').Select(int.Parse).ToArray();
60 | if (bounds.Length == 2 && bounds[0] <= bounds[1])
61 | {
62 | for (var i = bounds[0]; i <= bounds[1]; i++)
63 | {
64 | pages.Add(i);
65 | }
66 | }
67 | }
68 | else if (int.TryParse(trimmed, out var singlePage))
69 | {
70 | pages.Add(singlePage);
71 | }
72 | }
73 |
74 | return new PageRanges(pages);
75 | }
76 |
77 | private string GetPageRangeString()
78 | {
79 | // empty is "all"
80 | if (Pages.Count == 0)
81 | {
82 | return "";
83 | }
84 |
85 | var ranges = new List();
86 | int start = Pages.First(), end = start;
87 |
88 | foreach (var page in Pages.Skip(1))
89 | {
90 | if (page == end + 1)
91 | {
92 | end = page;
93 | }
94 | else
95 | {
96 | ranges.Add(start == end ? start.ToString(CultureInfo.InvariantCulture) : $"{start}-{end}");
97 | start = end = page;
98 | }
99 | }
100 |
101 | ranges.Add(start == end ? start.ToString(CultureInfo.InvariantCulture) : $"{start}-{end}");
102 | return string.Join(", ", ranges);
103 | }
104 |
105 | public override string ToString()
106 | {
107 | return GetPageRangeString();
108 | }
109 |
110 | public override bool Equals(object? obj)
111 | {
112 | return obj is PageRanges other && Pages.SequenceEqual(other.Pages);
113 | }
114 |
115 | public override int GetHashCode()
116 | {
117 | return Pages.Aggregate(0, (hash, page) => hash ^ page.GetHashCode());
118 | }
119 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/ApiRequests/GetApiRequestImpl.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests.ApiRequests;
17 |
18 | internal sealed class GetApiRequestImpl : IApiRequest
19 | {
20 | internal GetApiRequestImpl(string apiPath, ILookup? headers = null, bool isWebhookRequest = false)
21 | {
22 | this.ApiPath = apiPath;
23 | this.IsWebhookRequest = isWebhookRequest;
24 | this.Headers = headers;
25 | }
26 |
27 | public string ApiPath { get; }
28 |
29 | public ILookup? Headers { get; }
30 |
31 | private const string BoundaryPrefix = Constants.HttpContent.MultipartData.BoundaryPrefix;
32 |
33 | public bool IsWebhookRequest { get; }
34 |
35 | public HttpRequestMessage ToApiRequestMessage()
36 | {
37 | var message = new HttpRequestMessage(HttpMethod.Get, this.ApiPath);
38 |
39 | foreach (var header in this.Headers.IfNullEmpty())
40 | {
41 | message.Headers.Add(header.Key, header);
42 | }
43 |
44 | return message;
45 | }
46 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/ApiRequests/IApiRequest.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests.ApiRequests;
17 |
18 | public interface IApiRequest
19 | {
20 | HttpRequestMessage ToApiRequestMessage();
21 |
22 | bool IsWebhookRequest { get; }
23 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/ApiRequests/PostApiRequestImpl.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests.ApiRequests;
17 |
18 | internal sealed class PostApiRequestImpl : IApiRequest, IConvertToHttpContent
19 | {
20 | private readonly Func> _toHttpContent;
21 |
22 | internal PostApiRequestImpl(
23 | Func> toHttpContent,
24 | string apiPath,
25 | ILookup? headers,
26 | bool isWebhookRequest)
27 | {
28 | _toHttpContent = toHttpContent;
29 | ApiPath = apiPath;
30 | IsWebhookRequest = isWebhookRequest;
31 | Headers = headers;
32 | }
33 |
34 | public string ApiPath { get; }
35 |
36 | public ILookup? Headers { get; }
37 |
38 | private const string BoundaryPrefix = Constants.HttpContent.MultipartData.BoundaryPrefix;
39 |
40 | public bool IsWebhookRequest { get; }
41 |
42 | public HttpRequestMessage ToApiRequestMessage()
43 | {
44 | var formContent = new MultipartFormDataContent($"{BoundaryPrefix}{DateTime.Now.Ticks}");
45 |
46 | foreach (var item in ToHttpContent()) formContent.Add(item);
47 |
48 | var message = new HttpRequestMessage(HttpMethod.Post, ApiPath) { Content = formContent };
49 |
50 | if (Headers?.Any() ?? false)
51 | foreach (var header in Headers)
52 | message.Headers.Add(header.Key, header);
53 |
54 | return message;
55 | }
56 |
57 | public IEnumerable ToHttpContent() => _toHttpContent();
58 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/BuildRequestBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests.ApiRequests;
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Requests;
19 |
20 | public abstract class BuildRequestBase
21 | {
22 | internal RequestConfig? Config { get; set; }
23 |
24 | internal AssetDictionary? Assets { get; set; }
25 |
26 | protected abstract string ApiPath { get; }
27 |
28 | private const string _dispositionType = Constants.HttpContent.Disposition.Types.FormData;
29 |
30 | internal static StringContent CreateFormDataItem(T value, string fieldName)
31 | {
32 | var item = new StringContent(value!.ToString()!);
33 |
34 | item.Headers.ContentDisposition = new ContentDispositionHeaderValue(_dispositionType) { Name = fieldName };
35 |
36 | return item;
37 | }
38 |
39 | protected abstract IEnumerable ToHttpContent();
40 |
41 | protected virtual void Validate()
42 | {
43 | this.Config?.Validate();
44 | this.Assets?.Validate();
45 | }
46 |
47 | public virtual IApiRequest CreateApiRequest()
48 | {
49 | this.Validate();
50 |
51 | var isWebHook = this.Config?.Webhook?.IsConfigured() ?? false;
52 |
53 | var headers = (this.Config?.GetHeaders()).IfNullEmpty().ToLookup(s => s.Name, s => s.Value);
54 |
55 | return new PostApiRequestImpl(this.ToHttpContent, this.ApiPath, headers, isWebHook);
56 | }
57 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/ChromeRequest.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests
17 | {
18 | public abstract class ChromeRequest : BuildRequestBase
19 | {
20 | public PageProperties PageProperties { get; set; } = PageProperties.ToChromeDefaults();
21 |
22 | public HtmlConversionBehaviors ConversionBehaviors { get; set; } = new();
23 |
24 | protected override IEnumerable ToHttpContent() =>
25 | Config.IfNullEmptyContent()
26 | .Concat(this.PageProperties.IfNullEmptyContent())
27 | .Concat(ConversionBehaviors.IfNullEmptyContent());
28 | }
29 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/Facets/AssetDictionary.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.ContentTypes;
17 | using Gotenberg.Sharp.API.Client.Infrastructure.ContentTypes;
18 |
19 | namespace Gotenberg.Sharp.API.Client.Domain.Requests.Facets
20 | {
21 | public sealed class AssetDictionary : Dictionary, IConvertToHttpContent
22 | {
23 | readonly IResolveContentType _resolveContentType = new ResolveContentTypeImplementation();
24 |
25 | public IEnumerable ToHttpContent()
26 | {
27 | return this.Select(
28 | item => new
29 | {
30 | Asset = item,
31 | MediaType = _resolveContentType.GetContentType(item.Key)
32 | })
33 | .Where(i => i.MediaType.IsSet())
34 | .Select(
35 | item =>
36 | {
37 | var asset = item.Asset.Value.ToHttpContentItem();
38 |
39 | asset.Headers.ContentDisposition =
40 | new ContentDispositionHeaderValue(
41 | Constants.HttpContent.Disposition.Types.FormData)
42 | {
43 | Name = Constants.Gotenberg.SharedFormFieldNames.Files,
44 | FileName = item.Asset.Key
45 | };
46 |
47 | asset.Headers.ContentType = new MediaTypeHeaderValue(item.MediaType);
48 |
49 | return asset;
50 | });
51 | }
52 |
53 | public AssetDictionary AddRangeFluently(
54 | IEnumerable> items)
55 | {
56 | if (items == null) throw new ArgumentNullException(nameof(items));
57 |
58 | var pairs = items as KeyValuePair[] ?? items.ToArray();
59 |
60 | if (pairs.Any(item => item.Key.IsNotSet()))
61 | throw new ArgumentException("One or more asset file names are null or empty");
62 |
63 | foreach (var item in pairs)
64 | {
65 | this.Add(item.Key, item.Value);
66 | }
67 |
68 | return this;
69 | }
70 |
71 | public void Validate()
72 | {
73 | if (this.Count == 0) throw new InvalidOperationException("Must add at least one asset");
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/Facets/ContentItem.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 |
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Requests.Facets
19 | {
20 | public sealed class ContentItem
21 | {
22 | readonly Func _getHttpContent;
23 |
24 | ContentItem(Func getHttpContent)
25 | {
26 | _getHttpContent = getHttpContent;
27 | }
28 |
29 | public ContentItem(byte[] bytes)
30 | : this(() => new ByteArrayContent(bytes))
31 | {
32 | if (bytes == null) throw new ArgumentNullException(nameof(bytes));
33 | }
34 |
35 | public ContentItem(string str)
36 | : this(() => new StringContent(str))
37 | {
38 | if (str.IsNotSet())
39 | throw new ArgumentOutOfRangeException(nameof(str), "Must not be null or empty");
40 | }
41 |
42 | public ContentItem(Stream stream)
43 | : this(() => new StreamContent(stream))
44 | {
45 | if (stream == null) throw new ArgumentNullException(nameof(stream));
46 | }
47 |
48 | public HttpContent ToHttpContentItem()
49 | {
50 | return _getHttpContent();
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/Facets/ExtraHttpHeaders.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests.Facets;
17 |
18 | public sealed class ExtraHttpHeaders
19 | {
20 | private readonly Dictionary> _headers = new();
21 |
22 | public void Add(string name, string value)
23 | {
24 | if (name.IsNotSet()) throw new ArgumentException("Header name is null or empty");
25 |
26 | this._headers.Add(name, [value]);
27 | }
28 |
29 | public void Add(string name, IEnumerable values)
30 | {
31 | if (name.IsNotSet()) throw new ArgumentException("Header name is null or empty");
32 |
33 | this._headers.Add(name, values.ToArray());
34 | }
35 |
36 | internal IEnumerable<(string Name, string? Value)> GetHeaders()
37 | {
38 | if (!this._headers.Any()) yield break;
39 |
40 | yield return (Constants.Gotenberg.Webhook.ExtraHeaders, this.ToJson());
41 | }
42 |
43 | internal string ToJson()
44 | {
45 | return JsonConvert.SerializeObject(
46 | this._headers.ToDictionary(
47 | entry => entry.Key,
48 | entry => string.Join(",", entry.Value)));
49 | }
50 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/Facets/FacetBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 System.Globalization;
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Requests.Facets
19 | {
20 | public abstract class FacetBase : IConvertToHttpContent
21 | {
22 | public virtual IEnumerable ToHttpContent()
23 | {
24 | return MultiFormPropertyItem.FromType(this.GetType())
25 | .Select(this.GetHttpContentFromProperty)
26 | .WhereNotNull();
27 | }
28 |
29 | internal virtual HttpContent? GetHttpContentFromProperty(MultiFormPropertyItem item)
30 | {
31 | var value = item.Property.GetValue(this);
32 |
33 | if (value == null) return null;
34 |
35 | HttpContent? httpContent;
36 |
37 | if (value is ContentItem contentItem)
38 | {
39 | httpContent = contentItem.ToHttpContentItem();
40 | }
41 | else
42 | {
43 | var convertedValue = GetValueAsInvariantCultureString(value);
44 |
45 | if (convertedValue == null)
46 | {
47 | return null;
48 | }
49 |
50 | httpContent = new StringContent(convertedValue);
51 | }
52 |
53 | httpContent.Headers.ContentType = new MediaTypeHeaderValue(item.Attribute.MediaType);
54 | httpContent.Headers.ContentDisposition =
55 | new ContentDispositionHeaderValue(item.Attribute.ContentDisposition)
56 | {
57 | Name = item.Attribute.Name, FileName = item.Attribute.FileName
58 | };
59 |
60 | return httpContent;
61 | }
62 |
63 | protected static string? GetValueAsInvariantCultureString(object? value)
64 | {
65 | if (value == null) return null;
66 |
67 | var cultureInfo = CultureInfo.InvariantCulture;
68 |
69 | return value switch
70 | {
71 | LibrePdfFormats format => format.ToFormDataValue(),
72 | ConversionPdfFormats format => format.ToFormDataValue(),
73 | float f => f.ToString(cultureInfo),
74 | double d => d.ToString(cultureInfo),
75 | decimal c => c.ToString(cultureInfo),
76 | int i => i.ToString(cultureInfo),
77 | long l => l.ToString(cultureInfo),
78 | DateTime date => date.ToString(cultureInfo),
79 | _ => value.ToString()
80 | };
81 | }
82 | }
83 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/Facets/FullDocument.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests.Facets
17 | {
18 | ///
19 | /// Represents the elements of a document
20 | ///
21 | /// The file names are a Gotenberg Api convention
22 | public sealed class FullDocument : HeaderFooterDocument
23 | {
24 | [MultiFormHeader(fileName: Constants.Gotenberg.Chromium.Routes.Html.IndexFile)]
25 | public ContentItem? Body { get; internal set; }
26 | }
27 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/Facets/HeaderFooterDocument.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests.Facets;
17 |
18 | public class HeaderFooterDocument : FacetBase
19 | {
20 | [MultiFormHeader(fileName: Constants.Gotenberg.Chromium.Shared.FileNames.Header)]
21 | public ContentItem? Header { get; internal set; }
22 |
23 | [MultiFormHeader(fileName: Constants.Gotenberg.Chromium.Shared.FileNames.Footer)]
24 | public ContentItem? Footer { get; internal set; }
25 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/Facets/HtmlConversionBehaviors.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 Newtonsoft.Json.Linq;
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Requests.Facets;
19 |
20 | ///
21 | /// The right side tabs here: https://gotenberg.dev/docs/modules/chromium#routes
22 | ///
23 | public class HtmlConversionBehaviors : FacetBase
24 | {
25 | ///
26 | /// Duration to wait when loading an HTML document before converting it to PDF
27 | ///
28 | ///
29 | /// When the page relies on JavaScript for rendering, and you don't
30 | /// have access to the page's code, you may want to wait a certain amount
31 | /// of time to make sure Chromium has fully rendered the page you're trying to generate.
32 | ///
33 | [MultiFormHeader(Constants.Gotenberg.Chromium.Shared.HtmlConvert.WaitDelay)]
34 | public string? WaitDelay { get; set; }
35 |
36 | ///
37 | /// The JavaScript expression to wait before converting an HTML document to PDF until it returns true
38 | ///
39 | /// builder.SetBrowserWaitExpression("window.status === 'ready'")
40 | /// Prefer this option over waitDelay
41 | [MultiFormHeader(Constants.Gotenberg.Chromium.Shared.HtmlConvert.WaitForExpression)]
42 | public string? WaitForExpression { get; set; }
43 |
44 | ///
45 | /// Overrides the default User-Agent header
46 | ///
47 | [Obsolete("Deprecated in Gotenberg v8+")]
48 | [MultiFormHeader(Constants.Gotenberg.Chromium.Shared.HtmlConvert.UserAgent)]
49 | public string? UserAgent { get; set; }
50 |
51 | ///
52 | /// Sets extra HTTP headers that Chromium will send when loading the HTML
53 | ///
54 | [MultiFormHeader(Constants.Gotenberg.Chromium.Shared.HtmlConvert.ExtraHttpHeaders)]
55 | public JObject? ExtraHeaders { get; set; }
56 |
57 | ///
58 | /// The metadata to write to the PDF (JSON format).
59 | /// Not all metadata are writable.
60 | /// Consider taking a look at https://exiftool.org/TagNames/XMP.html#pdf for an (exhaustive?) list of available metadata.
61 | ///
62 | [MultiFormHeader(Constants.Gotenberg.Chromium.Shared.HtmlConvert.MetaData)]
63 | public JObject? MetaData { get; set; }
64 |
65 | ///
66 | /// Tells gotenberg to return a 409 response if there are exceptions in the Chromium console.
67 | ///
68 | ///
69 | /// Caution: does not work if JavaScript is disabled at the container level via --chromium-disable-javascript.
70 | ///
71 | [MultiFormHeader(Constants.Gotenberg.Chromium.Shared.HtmlConvert.FailOnConsoleExceptions)]
72 | public bool? FailOnConsoleExceptions { get; set; }
73 |
74 | ///
75 | /// The media type to emulate, either "screen" or "print" - empty means "print".
76 | ///
77 | [MultiFormHeader(Constants.Gotenberg.Chromium.Shared.HtmlConvert.EmulatedMediaType)]
78 | public string? EmulatedMediaType { get; set; }
79 |
80 | ///
81 | /// Do not wait for chromium network idle event before converting. (Gotenberg v8+)
82 | ///
83 | [MultiFormHeader(Constants.Gotenberg.Chromium.Shared.HtmlConvert.SkipNetworkIdleEvent)]
84 | public bool? SkipNetworkIdleEvent { get; set; }
85 |
86 | ///
87 | /// Convert the resulting PDF into the given PDF/A format.
88 | ///
89 | [MultiFormHeader(Constants.Gotenberg.Chromium.Shared.HtmlConvert.PdfFormat)]
90 | public ConversionPdfFormats? PdfFormat { get; set; }
91 |
92 | ///
93 | /// This tells gotenberg to enable Universal Access for the resulting PDF.
94 | ///
95 | [MultiFormHeader(Constants.Gotenberg.Chromium.Shared.HtmlConvert.PdfUa)]
96 | public bool? EnablePdfUa { get; set; }
97 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/Facets/RequestConfig.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Pages;
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Requests.Facets;
19 |
20 | ///
21 | /// All endpoints accept form fields for each property
22 | ///
23 | public sealed class RequestConfig : IConvertToHttpContent
24 | {
25 | #region ToHttpContent
26 |
27 | ///
28 | /// Converts the instance to a collection of http content items
29 | ///
30 | ///
31 | public IEnumerable ToHttpContent()
32 | {
33 | if (this.PageRanges?.Pages.Any() ?? false)
34 | yield return BuildRequestBase.CreateFormDataItem(
35 | this.PageRanges,
36 | Constants.Gotenberg.Chromium.Shared.PageProperties.PageRanges);
37 |
38 | if (this.ResultFileName.IsSet())
39 | yield return BuildRequestBase.CreateFormDataItem(
40 | this.ResultFileName,
41 | Constants.Gotenberg.SharedFormFieldNames.OutputFileName);
42 | }
43 |
44 | #endregion
45 |
46 | public void Validate()
47 | {
48 | this.Webhook?.Validate();
49 | }
50 |
51 | public IEnumerable<(string Name, string? Value)> GetHeaders()
52 | {
53 | if (this.Trace.IsSet())
54 | yield return (Constants.Gotenberg.All.Trace, this.Trace);
55 |
56 | foreach (var header in (this.Webhook?.GetHeaders()).IfNullEmpty()) yield return header;
57 | }
58 |
59 | #region Basic settings
60 |
61 | ///
62 | /// If provided, the API will return a pdf containing the pages in the specified range.
63 | ///
64 | ///
65 | /// The format is the same as the one from the print options of Google Chrome, e.g. 1-5,8,11-13.
66 | /// This may move...
67 | ///
68 | public PageRanges? PageRanges { get; set; }
69 |
70 | ///
71 | /// If provided, the API will return the resulting PDF file with the given filename. Otherwise a random filename is
72 | /// used.
73 | ///
74 | ///
75 | /// Attention: this feature does not work if the form field webHookURL is given.
76 | ///
77 | public string? ResultFileName { get; set; }
78 |
79 | ///
80 | /// If provided, the API will send the resulting PDF file in a POST request with the application/pdf Content-Type to
81 | /// given URL.
82 | /// Requests to the API complete before the conversions complete. For web hook configured requests,
83 | /// call FireWebhookAndForgetAsync on the client which returns nothing.
84 | ///
85 | /// All request types support web hooks
86 | public Webhook? Webhook { get; set; }
87 |
88 | ///
89 | /// If provided, the trace, or request ID, header will be added to the request.
90 | /// If you're using the webhook feature, it also adds the header to each request to your callbacks.
91 | ///
92 | public string? Trace { get; set; }
93 |
94 | #endregion
95 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/Facets/UrlExtras/ExtraUrlResourceItem.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 System.ComponentModel;
17 |
18 |
19 |
20 | namespace Gotenberg.Sharp.API.Client.Domain.Requests.Facets.UrlExtras;
21 |
22 | using urlConstants = Constants.Gotenberg.Chromium.Routes.Url;
23 |
24 | public class ExtraUrlResourceItem
25 | {
26 | const string LinkFieldName = urlConstants.ExtraLinkTags;
27 |
28 | const string ScriptFieldName = urlConstants.ExtraScriptTags;
29 |
30 | public ExtraUrlResourceItem(string url, ExtraUrlResourceType itemType)
31 | : this(new Uri(url), itemType)
32 | {
33 | }
34 |
35 | public ExtraUrlResourceItem(Uri url, ExtraUrlResourceType itemType)
36 | {
37 | Url = url ?? throw new ArgumentNullException(nameof(url));
38 | if (!url.IsAbsoluteUri)
39 | throw new InvalidOperationException("Url base href must be absolute");
40 | ItemType = itemType != default
41 | ? itemType
42 | : throw new InvalidEnumArgumentException(nameof(itemType));
43 | FormDataFieldName =
44 | itemType == ExtraUrlResourceType.LinkTag ? LinkFieldName : ScriptFieldName;
45 | }
46 |
47 |
48 | public Uri Url { get; }
49 |
50 |
51 | public ExtraUrlResourceType ItemType { get; }
52 |
53 | internal string FormDataFieldName { get; }
54 |
55 | internal string ToJson()
56 | {
57 | return ItemType == ExtraUrlResourceType.ScriptTag
58 | ? JsonConvert.SerializeObject(new { src = this.Url.ToString() })
59 | : JsonConvert.SerializeObject(new { href = this.Url.ToString() });
60 | }
61 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/Facets/UrlExtras/ExtraUrlResourceType.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 |
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Requests.Facets.UrlExtras;
19 |
20 | public enum ExtraUrlResourceType
21 | {
22 |
23 | None = 0,
24 |
25 | LinkTag = 1,
26 |
27 | ScriptTag = 2
28 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/Facets/UrlExtras/ExtraUrlResources.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 |
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Requests.Facets.UrlExtras;
19 |
20 | public class ExtraUrlResources : IConvertToHttpContent
21 | {
22 | public List Items { get; set; } = new();
23 |
24 | public IEnumerable ToHttpContent()
25 | {
26 | foreach (var g in Items.GroupBy(i => i.FormDataFieldName))
27 | {
28 | var groupValue = string.Join(", ", g.Select(gi => gi!.ToJson()));
29 | yield return BuildRequestBase.CreateFormDataItem($"[{groupValue}]", g.Key);
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/Facets/Webhook.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests.Facets;
17 |
18 | public sealed class Webhook
19 | {
20 | private Uri? _errorUrl;
21 |
22 | private Uri? _targetUrl;
23 |
24 | ///
25 | /// If set the Gotenberg API will send the resulting PDF file in a POST with
26 | /// the application-pdf content type to the given url. Requests to the API
27 | /// complete before the conversion is performed.
28 | ///
29 | ///
30 | /// When testing web hooks against a local container and a service
31 | /// running on localhost to receive the posts, use http://host.docker.internal
32 | /// Reference: https://docs.docker.com/desktop/windows/networking/#known-limitations-use-cases-and-workarounds
33 | ///
34 | public Uri? TargetUrl
35 | {
36 | get => this._targetUrl;
37 | set
38 | {
39 | if (value == null) throw new ArgumentNullException(nameof(value));
40 | if (!value.IsAbsoluteUri)
41 | throw new InvalidOperationException("WebHook url must be absolute");
42 |
43 | this._targetUrl = value;
44 | }
45 | }
46 |
47 | ///
48 | /// The HTTP method to use. Defaults to post if nothing is set.
49 | ///
50 | public string? HttpMethod { get; set; }
51 |
52 | ///
53 | /// The callback url to use if an error occurs
54 | ///
55 | public Uri? ErrorUrl
56 | {
57 | get => this._errorUrl;
58 | set
59 | {
60 | if (value == null) throw new ArgumentNullException(nameof(value));
61 | if (!value.IsAbsoluteUri)
62 | throw new InvalidOperationException("WebHook url must be absolute");
63 |
64 | this._errorUrl = value;
65 | }
66 | }
67 |
68 | ///
69 | /// The HTTP method to use when an error occurs. Defaults to post if nothing is set.
70 | ///
71 | public string? ErrorHttpMethod { get; set; }
72 |
73 | public ExtraHttpHeaders ExtraHttpHeaders { get; } = new();
74 |
75 | /*///
76 | /// By default, the API will wait 10 seconds before it considers the sending of the resulting PDF to be unsuccessful.
77 | /// On a per request basis, this property can override the container environment variable, DEFAULT_WEBHOOK_URL_TIMEOUT
78 | ///
79 | public float? Timeout { get; set; }*/
80 |
81 | public void Validate()
82 | {
83 | if (this.TargetUrl != null && this.ErrorUrl == null)
84 | throw new ArgumentNullException(
85 | nameof(this.ErrorUrl),
86 | "An webhook error url is required");
87 | }
88 |
89 | public bool IsConfigured()
90 | {
91 | return this.TargetUrl != null && this.ErrorUrl != null;
92 | }
93 |
94 | public IEnumerable<(string, string?)> GetHeaders()
95 | {
96 | if (!this.IsConfigured()) return [];
97 |
98 | var webHookHeaders = new List<(string Name, string? Value)>
99 | {
100 | (Constants.Gotenberg.Webhook.Url, this.TargetUrl?.ToString()),
101 | (Constants.Gotenberg.Webhook.HttpMethod, this.HttpMethod),
102 | (Constants.Gotenberg.Webhook.ErrorUrl, this.ErrorUrl?.ToString()),
103 | (Constants.Gotenberg.Webhook.ErrorHttpMethod, this.ErrorHttpMethod)
104 | };
105 |
106 | return webHookHeaders.Concat(this.ExtraHttpHeaders.GetHeaders())
107 | .Where(entry => !string.IsNullOrWhiteSpace(entry.Value));
108 | }
109 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/HtmlRequest.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests;
17 |
18 | ///
19 | /// Represents a Gotenberg Api conversion request for HTML or Markdown to pdf
20 | ///
21 | ///
22 | /// For Markdown conversions your Content.Body must contain HTML that references one or more markdown files
23 | /// using the Go template function 'toHTML' within the body element. Chrome uses the function to convert the contents
24 | /// of a given markdown file to HTML.
25 | /// See example here: https://gotenberg.dev/docs/modules/chromium#markdown
26 | ///
27 | public sealed class HtmlRequest(bool containsMarkdown) : ChromeRequest
28 | {
29 | public HtmlRequest()
30 | : this(false)
31 | {
32 | }
33 |
34 | protected override string ApiPath =>
35 | this.ContainsMarkdown
36 | ? Constants.Gotenberg.Chromium.ApiPaths.ConvertMarkdown
37 | : Constants.Gotenberg.Chromium.ApiPaths.ConvertHtml;
38 |
39 | public bool ContainsMarkdown { get; internal set; } = containsMarkdown;
40 |
41 | public FullDocument Content { get; internal set; } = new();
42 |
43 | ///
44 | /// Transforms the instance to a list of HttpContent items
45 | ///
46 | protected override IEnumerable ToHttpContent()
47 | {
48 | if (this.Content.Body == null)
49 | throw new InvalidOperationException("You need to Add at least a body");
50 |
51 | return base.ToHttpContent().Concat(this.Content.IfNullEmptyContent()).Concat(this.Assets.IfNullEmptyContent());
52 | }
53 |
54 | protected override void Validate()
55 | {
56 | if (this.Content?.Body == null)
57 | throw new InvalidOperationException("Request.Content or Content.Body is null");
58 |
59 | base.Validate();
60 | }
61 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/IConvertToHttpContent.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests
17 | {
18 | public interface IConvertToHttpContent
19 | {
20 | IEnumerable ToHttpContent();
21 | }
22 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/MergeOfficeConstants.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests;
17 |
18 | public static class MergeOfficeConstants
19 | {
20 | ///
21 | /// Docs are here: https://gotenberg.dev/docs/routes#office-documents-into-pdfs-route
22 | /// Last updated 2/12/2025
23 | ///
24 | public static readonly string[] AllowedExtensions =
25 | [
26 | ".123", ".602", ".abw", ".bib", ".bmp", ".cdr", ".cgm", ".cmx", ".csv", ".cwk",
27 | ".dbf", ".dif", ".doc", ".docm", ".docx", ".dot", ".dotm", ".dotx", ".dxf", ".emf",
28 | ".eps", ".epub", ".fodg", ".fodp", ".fods", ".fodt", ".fopd", ".gif", ".htm", ".html",
29 | ".hwp", ".jpeg", ".jpg", ".key", ".ltx", ".lwp", ".mcw", ".met", ".mml", ".mw",
30 | ".numbers", ".odd", ".odg", ".odm", ".odp", ".ods", ".odt", ".otg", ".oth", ".otp",
31 | ".ots", ".ott", ".pages", ".pbm", ".pcd", ".pct", ".pcx", ".pdb", ".pdf", ".pgm",
32 | ".png", ".pot", ".potm", ".potx", ".ppm", ".pps", ".ppt", ".pptm", ".pptx", ".psd",
33 | ".psw", ".pub", ".pwp", ".pxl", ".ras", ".rtf", ".sda", ".sdc", ".sdd", ".sdp",
34 | ".sdw", ".sgl", ".slk", ".smf", ".stc", ".std", ".sti", ".stw", ".svg", ".svm",
35 | ".swf", ".sxc", ".sxd", ".sxg", ".sxi", ".sxm", ".sxw", ".tga", ".tif", ".tiff",
36 | ".txt", ".uof", ".uop", ".uos", ".uot", ".vdx", ".vor", ".vsd", ".vsdm", ".vsdx",
37 | ".wb2", ".wk1", ".wks", ".wmf", ".wpd", ".wpg", ".wps", ".xbm", ".xhtml", ".xls",
38 | ".xlsb", ".xlsm", ".xlsx", ".xlt", ".xltm", ".xltx", ".xlw", ".xml", ".xpm", ".zabw"
39 | ];
40 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/MergeOfficeRequest.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.ContentTypes;
17 | using Gotenberg.Sharp.API.Client.Infrastructure.ContentTypes;
18 |
19 | namespace Gotenberg.Sharp.API.Client.Domain.Requests;
20 |
21 | ///
22 | /// Libre off ice has a convert route which can perform merges
23 | ///
24 | public class MergeOfficeRequest : PdfRequestBase
25 | {
26 | private readonly IResolveContentType _resolver = new ResolveContentTypeImplementation();
27 |
28 | protected override string ApiPath => Constants.Gotenberg.LibreOffice.ApiPaths.MergeOffice;
29 |
30 | public bool PrintAsLandscape { get; set; }
31 |
32 | ///
33 | /// If provided, the API will return a pdf containing the pages in the specified range.
34 | ///
35 | ///
36 | /// The format is the same as the one from the print options of Google Chrome, e.g. 1-5,8,11-13.
37 | /// This may move...
38 | ///
39 | public string? PageRanges { get; set; }
40 |
41 | protected override IEnumerable ToHttpContent()
42 | {
43 | var validItems = (this.Assets?.FindValidOfficeMergeItems(this._resolver)).IfNullEmpty().ToList();
44 |
45 | if (validItems.Count < 1)
46 | throw new ArgumentException(
47 | $"No Valid Office Documents to Convert. Valid extensions: {string.Join(
48 | ", ",
49 | MergeOfficeConstants.AllowedExtensions)}");
50 |
51 | yield return CreateFormDataItem("true", Constants.Gotenberg.LibreOffice.Routes.Convert.Merge);
52 |
53 | foreach (var item in validItems.ToHttpContent())
54 | yield return item;
55 |
56 | foreach (var item in this.Config.IfNullEmptyContent())
57 | yield return item;
58 |
59 | if (this.PrintAsLandscape)
60 | yield return CreateFormDataItem("true", Constants.Gotenberg.LibreOffice.Routes.Convert.Landscape);
61 |
62 | if (this.PageRanges.IsSet())
63 | yield return CreateFormDataItem(this.PageRanges, Constants.Gotenberg.LibreOffice.Routes.Convert.PageRanges);
64 |
65 | foreach (var content in base.ToHttpContent()) yield return content;
66 | }
67 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/MergeRequest.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests
17 | {
18 | public sealed class MergeRequest : PdfRequestBase
19 | {
20 | protected override string ApiPath
21 | => Constants.Gotenberg.PdfEngines.ApiPaths.MergePdf;
22 |
23 | protected override IEnumerable ToHttpContent()
24 | {
25 | foreach (var ci in base.ToHttpContent())
26 | yield return ci;
27 |
28 | foreach (var ci in Config.IfNullEmptyContent())
29 | yield return ci;
30 |
31 | foreach (var item in this.Assets.ToAlphabeticalOrderByIndex()
32 | .Where(item => item.IsValid()))
33 | {
34 | var contentItem = item.Value.ToHttpContentItem();
35 |
36 | contentItem.Headers.ContentDisposition =
37 | new ContentDispositionHeaderValue(
38 | Constants.HttpContent.Disposition.Types.FormData)
39 | {
40 | Name = Constants.Gotenberg.SharedFormFieldNames.Files,
41 | FileName = item.Key
42 | };
43 |
44 | contentItem.Headers.ContentType =
45 | new MediaTypeHeaderValue(Constants.HttpContent.MediaTypes.ApplicationPdf);
46 |
47 | yield return contentItem;
48 | }
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/PdfConversionRequest.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests;
17 |
18 | public class PdfConversionRequest : PdfRequestBase
19 | {
20 | protected override string ApiPath => Constants.Gotenberg.PdfEngines.ApiPaths.ConvertPdf;
21 |
22 | protected override IEnumerable ToHttpContent()
23 | {
24 | foreach (var content in base.ToHttpContent())
25 | {
26 | yield return content;
27 | }
28 |
29 | foreach (var item in this.Assets.IfNullEmpty().Where(item => item.IsValid()))
30 | {
31 | var contentItem = item.Value.ToHttpContentItem();
32 |
33 | contentItem.Headers.ContentDisposition =
34 | new ContentDispositionHeaderValue(Constants.HttpContent.Disposition.Types.FormData)
35 | {
36 | Name = Constants.Gotenberg.SharedFormFieldNames.Files, FileName = item.Key
37 | };
38 |
39 | contentItem.Headers.ContentType = new MediaTypeHeaderValue(Constants.HttpContent.MediaTypes.ApplicationPdf);
40 |
41 | yield return contentItem;
42 | }
43 |
44 | foreach (var item in Config.IfNullEmptyContent().Concat(this.Assets.IfNullEmptyContent()))
45 | {
46 | yield return item;
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/PdfRequestBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests
17 | {
18 | public abstract class PdfRequestBase : BuildRequestBase
19 | {
20 | public LibrePdfFormats? PdfFormat { get; set; }
21 |
22 | ///
23 | /// This tells gotenberg to enable Universal Access for the resulting PDF.
24 | ///
25 | public bool? EnablePdfUa { get; set; }
26 |
27 | ///
28 | /// Flatten the resulting PDF.
29 | ///
30 | public bool? EnableFlatten { get; set; }
31 |
32 | protected HttpContent? PdfFlattenContent()
33 | {
34 | if (this.EnableFlatten is null)
35 | {
36 | return null;
37 | }
38 |
39 | return CreateFormDataItem("true", Constants.Gotenberg.LibreOffice.Routes.Convert.Flatten);
40 | }
41 |
42 | protected HttpContent? PdfUaContent()
43 | {
44 | if (this.EnablePdfUa is null)
45 | {
46 | return null;
47 | }
48 |
49 | return CreateFormDataItem("true", Constants.Gotenberg.LibreOffice.Routes.Convert.PdfUa);
50 | }
51 |
52 | protected HttpContent? PdfFormatContent()
53 | {
54 | if (this.PdfFormat is null or LibrePdfFormats.None)
55 | {
56 | return null;
57 | }
58 |
59 | return CreateFormDataItem(
60 | this.PdfFormat.Value.ToFormDataValue(),
61 | Constants.Gotenberg.LibreOffice.Routes.Convert.PdfFormat);
62 | }
63 |
64 | protected override IEnumerable ToHttpContent()
65 | {
66 | HttpContent?[] items = [this.PdfFormatContent(), this.PdfUaContent(), this.PdfFlattenContent()];
67 |
68 | foreach (var item in items.WhereNotNull())
69 | {
70 | yield return item;
71 | }
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/lib/Domain/Requests/UrlRequest.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Requests.Facets.UrlExtras;
17 |
18 | namespace Gotenberg.Sharp.API.Client.Domain.Requests;
19 |
20 | public sealed class UrlRequest : ChromeRequest
21 | {
22 | protected override string ApiPath => Constants.Gotenberg.Chromium.ApiPaths.ConvertUrl;
23 |
24 | public Uri? Url { get; set; }
25 |
26 | ///
27 | /// Requires top/bottom margin set to appear
28 | ///
29 | public HeaderFooterDocument? Content { get; set; }
30 |
31 | public ExtraUrlResources? ExtraResources { get; set; }
32 |
33 | ///
34 | /// Convert the resulting PDF into the given PDF/A format.
35 | ///
36 | public ConversionPdfFormats? PdfFormat { get; set; }
37 |
38 | ///
39 | /// This tells gotenberg to enable Universal Access for the resulting PDF.
40 | ///
41 | public bool? EnablePdfUa { get; set; }
42 |
43 | HttpContent? PdfUaContent()
44 | {
45 | if (this.EnablePdfUa is null)
46 | {
47 | return null;
48 | }
49 |
50 | return CreateFormDataItem("true", Constants.Gotenberg.Chromium.Shared.UrlConvert.PdfUa);
51 | }
52 |
53 | HttpContent? PdfFormatContent()
54 | {
55 | if (this.PdfFormat is null or ConversionPdfFormats.None)
56 | {
57 | return null;
58 | }
59 |
60 | return CreateFormDataItem(
61 | this.PdfFormat.Value.ToFormDataValue(),
62 | Constants.Gotenberg.Chromium.Shared.UrlConvert.PdfFormat);
63 | }
64 |
65 | protected override IEnumerable ToHttpContent()
66 | {
67 | if (this.Url == null) throw new InvalidOperationException("Url is null");
68 | if (!this.Url.IsAbsoluteUri)
69 | throw new InvalidOperationException("Url.IsAbsoluteUri equals false");
70 |
71 | HttpContent?[] items = [this.PdfFormatContent(), this.PdfUaContent()];
72 |
73 | return base.ToHttpContent()
74 | .Concat(Content.IfNullEmptyContent())
75 | .Concat(ExtraResources.IfNullEmptyContent())
76 | .Concat(Assets.IfNullEmptyContent())
77 | .Concat(items)
78 | .Concat(
79 | [
80 | CreateFormDataItem(this.Url, Constants.Gotenberg.Chromium.Routes.Url.RemoteUrl)
81 | ])
82 | .WhereNotNull();
83 | }
84 |
85 | protected override void Validate()
86 | {
87 | if (this.Url == null) throw new InvalidOperationException("Request.Url is null");
88 |
89 | base.Validate();
90 | }
91 | }
--------------------------------------------------------------------------------
/lib/Domain/Settings/GotenbergSharpClientOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Settings;
17 |
18 | public class GotenbergSharpClientOptions
19 | {
20 | public TimeSpan TimeOut { get; set; } = TimeSpan.FromMinutes(3);
21 |
22 | public Uri ServiceUrl { get; set; } = new Uri("http://localhost:3000");
23 |
24 | public Uri HealthCheckUrl { get; set; } = new Uri("http://localhost:3000/health");
25 |
26 | public RetryOptions RetryPolicy { get; set; } = new RetryOptions();
27 | }
--------------------------------------------------------------------------------
/lib/Domain/Settings/RetryOptions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Settings;
17 |
18 | public class RetryOptions
19 | {
20 | public bool Enabled { get; set; }
21 |
22 | public int RetryCount { get; set; } = 3;
23 |
24 | ///
25 | /// Configures the sleep duration provider with an exponential wait time between retries.
26 | /// E.G. sleepDurationProvider: retryCount => TimeSpan.FromSeconds(Math.Pow(retryOps.BackoffPower, retryCount))
27 | ///
28 |
29 | public double BackoffPower { get; set; } = 1.5;
30 |
31 | public bool LoggingEnabled { get; set; } = true;
32 | }
--------------------------------------------------------------------------------
/lib/Extensions/DictionaryExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.ContentTypes;
17 |
18 | namespace Gotenberg.Sharp.API.Client.Extensions;
19 |
20 | public static class DictionaryExtensions
21 | {
22 | private static readonly StringComparer _comparer = StringComparer.InvariantCultureIgnoreCase;
23 |
24 | ///
25 | /// Ensures the merged documents appear in the order each was added.
26 | /// Gotenberg merges files in alphabetical order via the key/file name.
27 | /// https://gotenberg.dev/docs/modules/pdf-engines#merge
28 | ///
29 | ///
30 | ///
31 | /// Note: For merges only. Embedded assets for html docs have
32 | /// key values with whatever extension the html references: .md, .css, .jpg, etc
33 | ///
34 | public static AssetDictionary ToAlphabeticalOrderByIndex(this AssetDictionary? unordered)
35 | {
36 | var ordered = unordered.IfNullEmpty()
37 | .Select(
38 | (item, i) =>
39 | KeyValuePair.Create(
40 | i.ToAlphabeticallySortableFileName(new FileInfo(item.Key).Extension),
41 | item.Value));
42 |
43 | return new AssetDictionary().AddRangeFluently(ordered);
44 | }
45 |
46 | internal static Dictionary IfNullEmpty(
47 | this Dictionary? instance)
48 | where TKey : notnull
49 | {
50 | return instance ?? new Dictionary();
51 | }
52 |
53 | internal static IEnumerable FindValidOfficeMergeItems(
54 | this AssetDictionary assets,
55 | IResolveContentType resolver)
56 | {
57 | return assets.RemoveInvalidOfficeDocs()
58 | .ToAlphabeticalOrderByIndex()
59 | .Where(item => item.IsValid())
60 | .Select(item => new ValidOfficeMergeItem(item, resolver.GetContentType(item.Key)))
61 | .Where(item => item.MediaType.IsSet());
62 | }
63 |
64 | private static AssetDictionary RemoveInvalidOfficeDocs(this AssetDictionary unfiltered)
65 | {
66 | var filtered = unfiltered.IfNullEmpty()
67 | .Where(
68 | asset => MergeOfficeConstants.AllowedExtensions.Contains(
69 | new FileInfo(asset.Key).Extension,
70 | _comparer));
71 |
72 | return new AssetDictionary().AddRangeFluently(filtered);
73 | }
74 | }
--------------------------------------------------------------------------------
/lib/Extensions/DimensionHelpers.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 System.ComponentModel;
17 | using Gotenberg.Sharp.API.Client.Domain.Dimensions;
18 |
19 | namespace Gotenberg.Sharp.API.Client.Extensions
20 | {
21 | public static class DimensionHelpers
22 | {
23 | private static readonly IEnumerable<(Margins MarginType, (Dimension Left, Dimension Right, Dimension Top, Dimension Bottom) Value)> MarginSizer =
24 | [
25 | (Margins.None, (Left: 0.0, Right: 0.0, Top: 0.0, Bottom: 0.0)),
26 | (Margins.Default, (Left: 0.39, Right: 0.39, Top: 0.39, Bottom: 0.39)),
27 | (Margins.Normal, (Left: 1.0, Right: 1.0, Top: 1.0, Bottom: 1.0)),
28 | (Margins.Large, (Left: 2.0, Right: 2.0, Top: 2.0, Bottom: 2.0))
29 | ];
30 |
31 | private static readonly IEnumerable<(PaperSizes Size, (Dimension Width, Dimension Height) Value)> PaperSizer =
32 | [
33 | (PaperSizes.A3, (Width: 11.7, Height: 16.5)),
34 | (PaperSizes.A4, (Width: 8.27, Height: 11.7)),
35 | (PaperSizes.A5, (Width: 5.8, Height: 8.2)),
36 | (PaperSizes.A6, (Width: 4.1, Height: 5.8)),
37 | (PaperSizes.Letter, (Width: 8.5, Height: 11.0)),
38 | (PaperSizes.Legal, (Width: 8.5, Height: 14.0)),
39 | (PaperSizes.Tabloid, (Width: 11.0, Height: 17.0)),
40 | (PaperSizes.D, (Width: 22.0, Height: 34.0)),
41 | (PaperSizes.E, (Width: 34.0, Height: 44.0))
42 | ];
43 |
44 | internal static (Dimension Width, Dimension Height) ToSelectedSize(this PaperSizes selectedSize)
45 | {
46 | if (!Enum.IsDefined(typeof(PaperSizes), selectedSize))
47 | throw new InvalidEnumArgumentException(
48 | nameof(selectedSize),
49 | (int)selectedSize,
50 | typeof(PaperSizes));
51 |
52 | if (selectedSize == default)
53 | throw new InvalidOperationException(nameof(selectedSize));
54 |
55 | return PaperSizer.First(s => s.Size == selectedSize).Value;
56 | }
57 |
58 | internal static (Dimension Left, Dimension Right, Dimension Top, Dimension Bottom) ToSelectedMargins(
59 | this Margins selected)
60 | {
61 | if (!Enum.IsDefined(typeof(Margins), selected))
62 | throw new InvalidEnumArgumentException(
63 | nameof(selected),
64 | (int)selected,
65 | typeof(PaperSizes));
66 |
67 | return MarginSizer.First(m => m.MarginType == selected).Value;
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/lib/Extensions/EnumExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 System.ComponentModel;
17 | using System.Globalization;
18 | using System.Reflection;
19 |
20 | namespace Gotenberg.Sharp.API.Client.Extensions;
21 |
22 | internal static class EnumExtensions
23 | {
24 | internal static string ToFormDataValue(this LibrePdfFormats format)
25 | {
26 | return format == default ? "None" : $"PDF/A-{format.ToString().Substring(1, 2)}";
27 | }
28 |
29 | internal static string ToFormDataValue(this ConversionPdfFormats format)
30 | {
31 | return format == default ? "None" : $"PDF/A-{format.ToString().Substring(1, 2)}";
32 | }
33 |
34 | public static string GetDescription(this Enum value)
35 | {
36 | FieldInfo field = value.GetType().GetField(value.ToString())!;
37 | DescriptionAttribute? attribute = field.GetCustomAttribute();
38 |
39 | return attribute?.Description ?? value.ToString();
40 | }
41 | }
--------------------------------------------------------------------------------
/lib/Extensions/EnumerableExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 |
17 |
18 | namespace Gotenberg.Sharp.API.Client.Extensions;
19 |
20 | internal static class EnumerableExtensions
21 | {
22 | internal static IEnumerable WhereNotNull(this IEnumerable? items)
23 | where T : class
24 | {
25 | return items.IfNullEmpty().Where(item => item != null)!;
26 | }
27 |
28 | internal static IEnumerable IfNullEmpty(this IEnumerable? items)
29 | {
30 | return items ?? Enumerable.Empty();
31 | }
32 | }
--------------------------------------------------------------------------------
/lib/Extensions/HttpRequestExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Extensions;
17 |
18 | internal static class HttpRequestExtensions
19 | {
20 | private const string TimeoutPropertyKey = "RequestTimeout";
21 |
22 | ///
23 | /// Sets the timeout.
24 | ///
25 | /// The request.
26 | /// The timeout.
27 | /// request
28 | internal static void SetTimeout(this HttpRequestMessage request, TimeSpan? timeout)
29 | {
30 | if (request == null) throw new ArgumentNullException(nameof(request));
31 |
32 | #if NET5_0_OR_GREATER
33 | request.Options.TryAdd(TimeoutPropertyKey, timeout);
34 | #else
35 | request.Properties[TimeoutPropertyKey] = timeout;
36 | #endif
37 | }
38 |
39 | ///
40 | /// Gets the timeout.
41 | ///
42 | /// The request.
43 | ///
44 | internal static TimeSpan? GetTimeout(this HttpRequestMessage request)
45 | {
46 | if (request == null) throw new ArgumentNullException(nameof(request));
47 |
48 | #if NET5_0_OR_GREATER
49 | if (request.Options.TryGetValue(
50 | new HttpRequestOptionsKey(TimeoutPropertyKey),
51 | out var timeout)) return timeout;
52 | #else
53 | if (request.Properties.TryGetValue(TimeoutPropertyKey, out var value)
54 | && value is TimeSpan timeout) return timeout;
55 | #endif
56 |
57 | return null;
58 | }
59 | }
--------------------------------------------------------------------------------
/lib/Extensions/IntExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Extensions;
17 |
18 | public static class IntExtensions
19 | {
20 | private const int AlphabetLength = 26;
21 |
22 | private static readonly char[] Alphabet = Enumerable.Range('A', 'Z' - 'A' + 1)
23 | .Select(c => (char)c)
24 | .ToArray();
25 |
26 | ///
27 | /// Returns a-z for the first 26 (0-25); then za - zz for the next;
28 | /// zza - zzz, etc. with the specified extension appended to the end
29 | ///
30 | ///
31 | /// https://gotenberg.dev/docs/modules/pdf-engines#merge
32 | ///
33 | ///
34 | ///
35 | ///
36 | public static string ToAlphabeticallySortableFileName(this int sortNumber, string extension)
37 | {
38 | if (sortNumber < 0) throw new ArgumentOutOfRangeException(nameof(sortNumber));
39 | if (extension.IsNotSet())
40 | throw new ArgumentException("extension is either null or empty");
41 |
42 | return
43 | $"{new string('Z', sortNumber / AlphabetLength)}{Alphabet[sortNumber % AlphabetLength]}{extension}";
44 | }
45 | }
--------------------------------------------------------------------------------
/lib/Extensions/KeyValuePairExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Extensions;
17 |
18 | internal static class KeyValuePairExtensions
19 | {
20 | internal static bool IsValid(this KeyValuePair pair)
21 | where TValue : class
22 | {
23 | return pair.Key.IsSet() && pair.Value != default(TValue);
24 | }
25 | }
--------------------------------------------------------------------------------
/lib/Extensions/RequestInterfaceExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Extensions
17 | {
18 | public static class RequestInterfaceExtensions
19 | {
20 | private const string BoundaryPrefix = Constants.HttpContent.MultipartData.BoundaryPrefix;
21 |
22 | public static IEnumerable IfNullEmptyContent(
23 | this IConvertToHttpContent? converter)
24 | {
25 | return converter?.ToHttpContent() ?? Enumerable.Empty();
26 | }
27 |
28 | ///
29 | /// A helper method for the linqPad scripts
30 | ///
31 | ///
32 | ///
33 | ///
34 | public static IEnumerable ToDumpFriendlyFormat(
35 | this IEnumerable items,
36 | bool includeNonText = false)
37 | {
38 | return items.Select(
39 | c =>
40 | {
41 | var includeContent = includeNonText ||
42 | (c.Headers.ContentType?.ToString().StartsWith("text"))
43 | .GetValueOrDefault();
44 |
45 | return new
46 | {
47 | Headers = new
48 | {
49 | ContentType = string.Join(" | ", c.Headers.ContentType),
50 | Disposition = string.Join(" | ", c.Headers.ContentDisposition)
51 | },
52 | Content = includeContent
53 | ? c.ReadAsStringAsync().Result
54 | : "-its not text-"
55 | };
56 | });
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/lib/Extensions/StringExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Extensions;
17 |
18 | internal static class StringExtensions
19 | {
20 | internal static bool IsSet(this string? value)
21 | {
22 | return !value.IsNotSet();
23 | }
24 |
25 | internal static bool IsNotSet(this string? value)
26 | {
27 | return string.IsNullOrWhiteSpace(value);
28 | }
29 | }
--------------------------------------------------------------------------------
/lib/Extensions/TypedClientServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 System.Net;
17 |
18 | using Gotenberg.Sharp.API.Client.Domain.Settings;
19 | using Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
20 |
21 |
22 |
23 | using Microsoft.Extensions.DependencyInjection;
24 | using Microsoft.Extensions.Options;
25 |
26 | namespace Gotenberg.Sharp.API.Client.Extensions;
27 |
28 | public static class TypedClientServiceCollectionExtensions
29 | {
30 | public static IHttpClientBuilder AddGotenbergSharpClient(
31 | this IServiceCollection services)
32 | {
33 | if (services == null) throw new ArgumentNullException(nameof(services));
34 |
35 | return services.AddGotenbergSharpClient(
36 | (sp, client) =>
37 | {
38 | var ops = GetOptions(sp);
39 | client.Timeout = ops.TimeOut;
40 | client.BaseAddress = ops.ServiceUrl;
41 | });
42 | }
43 |
44 |
45 | public static IHttpClientBuilder AddGotenbergSharpClient(
46 | this IServiceCollection services,
47 | Action configureClient)
48 | {
49 | if (configureClient == null) throw new ArgumentNullException(nameof(configureClient));
50 |
51 | return services
52 | .AddHttpClient(nameof(GotenbergSharpClient), configureClient)
53 | .AddTypedClient()
54 | .ConfigurePrimaryHttpMessageHandler(
55 | () => new TimeoutHandler(
56 | new HttpClientHandler
57 | {
58 | AutomaticDecompression = DecompressionMethods.GZip
59 | | DecompressionMethods.Deflate
60 | }))
61 | .AddPolicyHandler(PolicyFactory.CreatePolicyFromSettings)
62 | .SetHandlerLifetime(TimeSpan.FromMinutes(6));
63 | }
64 |
65 | private static GotenbergSharpClientOptions GetOptions(IServiceProvider sp)
66 | {
67 | return sp.GetRequiredService>().Value;
68 | }
69 | }
--------------------------------------------------------------------------------
/lib/Extensions/ValidOfficeMergeItemExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Extensions;
17 |
18 | internal static class ValidOfficeMergeItemExtensions
19 | {
20 | internal static IEnumerable ToHttpContent(
21 | this IEnumerable validItems)
22 | {
23 | foreach (var item in validItems)
24 | {
25 | var contentItem = item.Asset.Value.ToHttpContentItem();
26 |
27 | contentItem.Headers.ContentDisposition =
28 | new ContentDispositionHeaderValue(Constants.HttpContent.Disposition.Types.FormData)
29 | {
30 | Name = Constants.Gotenberg.SharedFormFieldNames.Files,
31 | FileName = item.Asset.Key
32 | };
33 |
34 | contentItem.Headers.ContentType = new MediaTypeHeaderValue(item.MediaType);
35 |
36 | yield return contentItem;
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/lib/GlobalSuppressions.cs:
--------------------------------------------------------------------------------
1 | // This file is used by Code Analysis to maintain SuppressMessage
2 | // attributes that are applied to this project.
3 | // Project-level suppressions either have no target or are given
4 | // a specific target and scoped to a namespace, type, member, etc.
5 |
6 | using System.Diagnostics.CodeAnalysis;
7 |
8 | [assembly: SuppressMessage("Style", "IDE0042:Deconstruct variable declaration")]
9 | [assembly: SuppressMessage("Roslynator", "RCS1080:Use 'Count/Length' property instead of 'Any' method.")]
10 | [assembly: SuppressMessage("ReSharper", "UnusedMember.Global")]
--------------------------------------------------------------------------------
/lib/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 | global using System.Net.Http.Headers;
17 |
18 | global using Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
19 | global using Gotenberg.Sharp.API.Client.Domain.Requests;
20 | global using Gotenberg.Sharp.API.Client.Domain.Requests.Facets;
21 | global using Gotenberg.Sharp.API.Client.Extensions;
22 | global using Gotenberg.Sharp.API.Client.Infrastructure;
23 |
24 | global using Newtonsoft.Json;
--------------------------------------------------------------------------------
/lib/Gotenberg.Sharp.Api.Client.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0;net7.0;net6.0;net5.0;netstandard2.1;netstandard2.0
5 | latest
6 | Gotenberg.Sharp.API.Client
7 | Gotenberg.Sharp.API.Client
8 | enable
9 | enable
10 |
11 |
12 |
13 | $(NoWarn);NU1605;1701;1702;1705;1591
14 |
15 |
16 |
17 | 2.8.0
18 | Gotenberg pdf C# ApiClient unoconv
19 |
20 | C# API client for interacting with the Gotenberg v7 & v8 micro-service's API, a docker-powered stateless API for converting & merging HTML, Markdown and Office documents to PDF. The client supports a configurable Polly retry policy with exponential back-off for handling transient exceptions.
21 |
22 | True
23 |
24 | v2.8 - Improving handling of PDF formatting and added flatten support.
25 | v2.7 - Fixes issue with "Inches".
26 | v2.6 - Updated office Extensions. Added document metadata support. Add Dimension.FromUnit() support for dimensional values.
27 | v2.5 - Renamed "Dimentions" to "PageProperties". Added support for 'GenerateDocumentOutline' and 'OmitBackground.'
28 | v2.4 - Updated dependencies. Removed Annotations. Add support for PDF/UA form field. Thank you for the PR @lennartb-!
29 | v2.3 - Added Convert Page 'ExportFormFields' flag support (Gotenberg v8.3+ Only). Added .NET 8 target.
30 | v2.2 - Added 'SkipNetworkIdle' flag support (Gotenberg v8+ Only). Thank you for the PR @guillaumeduhr! Upgraded nugets to latest. Added .NET 7.0 support.
31 | v2.1 - Added Trace Support. Fixed extra webhook header support.
32 | v2.0 - Upgraded to support Gotenberg v7 -- this version no longer works with Gotenberg v6.
33 |
34 | http://www.apache.org/licenses/LICENSE-2.0
35 | https://github.com/ChangemakerStudios/GotenbergSharpApiClient
36 | https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/master/lib/Resources/gotenbergSharpClient.PNG
37 | gotenbergSharpClient-large.PNG
38 | https://github.com/ChangemakerStudios/GotenbergSharpApiClient
39 | README.md
40 |
41 |
42 |
43 | true
44 | true
45 | true
46 | snupkg
47 | true
48 |
49 |
50 |
51 |
52 |
53 | all
54 | runtime; build; native; contentfiles; analyzers; buildtransitive
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/lib/GotenbergSharpApiClient.DotSettings.csproj:
--------------------------------------------------------------------------------
1 |
2 | True
3 | True
4 |
5 | True
6 | True
7 | True
8 | True
9 |
10 | True
11 | True
12 | True
--------------------------------------------------------------------------------
/lib/GotenbergSharpApiClient.DotSettings.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GotenbergSharpApiClient.DotSettings", "GotenbergSharpApiClient.DotSettings.csproj", "{283481A8-6AA9-46A8-A953-7554BFEDB8AA}"
5 | EndProject
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|Any CPU = Debug|Any CPU
9 | Release|Any CPU = Release|Any CPU
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {283481A8-6AA9-46A8-A953-7554BFEDB8AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
13 | {283481A8-6AA9-46A8-A953-7554BFEDB8AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
14 | {283481A8-6AA9-46A8-A953-7554BFEDB8AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
15 | {283481A8-6AA9-46A8-A953-7554BFEDB8AA}.Release|Any CPU.Build.0 = Release|Any CPU
16 | EndGlobalSection
17 | EndGlobal
18 |
--------------------------------------------------------------------------------
/lib/Infrastructure/ContentTypes/ResolveContentTypeImplementation.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.ContentTypes;
17 |
18 | using MimeMapping;
19 |
20 | namespace Gotenberg.Sharp.API.Client.Infrastructure.ContentTypes;
21 |
22 | public class ResolveContentTypeImplementation : IResolveContentType
23 | {
24 | public string GetContentType(
25 | string fileName,
26 | string defaultContentType = "application/octet-stream")
27 | {
28 | if (fileName.IsNotSet())
29 | throw new ArgumentException("file name is either null or empty");
30 |
31 | return MimeUtility.GetMimeMapping(fileName) ?? defaultContentType;
32 | }
33 | }
--------------------------------------------------------------------------------
/lib/Infrastructure/GotenbergApiException.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 System.Net;
17 |
18 | using Gotenberg.Sharp.API.Client.Domain.Requests.ApiRequests;
19 |
20 | // ReSharper disable All CA1032
21 | // ReSharper disable All CA1822
22 | namespace Gotenberg.Sharp.API.Client.Infrastructure
23 | {
24 | ///
25 | public sealed class GotenbergApiException : Exception
26 | {
27 | readonly IApiRequest _request;
28 |
29 | readonly HttpResponseMessage _response;
30 |
31 | public GotenbergApiException(
32 | string message,
33 | IApiRequest request,
34 | HttpResponseMessage response)
35 | : base(message)
36 | {
37 | this._request = request;
38 | this._response = response;
39 | this.StatusCode = _response.StatusCode;
40 | this.RequestUri = _response.RequestMessage?.RequestUri;
41 | this.ReasonPhrase = _response.ReasonPhrase;
42 | }
43 |
44 | public HttpStatusCode StatusCode { get; }
45 |
46 | public Uri? RequestUri { get; }
47 |
48 | public string? ReasonPhrase { get; }
49 |
50 | public static GotenbergApiException Create(
51 | IApiRequest request,
52 | HttpResponseMessage response)
53 | {
54 | var message = response.Content.ReadAsStringAsync().Result;
55 | return new GotenbergApiException(message, request, response);
56 | }
57 |
58 | public string ToVerboseJson(
59 | bool includeGotenbergResponse = true,
60 | bool includeRequestContent = true,
61 | bool indentJson = false)
62 | {
63 | using (_response)
64 | {
65 | IEnumerable? clientRequestFormContent = null;
66 |
67 | if (includeRequestContent
68 | && this._request is IConvertToHttpContent convertToHttpContent)
69 | {
70 | clientRequestFormContent = convertToHttpContent.IfNullEmptyContent()
71 | .ToDumpFriendlyFormat(false);
72 | }
73 |
74 | return JsonConvert.SerializeObject(
75 | new
76 | {
77 | GotenbergMessage = Message,
78 | GotenbergResponseReceived = includeGotenbergResponse ? _response : null,
79 | ClientRequestSent = _request,
80 | ClientRequestFormContent = clientRequestFormContent
81 | },
82 | new JsonSerializerSettings
83 | {
84 | NullValueHandling = NullValueHandling.Ignore,
85 | Formatting = indentJson ? Formatting.Indented : Formatting.None
86 | });
87 | }
88 | }
89 | }
90 | }
--------------------------------------------------------------------------------
/lib/Infrastructure/MultiFormHeaderAttribute.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Infrastructure;
17 |
18 | [AttributeUsage(AttributeTargets.Property)]
19 | public sealed class MultiFormHeaderAttribute : Attribute
20 | {
21 | ///
22 | /// Initializes a new instance of the class.
23 | ///
24 | /// The content disposition.
25 | /// The name.
26 | /// Name of the file.
27 | /// The media type
28 | public MultiFormHeaderAttribute(
29 | string name = Constants.Gotenberg.SharedFormFieldNames.Files,
30 | string? fileName = null,
31 | string contentDisposition = Constants.HttpContent.Disposition.Types.FormData,
32 | string mediaType = Constants.HttpContent.MediaTypes.TextHtml)
33 | {
34 | this.Name = name;
35 | this.FileName = fileName;
36 | this.ContentDisposition = contentDisposition;
37 | this.MediaType = mediaType;
38 | }
39 |
40 | ///
41 | /// Gets or sets the content disposition.
42 | ///
43 | ///
44 | /// The content disposition.
45 | ///
46 | public string ContentDisposition { get; }
47 |
48 | ///
49 | /// Gets or sets the name.
50 | ///
51 | ///
52 | /// The name.
53 | ///
54 | public string Name { get; }
55 |
56 | ///
57 | /// Gets or sets the name of the file.
58 | ///
59 | ///
60 | /// The name of the file.
61 | ///
62 | public string? FileName { get; }
63 |
64 | ///
65 | /// Gets the type of the media.
66 | ///
67 | ///
68 | /// The type of the media.
69 | ///
70 | public string MediaType { get; }
71 | }
--------------------------------------------------------------------------------
/lib/Infrastructure/MultiFormPropertyItem.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 System.Reflection;
17 |
18 | namespace Gotenberg.Sharp.API.Client.Infrastructure;
19 |
20 | internal class MultiFormPropertyItem(PropertyInfo property, MultiFormHeaderAttribute attribute)
21 | {
22 | private static readonly Type _attributeType = typeof(MultiFormHeaderAttribute);
23 |
24 | public PropertyInfo Property { get; } =
25 | property ?? throw new ArgumentNullException(nameof(property));
26 |
27 | public MultiFormHeaderAttribute Attribute { get; } =
28 | attribute ?? throw new ArgumentNullException(nameof(attribute));
29 |
30 | internal static IEnumerable FromType(Type instanceType)
31 | {
32 | if (instanceType == null) throw new ArgumentNullException(nameof(instanceType));
33 |
34 | var propertyInfos = instanceType.GetProperties()
35 | .Where(prop => System.Attribute.IsDefined(prop, _attributeType)).ToList();
36 |
37 | foreach (var propertyInfo in propertyInfos)
38 | if (System.Attribute.GetCustomAttribute(
39 | propertyInfo,
40 | _attributeType) is MultiFormHeaderAttribute multiFormHeaderAttribute)
41 | yield return new MultiFormPropertyItem(
42 | propertyInfo,
43 | multiFormHeaderAttribute);
44 | }
45 | }
--------------------------------------------------------------------------------
/lib/Infrastructure/MultiTargetHelpers/KeyValuePair.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 | #if NETSTANDARD2_0
17 | // ReSharper disable once CheckNamespace
18 | namespace System.Collections.Generic;
19 |
20 | internal static class KeyValuePair
21 | {
22 | ///
23 | /// b/c Kvp.Create is not supported by netstandard2.0
24 | ///
25 | /// Type of the key.
26 | /// Type of the value.
27 | /// The key.
28 | /// The value.
29 | ///
30 | ///
31 | internal static KeyValuePair Create(TKey key, TValue value)
32 | {
33 | return new KeyValuePair(key, value);
34 | }
35 | }
36 |
37 | #endif
--------------------------------------------------------------------------------
/lib/Infrastructure/Pipeline/PolicyFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 System.Diagnostics.Contracts;
17 |
18 | using Gotenberg.Sharp.API.Client.Domain.Settings;
19 |
20 |
21 |
22 | using Microsoft.Extensions.DependencyInjection;
23 | using Microsoft.Extensions.Logging;
24 | using Microsoft.Extensions.Options;
25 |
26 | using Polly;
27 | using Polly.Timeout;
28 |
29 | using static Polly.Extensions.Http.HttpPolicyExtensions;
30 |
31 | namespace Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
32 |
33 | internal static class PolicyFactory
34 | {
35 | internal static IAsyncPolicy CreatePolicyFromSettings(
36 | IServiceProvider sp,
37 | HttpRequestMessage _)
38 | {
39 | Contract.Ensures(Contract.Result>() != null);
40 |
41 | var retryOps = GetRetryOptions(sp);
42 |
43 | if (!(retryOps?.Enabled ?? false)) return Policy.NoOpAsync();
44 |
45 | return HandleTransientHttpError()
46 | .Or()
47 | .WaitAndRetryAsync(
48 | retryOps.RetryCount,
49 | retryCount => TimeSpan.FromSeconds(Math.Pow(retryOps.BackoffPower, retryCount)),
50 | (outcome, delay, retryCount, context) =>
51 | {
52 | context["retry-count"] = retryCount;
53 |
54 | if (!retryOps.LoggingEnabled) return;
55 |
56 | var logger = sp.GetRequiredService>();
57 |
58 | logger?.LogWarning(
59 | "{name} delaying for {delay} ms, then making retry # {retry} of {retryAttempts}. Retry reason: '{reason}'",
60 | context.PolicyKey,
61 | delay.TotalMilliseconds,
62 | retryCount,
63 | retryOps.RetryCount,
64 | outcome?.Exception?.Message ??
65 | "No exception, check the gotenberg container logs for errors");
66 | })
67 | .WithPolicyKey(nameof(GotenbergSharpClient));
68 | }
69 |
70 | private static RetryOptions? GetRetryOptions(IServiceProvider sp)
71 | {
72 | return sp.GetRequiredService>().Value
73 | ?.RetryPolicy;
74 | }
75 | }
--------------------------------------------------------------------------------
/lib/Infrastructure/Pipeline/TimeoutHandler.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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 System.Diagnostics.CodeAnalysis;
17 | using System.Threading;
18 |
19 |
20 |
21 | namespace Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
22 |
23 |
24 | [SuppressMessage("ReSharper", "CA2000")]
25 | // ReSharper disable once HollowTypeName
26 | public sealed class TimeoutHandler : DelegatingHandler
27 | {
28 | ///
29 | /// Initializes a new instance of the class.
30 | ///
31 | /// The inner handler which is responsible for processing the HTTP response messages.
32 | public TimeoutHandler(HttpMessageHandler? innerHandler = null)
33 | : base(innerHandler ?? new HttpClientHandler())
34 | {
35 | }
36 |
37 | ///
38 | /// Gets or sets the default timeout.
39 | ///
40 | ///
41 | /// The default timeout.
42 | ///
43 |
44 | public TimeSpan DefaultTimeout { get; set; } = TimeSpan.FromSeconds(300);
45 |
46 | ///
47 | /// Sends the asynchronous.
48 | ///
49 | /// The request.
50 | /// The cancel token.
51 | ///
52 | /// Request Timeout
53 | protected override async Task SendAsync(
54 | HttpRequestMessage request,
55 | CancellationToken cancellationToken)
56 | {
57 | using var cts = this.GetCancelTokenSource(request, cancellationToken);
58 |
59 | try
60 | {
61 | return await base.SendAsync(request, cts?.Token ?? cancellationToken)
62 | .ConfigureAwait(false);
63 | }
64 | catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)
65 | {
66 | throw new TimeoutException("Request Timeout", ex.InnerException);
67 | }
68 | }
69 |
70 | private CancellationTokenSource? GetCancelTokenSource(
71 | HttpRequestMessage request,
72 | CancellationToken cancelToken)
73 | {
74 | var timeout = request.GetTimeout() ?? this.DefaultTimeout;
75 | if (timeout == Timeout.InfiniteTimeSpan) return null;
76 |
77 | var cts = CancellationTokenSource.CreateLinkedTokenSource(cancelToken);
78 | cts.CancelAfter(timeout);
79 |
80 | return cts;
81 | }
82 | }
--------------------------------------------------------------------------------
/lib/Infrastructure/ValidOfficeMergeItem.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2019-2025 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.Infrastructure;
17 |
18 | public class ValidOfficeMergeItem(KeyValuePair asset, string mediaType)
19 | {
20 | public string MediaType { get; } = mediaType;
21 |
22 | public KeyValuePair Asset { get; } = asset;
23 | }
--------------------------------------------------------------------------------
/lib/Resources/gotenbergSharpClient-large.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/05c1fe7508c700d08be2ff2979857e6493a8c0d5/lib/Resources/gotenbergSharpClient-large.PNG
--------------------------------------------------------------------------------
/lib/Resources/gotenbergSharpClient.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/05c1fe7508c700d08be2ff2979857e6493a8c0d5/lib/Resources/gotenbergSharpClient.PNG
--------------------------------------------------------------------------------
/linqpad/DI-Example.linq:
--------------------------------------------------------------------------------
1 |
2 | Autofac
3 | Autofac.Extensions.DependencyInjection
4 | Gotenberg.Sharp.API.Client
5 | Microsoft.Extensions.Configuration
6 | Microsoft.Extensions.Configuration.Json
7 | Microsoft.Extensions.DependencyInjection
8 | Microsoft.Extensions.Logging
9 | Microsoft.Extensions.Logging.Console
10 | Microsoft.Extensions.Options
11 | Microsoft.Extensions.Options.ConfigurationExtensions
12 | Autofac
13 | Gotenberg.Sharp.API.Client
14 | Gotenberg.Sharp.API.Client.Domain.Builders
15 | Gotenberg.Sharp.API.Client.Domain.Builders.Faceted
16 | Gotenberg.Sharp.API.Client.Domain.Requests
17 | Gotenberg.Sharp.API.Client.Domain.Settings
18 | Gotenberg.Sharp.API.Client.Extensions
19 | Microsoft.Extensions.Configuration
20 | Microsoft.Extensions.DependencyInjection
21 | Microsoft.Extensions.DependencyInjection.Extensions
22 | Microsoft.Extensions.Logging
23 | Microsoft.Extensions.Logging.Console
24 | Microsoft.Extensions.Options
25 | System.Threading.Tasks
26 |
27 |
28 | //Builds a simple DI container with logging enabled.
29 | //Client retreived through the SP is configured with options defined in local appsettings.json
30 | //Watch the polly-retry policy in action:
31 | // Turn off gotenberg, run this script and let it fail/retry two or three times.
32 | // Turn gotenberg back on & the request will successfully complete.
33 | //Example builds a 1 page PDF from the specified TargetUrl
34 |
35 | const string TargetUrl = "https://www.cnn.com";
36 | const string SaveToPath = @"D:\Gotenberg\Dumps";
37 |
38 | static Random Rand = new Random(Math.Abs( (int) DateTime.Now.Ticks));
39 |
40 | async Task Main()
41 | {
42 | using (var scope = new ContainerBuilder().Build().BeginLifetimeScope())
43 | {
44 | var services = BuildServiceCollection();
45 |
46 | var sp = services.BuildServiceProvider();
47 |
48 | var sharpClient = sp.GetRequiredService();
49 |
50 | var request = await CreateUrlRequest();
51 |
52 | var response = await sharpClient.UrlToPdfAsync(request);
53 |
54 | var resultPath = @$"{SaveToPath}\GotenbergFromUrl-{Rand.Next()}.pdf";
55 |
56 | using (var destinationStream = File.Create(resultPath))
57 | {
58 | await response.CopyToAsync(destinationStream);
59 | }
60 |
61 | var info = new ProcessStartInfo { FileName = resultPath, UseShellExecute = true };
62 | Process.Start(info);
63 |
64 | resultPath.Dump("Done");
65 |
66 | //var ops = sp.GetRequiredService>();
67 | //ops.Dump();
68 | }
69 | }
70 |
71 | #region configuration & service collection Setup
72 |
73 | IServiceCollection BuildServiceCollection()
74 | {
75 | var config = new ConfigurationBuilder()
76 | .SetBasePath(@$"{Path.GetDirectoryName(Util.CurrentQueryPath)}\Resources\Settings")
77 | .AddJsonFile("appsettings.json")
78 | .Build();
79 |
80 | return new ServiceCollection()
81 | .AddOptions()
82 | .Bind(config.GetSection(nameof(GotenbergSharpClient))).Services
83 | .AddGotenbergSharpClient()
84 | .Services.AddLogging(s => s.AddSimpleConsole(ops => {
85 | ops.IncludeScopes = true;
86 | ops.SingleLine = false;
87 | ops.TimestampFormat = "hh:mm:ss ";
88 | }));
89 | }
90 |
91 | #endregion
92 |
93 | #region gotenberg request creation
94 |
95 | Task CreateUrlRequest()
96 | {
97 | var builder = new UrlRequestBuilder()
98 | .SetUrl(TargetUrl)
99 | .SetConversionBehaviors(b =>
100 | {
101 | b.SetUserAgent(nameof(GotenbergSharpClient));
102 | })
103 | .ConfigureRequest(b => b.SetPageRanges("1-2"))
104 | .WithDimensions(b =>
105 | {
106 | b.SetPaperSize(PaperSizes.A4)
107 | .SetMargins(Margins.None);
108 | });
109 |
110 | return builder.BuildAsync();
111 | }
112 |
113 | #endregion
--------------------------------------------------------------------------------
/linqpad/HtmlConvert.linq:
--------------------------------------------------------------------------------
1 |
2 | Gotenberg.Sharp.API.Client
3 | Gotenberg.Sharp.API.Client
4 | Gotenberg.Sharp.API.Client.Domain.Builders
5 | Gotenberg.Sharp.API.Client.Domain.Builders.Faceted
6 | Gotenberg.Sharp.API.Client.Extensions
7 | System.Net.Http
8 | System.Threading.Tasks
9 |
10 |
11 |
12 | static string ResourcePath = @$"{Path.GetDirectoryName(Util.CurrentQueryPath)}\Resources\Html\ConvertExample\";
13 | static Random Rand = new Random(Math.Abs( (int) DateTime.Now.Ticks));
14 |
15 | async Task Main()
16 | {
17 | var path = await CreateFromHtml(@"D:\Gotenberg\Dumps");
18 |
19 | var info = new ProcessStartInfo { FileName = path, UseShellExecute = true };
20 | Process.Start(info);
21 |
22 | path.Dump("Done");
23 | }
24 |
25 | public async Task CreateFromHtml(string destinationDirectory)
26 | {
27 | var sharpClient = new GotenbergSharpClient("http://localhost:3000");
28 |
29 | var builder = new HtmlRequestBuilder()
30 | .AddAsyncDocument(async doc =>
31 | doc.SetBody(await GetHtmlFile("body.html"))
32 | .SetFooter(await GetHtmlFile("footer.html"))
33 | ).WithDimensions(dims => dims.UseChromeDefaults())
34 | .WithAsyncAssets(async
35 | assets => assets.AddItem("ear-on-beach.jpg", await GetImageBytes())
36 | ).SetConversionBehaviors(b =>
37 | b.AddAdditionalHeaders("hello", "from-earth")
38 | ).ConfigureRequest(b=> b.SetPageRanges("1"));
39 |
40 | var request = await builder.BuildAsync();
41 |
42 | var resultPath = @$"{destinationDirectory}\GotenbergFromHtml-{Rand.Next()}.pdf";
43 | var response = await sharpClient.HtmlToPdfAsync(request);
44 |
45 | using (var destinationStream = File.Create(resultPath))
46 | {
47 | await response.CopyToAsync(destinationStream);
48 | }
49 |
50 | return resultPath;
51 | }
52 |
53 | static Task GetImageBytes()
54 | {
55 | return File.ReadAllBytesAsync($@"{ResourcePath}\ear-on-beach.jpg");
56 | }
57 |
58 | static Task GetHtmlFile(string fileName)
59 | {
60 | return File.ReadAllBytesAsync($@"{ResourcePath}\{fileName}");
61 | }
62 |
--------------------------------------------------------------------------------
/linqpad/HtmlWithMarkdown.linq:
--------------------------------------------------------------------------------
1 |
2 | C:\Projects\GotenbergSharpApiClient\lib\bin\Debug\net5.0\Gotenberg.Sharp.API.Client.dll
3 | Gotenberg.Sharp.API.Client
4 | Gotenberg.Sharp.API.Client.Domain.Builders
5 | Gotenberg.Sharp.API.Client.Extensions
6 | Gotenberg.Sharp.API.Client.Infrastructure
7 | System.Net.Http
8 | System.Threading.Tasks
9 |
10 |
11 |
12 | static Random Rand = new Random(Math.Abs( (int) DateTime.Now.Ticks));
13 | static string ResourcePath = @$"{Path.GetDirectoryName(Util.CurrentQueryPath)}\Resources\Markdown";
14 |
15 | async Task Main()
16 | {
17 | var path = await CreateFromMarkdown(@"C:\Temp\Gotenberg\Dumps");
18 |
19 | var info = new ProcessStartInfo { FileName = path, UseShellExecute = true };
20 | Process.Start(info);
21 |
22 | path.Dump("done");
23 | }
24 |
25 | public async Task CreateFromMarkdown(string destinationDirectory)
26 | {
27 | var sharpClient = new GotenbergSharpClient("http://localhost:3000");
28 |
29 | var builder = new HtmlRequestBuilder()
30 | .AddAsyncDocument(async
31 | b => b.SetHeader(await GetFile("header.html"))
32 | .SetBody(await GetFile("index.html"))
33 | .ContainsMarkdown()
34 | .SetFooter(await GetFile("footer.html"))
35 | ).WithDimensions(b =>
36 | {
37 | b.UseChromeDefaults()
38 | .LandScape()
39 | .SetScale(.90);
40 | }).WithAsyncAssets(async
41 | b => b.AddItems(await GetMarkdownAssets())
42 | ).ConfigureRequest(b => b.SetResultFileName("hello.pdf")
43 | ).SetConversionBehaviors(b => b.SetBrowserWaitDelay(2));
44 |
45 | var request = await builder.BuildAsync();
46 |
47 | var response = await sharpClient.HtmlToPdfAsync(request);
48 |
49 | var outPath = @$"{destinationDirectory}\GotenbergFromMarkDown-{Rand.Next()}.pdf";
50 |
51 | using (var destinationStream = File.Create(outPath))
52 | {
53 | await response.CopyToAsync(destinationStream);
54 | }
55 |
56 | return outPath;
57 | }
58 |
59 | async Task GetFile(string fileName)
60 | => await File.ReadAllTextAsync(@$"{ResourcePath}\{fileName}");
61 |
62 | async Task>> GetMarkdownAssets()
63 | {
64 | var bodyAssetNames = new[] {"img.gif" , "font.woff", "style.css" };
65 | var markdownFiles = new[] { "paragraph1.md", "paragraph2.md", "paragraph3.md" };
66 |
67 | var bodyAssetTasks = bodyAssetNames.Select(ba => GetFile(ba));
68 | var mdTasks = markdownFiles.Select(md => GetFile(md));
69 |
70 | var bodyAssets = await Task.WhenAll(bodyAssetTasks);
71 | var mdParagraphs = await Task.WhenAll(mdTasks);
72 |
73 | return bodyAssetNames.Select((name, index) => KeyValuePair.Create(name, bodyAssets[index]))
74 | .Concat(markdownFiles.Select((name, index) => KeyValuePair.Create(name, mdParagraphs[index])));
75 | }
--------------------------------------------------------------------------------
/linqpad/OfficeMerge.linq:
--------------------------------------------------------------------------------
1 |
2 | C:\Projects\GotenbergSharpApiClient\lib\bin\Debug\net5.0\Gotenberg.Sharp.API.Client.dll
3 | Gotenberg.Sharp.API.Client
4 | Gotenberg.Sharp.API.Client.Domain.Builders
5 | Gotenberg.Sharp.API.Client.Domain.Builders.Faceted
6 | Gotenberg.Sharp.API.Client.Domain.Requests
7 | Gotenberg.Sharp.API.Client.Extensions
8 | Gotenberg.Sharp.API.Client.Infrastructure
9 | System.Net.Http
10 | System.Threading.Tasks
11 |
12 |
13 |
14 | static Random Rand = new Random(Math.Abs((int)DateTime.Now.Ticks));
15 | static string ResourcePath = @$"{Path.GetDirectoryName(Util.CurrentQueryPath)}\Resources\OfficeDocs";
16 |
17 | async Task Main()
18 | {
19 | var destination = @"C:\Temp\Gotenberg\Dumps";
20 | var p = await DoOfficeMerge(ResourcePath, destination);
21 |
22 | ResourcePath.Dump();
23 |
24 | var info = new ProcessStartInfo { FileName = p, UseShellExecute = true };
25 | Process.Start(info);
26 |
27 | p.Dump("The path");
28 | }
29 |
30 | public async Task DoOfficeMerge(string sourceDirectory, string destinationDirectory)
31 | {
32 | var client = new GotenbergSharpClient("http://localhost:3000");
33 |
34 | var builder = new MergeOfficeBuilder()
35 | .ConfigureRequest(c => c.SetTrace("LinqPad"))
36 | .WithAsyncAssets(async b => b.AddItems(await GetDocsAsync(sourceDirectory)))
37 | .SetPdfFormat(PdfFormats.A2b)
38 | .UseNativePdfFormat()
39 | .ConfigureRequest(n =>
40 | n.SetPageRanges("1-3") //Only one of the files has more than 1 page.
41 | );
42 |
43 | var response = await client.MergeOfficeDocsAsync(builder).ConfigureAwait(false);
44 |
45 | var mergeResultPath = @$"{destinationDirectory}\GotenbergOfficeMerge-{Rand.Next()}.pdf";
46 |
47 | using (var destinationStream = File.Create(mergeResultPath))
48 | {
49 | await response.CopyToAsync(destinationStream).ConfigureAwait(false);
50 | }
51 |
52 | return mergeResultPath;
53 | }
54 |
55 | async Task>> GetDocsAsync(string sourceDirectory)
56 | {
57 | var paths = Directory.GetFiles(sourceDirectory, "*.*", SearchOption.TopDirectoryOnly);
58 | var names = paths.Select(p => new FileInfo(p).Name);
59 | var tasks = paths.Select(f => File.ReadAllBytesAsync(f));
60 |
61 | var docs = await Task.WhenAll(tasks);
62 |
63 | return names.Select((name, index) => KeyValuePair.Create(name, docs[index]))
64 | .Take(10);
65 | }
--------------------------------------------------------------------------------
/linqpad/PdfMerge.linq:
--------------------------------------------------------------------------------
1 |
2 | Gotenberg.Sharp.API.Client
3 | Gotenberg.Sharp.API.Client
4 | Gotenberg.Sharp.API.Client.Domain.Builders
5 | Gotenberg.Sharp.API.Client.Domain.Builders.Faceted
6 | Gotenberg.Sharp.API.Client.Domain.Requests
7 | Gotenberg.Sharp.API.Client.Domain.Requests.Facets
8 | Gotenberg.Sharp.API.Client.Extensions
9 | System.Net.Http
10 | System.Threading.Tasks
11 |
12 |
13 | async Task Main()
14 | {
15 | var p = await DoMerge(@"D:\Gotenberg\Dumps");
16 |
17 | var info = new ProcessStartInfo { FileName = p, UseShellExecute = true };
18 | Process.Start(info);
19 |
20 | p.Dump("Done");
21 | }
22 |
23 | async Task DoMerge(string destinationPath)
24 | {
25 | var sharpClient = new GotenbergSharpClient("http://localhost:3000");
26 |
27 | var items = Directory.GetFiles(destinationPath, "*.pdf", SearchOption.TopDirectoryOnly)
28 | .Select(p => new { Info = new FileInfo(p), Path = p })
29 | .Where(item => !item.Info.Name.Contains("GotenbergMergeResult.pdf"))
30 | .OrderBy(item => item.Info.CreationTime)
31 | .Take(2);
32 |
33 | items.Dump("Items", 0);
34 |
35 | var toMerge = items.Select(item => KeyValuePair.Create(item.Info.Name, File.ReadAllBytes(item.Path)));
36 |
37 | var builder = new MergeBuilder()
38 | .SetPdfFormat(PdfFormats.A2b)
39 | .WithAssets(b => { b.AddItems(toMerge) ;});
40 |
41 | var request = builder.Build();
42 | request.ToHttpContent()
43 | .ToDumpFriendlyFormat()
44 | .Dump();
45 | var response = await sharpClient.MergePdfsAsync(request);
46 |
47 | var outPath = @$"{destinationPath}\GotenbergMergeResult.pdf";
48 |
49 | using (var destinationStream = File.Create(outPath))
50 | {
51 | await response.CopyToAsync(destinationStream, default(CancellationToken));
52 | }
53 |
54 | return outPath;
55 |
56 | }
--------------------------------------------------------------------------------
/linqpad/Resources/Html/ConvertExample/body.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Thanks to Gotenberg
6 |
16 |
17 |
18 | Hello World
19 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/linqpad/Resources/Html/ConvertExample/ear-on-beach.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/05c1fe7508c700d08be2ff2979857e6493a8c0d5/linqpad/Resources/Html/ConvertExample/ear-on-beach.jpg
--------------------------------------------------------------------------------
/linqpad/Resources/Html/ConvertExample/footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 | of pages PDF Created on
15 |
16 |
17 |
--------------------------------------------------------------------------------
/linqpad/Resources/Html/UrlFooter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
Footer
13 | of
14 |
15 |
16 |
--------------------------------------------------------------------------------
/linqpad/Resources/Html/UrlHeader.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 | Header
13 |
14 |
15 |
--------------------------------------------------------------------------------
/linqpad/Resources/Html/font.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/05c1fe7508c700d08be2ff2979857e6493a8c0d5/linqpad/Resources/Html/font.woff
--------------------------------------------------------------------------------
/linqpad/Resources/Html/footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 | of
13 |
14 |
15 |
--------------------------------------------------------------------------------
/linqpad/Resources/Html/header.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/linqpad/Resources/Html/img.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/05c1fe7508c700d08be2ff2979857e6493a8c0d5/linqpad/Resources/Html/img.gif
--------------------------------------------------------------------------------
/linqpad/Resources/Html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Gutenberg
8 |
9 |
10 |
11 |
12 |
Gutenberg
13 |
14 |
15 |
16 |
17 | It is a press, certainly, but a press from which shall flow in inexhaustible streams...Through it, God will spread His Word. A spring of truth shall flow from it: like a new star it shall scatter the darkness of ignorance, and cause a light heretofore unknown to shine amongst men.
18 |
19 |
20 |
21 |
22 |
23 |
This paragraph use the default font
24 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
25 |
26 |
This paragraph use a Google font
27 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
28 |
29 |
This paragraph use a local font
30 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
31 |
32 |
33 |
34 |
This image is loaded from a URL
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/linqpad/Resources/Html/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Arial, Helvetica, sans-serif;
3 | }
4 |
5 | .center {
6 | text-align: center;
7 | }
8 |
9 | .google-font {
10 | font-family: 'Montserrat', sans-serif;
11 | }
12 |
13 | @font-face {
14 | font-family: 'Local';
15 | src: url('font.woff') format('woff');
16 | font-weight: normal;
17 | font-style: normal;
18 | }
19 |
20 | .local-font {
21 | font-family: 'Local'
22 | }
23 |
24 | @media print {
25 | .page-break-after {
26 | page-break-after: always;
27 | }
28 | }
--------------------------------------------------------------------------------
/linqpad/Resources/Markdown/font.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/05c1fe7508c700d08be2ff2979857e6493a8c0d5/linqpad/Resources/Markdown/font.woff
--------------------------------------------------------------------------------
/linqpad/Resources/Markdown/footer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 | of
13 |
14 |
15 |
--------------------------------------------------------------------------------
/linqpad/Resources/Markdown/header.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/linqpad/Resources/Markdown/img.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/05c1fe7508c700d08be2ff2979857e6493a8c0d5/linqpad/Resources/Markdown/img.gif
--------------------------------------------------------------------------------
/linqpad/Resources/Markdown/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Gutenberg
8 |
9 |
10 |
11 |
12 |
Gutenberg
13 |
14 |
15 |
16 |
17 | It is a press, certainly, but a press from which shall flow in inexhaustible streams...Through it, God will spread His Word. A spring of truth shall flow from it: like a new star it shall scatter the darkness of ignorance, and cause a light heretofore unknown to shine amongst men.
18 |
19 |
20 |
21 |
22 |
23 | {{ toHTML "paragraph1.md" }}
24 |
25 |
26 | {{ toHTML "paragraph2.md" }}
27 |
28 |
29 |
30 | {{ toHTML "paragraph3.md" }}
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/linqpad/Resources/Markdown/paragraph1.md:
--------------------------------------------------------------------------------
1 | ## This paragraph use the default font and has been generated from a markdown file
2 |
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
--------------------------------------------------------------------------------
/linqpad/Resources/Markdown/paragraph2.md:
--------------------------------------------------------------------------------
1 | ## This paragraph use a Google font and has been generated from a markdown file
2 |
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
--------------------------------------------------------------------------------
/linqpad/Resources/Markdown/paragraph3.md:
--------------------------------------------------------------------------------
1 | ## This paragraph use a local font and has been generated from a markdown file
2 |
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
--------------------------------------------------------------------------------
/linqpad/Resources/Markdown/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Arial, Helvetica, sans-serif;
3 | }
4 |
5 | .center {
6 | text-align: center;
7 | }
8 |
9 | .google-font {
10 | font-family: 'Montserrat', sans-serif;
11 | }
12 |
13 | @font-face {
14 | font-family: 'Local';
15 | src: url('font.woff') format('woff');
16 | font-weight: normal;
17 | font-style: normal;
18 | }
19 |
20 | .local-font {
21 | font-family: 'Local'
22 | }
23 |
24 | @media print {
25 | .page-break-after {
26 | page-break-after: always;
27 | }
28 | }
--------------------------------------------------------------------------------
/linqpad/Resources/OfficeDocs/LorumIpsem.txt:
--------------------------------------------------------------------------------
1 | Gutenberg
2 |
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
--------------------------------------------------------------------------------
/linqpad/Resources/OfficeDocs/Visual-Studio-NOTICE.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/05c1fe7508c700d08be2ff2979857e6493a8c0d5/linqpad/Resources/OfficeDocs/Visual-Studio-NOTICE.docx
--------------------------------------------------------------------------------
/linqpad/Resources/OfficeDocs/document.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/05c1fe7508c700d08be2ff2979857e6493a8c0d5/linqpad/Resources/OfficeDocs/document.docx
--------------------------------------------------------------------------------
/linqpad/Resources/OfficeDocs/document2.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/05c1fe7508c700d08be2ff2979857e6493a8c0d5/linqpad/Resources/OfficeDocs/document2.docx
--------------------------------------------------------------------------------
/linqpad/Resources/Settings/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "GotenbergSharpClient": {
3 | "ServiceUrl": "http://localhost:3000",
4 | "HealthCheckUrl": "http://localhost:3000/health",
5 | "RetryPolicy": {
6 | "Enabled": true,
7 | "RetryCount": 7,
8 | "BackoffPower": 1.5,
9 | "LoggingEnabled": true
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/linqpad/Resources/office/document.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChangemakerStudios/GotenbergSharpApiClient/05c1fe7508c700d08be2ff2979857e6493a8c0d5/linqpad/Resources/office/document.docx
--------------------------------------------------------------------------------
/linqpad/UrlConvert.linq:
--------------------------------------------------------------------------------
1 |
2 | C:\Projects\GotenbergSharpApiClient\lib\bin\Debug\net5.0\Gotenberg.Sharp.API.Client.dll
3 | Gotenberg.Sharp.API.Client
4 | Gotenberg.Sharp.API.Client.Domain.Builders
5 | Gotenberg.Sharp.API.Client.Domain.Builders.Faceted
6 | Gotenberg.Sharp.API.Client.Domain.Requests
7 | Gotenberg.Sharp.API.Client.Extensions
8 | System.Threading.Tasks
9 |
10 |
11 | static Random Rand = new Random(Math.Abs( (int) DateTime.Now.Ticks));
12 |
13 | async Task Main()
14 | {
15 | var destinationPath = @"C:\Temp\Gotenberg\Dumps";
16 | var headerFooterPath = @$"{Path.GetDirectoryName(Util.CurrentQueryPath)}\Resources\Html";;
17 |
18 | var path = await CreateFromUrl(
19 | destinationPath,
20 | @$"{headerFooterPath}\UrlHeader.html",
21 | @$"{headerFooterPath}\UrlFooter.html");
22 |
23 | var info = new ProcessStartInfo { FileName = path, UseShellExecute = true };
24 | Process.Start(info);
25 |
26 | path.Dump("done");
27 | }
28 |
29 | public async Task CreateFromUrl(string destinationPath, string headerPath, string footerPath)
30 | {
31 | var sharpClient = new GotenbergSharpClient("http://localhost:3000");
32 |
33 | var builder = new UrlRequestBuilder()
34 | .SetUrl("https://www.cnn.com")
35 | .SetConversionBehaviors(b =>
36 | b.EmulateAsScreen()
37 | .SetBrowserWaitDelay(1)
38 | .SetUserAgent(nameof(GotenbergSharpClient))
39 | ).ConfigureRequest(b => b.SetTrace("Linqpad").SetPageRanges("1-2"))
40 | .AddAsyncHeaderFooter(async
41 | b => b.SetHeader(await File.ReadAllBytesAsync(headerPath))
42 | .SetFooter(await File.ReadAllBytesAsync(footerPath)
43 | )).WithDimensions(b =>
44 | b.SetPaperSize(PaperSizes.A4)
45 | .UseChromeDefaults()
46 | .MarginLeft(0)
47 | .MarginRight(0)
48 | );
49 |
50 | var request = await builder.BuildAsync();
51 |
52 | var response = await sharpClient.UrlToPdfAsync(request);
53 |
54 | var resultPath = @$"{destinationPath}\GotenbergFromUrl-{Rand.Next()}.pdf";
55 |
56 | using (var destinationStream = File.Create(resultPath))
57 | {
58 | await response.CopyToAsync(destinationStream);
59 | }
60 |
61 | return resultPath;
62 | }
--------------------------------------------------------------------------------
/linqpad/UrlsToMergedPdf.linq:
--------------------------------------------------------------------------------
1 |
2 | Gotenberg.Sharp.API.Client
3 | Gotenberg.Sharp.API.Client
4 | Gotenberg.Sharp.API.Client.Domain.Builders
5 | Gotenberg.Sharp.API.Client.Domain.Builders.Faceted
6 | Gotenberg.Sharp.API.Client.Domain.Requests
7 | System.Net.Http
8 | System.Threading.Tasks
9 |
10 |
11 | //NOTE: You need to increase gotenberg api's timeout for this to work
12 | //by passing --api-timeout=1800s when running the container.
13 | static Random Rand = new Random(Math.Abs( (int) DateTime.Now.Ticks));
14 |
15 | async Task Main()
16 | {
17 | var path = await CreateWorldNewsSummary($@"D:\NewsArchive");
18 |
19 | var info = new ProcessStartInfo{ FileName = path, UseShellExecute = true};
20 | Process.Start(info);
21 |
22 | path.Dump("Done");
23 | }
24 |
25 | public async Task CreateWorldNewsSummary(string destinationDirectory)
26 | {
27 | var sites = new[] {
28 | "https://www.nytimes.com","https://www.axios.com/",
29 | "https://www.cnn.com", "https://www.csmonitor.com",
30 | "https://www.wsj.com", "https://www.usatoday.com",
31 | "https://www.irishtimes.com", "https://www.lemonde.fr",
32 | "https://calgaryherald.com", "https://www.bbc.com/news/uk",
33 | "https://english.elpais.com/", "https://www.thehindu.com",
34 | "https://www.theaustralian.com.au", "https://www.welt.de",
35 | "https://www.cankaoxiaoxi.com", "https://www.novinky.cz",
36 | "https://www.elobservador.com.uy"}
37 | .Select(u => new Uri(u));
38 |
39 | var builders = CreateRequestBuilders(sites);
40 | var requests = builders.Select(b => b.Build());
41 |
42 | return await ExecuteRequestsAndMerge(requests, destinationDirectory);
43 | }
44 |
45 | IEnumerable CreateRequestBuilders(IEnumerable uris)
46 | {
47 | foreach (var uri in uris)
48 | {
49 | yield return new UrlRequestBuilder()
50 | .SetUrl(uri)
51 | .SetConversionBehaviors(b =>
52 | b.EmulateAsScreen()
53 | .SetUserAgent(nameof(GotenbergSharpClient)))
54 | .ConfigureRequest(b =>
55 | {
56 | b.SetPageRanges("1-2");
57 | })
58 | .WithDimensions(b =>
59 | {
60 | b.SetMargins(Margins.None)
61 | .MarginLeft(.3)
62 | .MarginRight(.3);
63 | });
64 | }
65 |
66 | }
67 |
68 | async Task ExecuteRequestsAndMerge(IEnumerable requests, string destinationDirectory)
69 | {
70 | var innerClient = new HttpClient {
71 | BaseAddress = new Uri("http://localhost:3000"),
72 | Timeout = TimeSpan.FromMinutes(7)
73 | };
74 |
75 | var sharpClient = new GotenbergSharpClient(innerClient);
76 |
77 | var tasks = requests.Select(r => sharpClient.UrlToPdfAsync(r, CancellationToken.None));
78 | var results = await Task.WhenAll(tasks);
79 |
80 | var mergeBuilder = new MergeBuilder()
81 | .WithAssets(b =>
82 | {
83 | b.AddItems(results.Select((r, i) => KeyValuePair.Create($"{i}.pdf", r)));
84 | });
85 |
86 | var response = await sharpClient.MergePdfsAsync(mergeBuilder.Build());
87 |
88 | return await WriteFileAndGetPath(response, destinationDirectory);
89 | }
90 |
91 | async Task WriteFileAndGetPath(Stream responseStream, string desinationDirectory)
92 | {
93 | var fullPath = @$"{desinationDirectory}\{DateTime.Now.ToString("yyyy-MM-MMMM-dd")}-{Rand.Next()}.pdf";
94 |
95 | using (var destinationStream = File.Create(fullPath))
96 | {
97 | await responseStream.CopyToAsync(destinationStream);
98 | }
99 | return fullPath;
100 | }
--------------------------------------------------------------------------------
/linqpad/Webhook.linq:
--------------------------------------------------------------------------------
1 |
2 | C:\Projects\GotenbergSharpApiClient\lib\bin\Debug\net5.0\Gotenberg.Sharp.API.Client.dll
3 | Gotenberg.Sharp.API.Client
4 | Gotenberg.Sharp.API.Client.Domain.Builders
5 | Gotenberg.Sharp.API.Client.Domain.Builders.Faceted
6 | Gotenberg.Sharp.API.Client.Extensions
7 | System.Threading.Tasks
8 |
9 |
10 |
11 | async Task Main()
12 | {
13 | //For this to work you need an api running on localhost:5000 w/ an endpoint to receive the webhook
14 |
15 | var resourcePath = @$"{Path.GetDirectoryName(Util.CurrentQueryPath)}\Resources\Html";
16 |
17 | var destinationPath = @"C:\Temp\Gotenberg\Dumps\FromWebhook";
18 | var footerPath = @$"{resourcePath}\UrlHeader.html";
19 | var headerPath =@$"{resourcePath}\UrlFooter.html";
20 |
21 | headerPath.Dump();
22 |
23 | await CreateFromUrl(destinationPath,headerPath, footerPath);
24 |
25 | "Request sent...".Dump();
26 | }
27 | public async Task CreateFromUrl(string destinationPath, string headerPath, string footerPath)
28 | {
29 | var sharpClient = new GotenbergSharpClient("http://localhost:3000");
30 |
31 | var builder = new UrlRequestBuilder()
32 | .SetUrl("https://www.newyorker.com")
33 | .ConfigureRequest(b =>
34 | {
35 | b.AddWebhook(hook =>
36 | {
37 | hook.SetUrl("http://host.docker.internal:5000/api/WebhookReceiver")
38 | .SetErrorUrl("http://host.docker.internal:5000/api/WebhookReceiver")
39 | .AddExtraHeader("custom-header", "value");
40 | }).SetPageRanges("1-2");
41 | })
42 | .AddAsyncHeaderFooter(async
43 | b => b.SetHeader(await File.ReadAllTextAsync(headerPath))
44 | .SetFooter(await File.ReadAllBytesAsync(footerPath)
45 | )).WithDimensions(b =>
46 | {
47 | b.SetPaperSize(PaperSizes.A4)
48 | .SetMargins(Margins.None);
49 |
50 | });
51 |
52 | var request = await builder.BuildAsync();
53 |
54 | await sharpClient.FireWebhookAndForgetAsync(request);
55 | }
--------------------------------------------------------------------------------
/linqpad/pdfConvert.linq:
--------------------------------------------------------------------------------
1 |
2 | C:\Projects\GotenbergSharpApiClient\lib\bin\Debug\net5.0\Gotenberg.Sharp.API.Client.dll
3 | Gotenberg.Sharp.API.Client
4 | Gotenberg.Sharp.API.Client.Domain.Builders
5 | Gotenberg.Sharp.API.Client.Domain.Builders.Faceted
6 | Gotenberg.Sharp.API.Client.Domain.Requests
7 | Gotenberg.Sharp.API.Client.Domain.Requests.Facets
8 | Gotenberg.Sharp.API.Client.Extensions
9 | System.Threading.Tasks
10 |
11 |
12 |
13 | static Random Rand = new Random(Math.Abs((int)DateTime.Now.Ticks));
14 | const int NumberOfFilesToGet = 1;
15 | //If you get 1, the result is a pdf; get more
16 | //and the API retuns a zip containing the results
17 | //Currently, Gotenberg supports these formats: A1a, A2b & A3b
18 |
19 | async Task Main()
20 | {
21 | var p = await DoConversion(@"C:\Temp\Gotenberg\Delivs");
22 |
23 | var info = new ProcessStartInfo { FileName = p, UseShellExecute = true };
24 | Process.Start(info);
25 |
26 | p.Dump("Done");
27 | }
28 |
29 | async Task DoConversion(string destinationPath)
30 | {
31 | var sharpClient = new GotenbergSharpClient("http://localhost:3000");
32 |
33 | var items = Directory.GetFiles(destinationPath, "*.pdf", SearchOption.TopDirectoryOnly)
34 | .Select(p => new { Info = new FileInfo(p), Path = p })
35 | .OrderBy(item => item.Info.CreationTime)
36 | .Take(2);
37 |
38 | var toConvert = items.Select(item => KeyValuePair.Create(item.Info.Name, File.ReadAllBytes(item.Path)));
39 |
40 | var builder = new PdfConversionBuilder()
41 | .WithPdfs(b => b.AddItems(toConvert) )
42 | .SetFormat(PdfFormats.A1a);
43 |
44 | var request = builder.Build();
45 |
46 | var response = await sharpClient.ConvertPdfDocumentsAsync(request);
47 |
48 | //If you send one in -- the result is pdf.
49 | var extension = items.Count() >1 ? "zip" : "pdf";
50 | var outPath = @$"{destinationPath}\GotenbergConvertResult.{extension}";
51 |
52 | using (var destinationStream = File.Create(outPath))
53 | {
54 | await response.CopyToAsync(destinationStream, default(CancellationToken));
55 | }
56 |
57 | return outPath;
58 |
59 | }
--------------------------------------------------------------------------------