├── .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 |
20 |
21 | 22 |
Photo by Bill Brandt.
23 |
24 |
25 |

Powered by Gotenberg

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 | } --------------------------------------------------------------------------------