Skip to content

Commit c9521c8

Browse files
authored
Merge pull request #72 from ChangemakerStudios/feature/libre-office-options
Add LibreOffice-specific conversion options
2 parents badcdbe + 1f852d5 commit c9521c8

11 files changed

Lines changed: 927 additions & 0 deletions

File tree

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,26 @@ public async Task<Stream> DoOfficeMerge(string sourceDirectory)
222222
return await _sharpClient.MergeOfficeDocsAsync(request);
223223
}
224224
```
225+
226+
### LibreOffice Conversion Options
227+
*Fine-tune LibreOffice conversions with image compression, bookmarks, and native watermarks:*
228+
229+
```csharp
230+
public async Task<Stream> ConvertWithOptions(string sourceDirectory)
231+
{
232+
var builder = new MergeOfficeBuilder()
233+
.WithAsyncAssets(async a => a.AddItems(await GetDocsAsync(sourceDirectory)))
234+
.SetLibreOfficeOptions(o => o
235+
.SetQuality(85)
236+
.SetReduceImageResolution()
237+
.SetMaxImageResolution(300)
238+
.SetExportBookmarks()
239+
.SetNativeWatermarkText("DRAFT"));
240+
241+
var request = await builder.BuildAsync();
242+
return await _sharpClient.MergeOfficeDocsAsync(request);
243+
}
244+
```
225245
### Markdown to PDF
226246
*Markdown to PDF conversion with embedded assets:*
227247

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
</Project>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using Gotenberg.Sharp.API.Client;
2+
using Gotenberg.Sharp.API.Client.Domain.Builders;
3+
using Gotenberg.Sharp.API.Client.Domain.Settings;
4+
using Gotenberg.Sharp.API.Client.Infrastructure.Pipeline;
5+
6+
using Microsoft.Extensions.Configuration;
7+
8+
var config = new ConfigurationBuilder()
9+
.SetBasePath(AppContext.BaseDirectory)
10+
.AddJsonFile("appsettings.json")
11+
.Build();
12+
13+
var options = new GotenbergSharpClientOptions();
14+
config.GetSection(nameof(GotenbergSharpClient)).Bind(options);
15+
16+
var sourceDirectory = args.Length > 0 ? args[0] : Path.Combine(AppContext.BaseDirectory, "resources", "OfficeDocs");
17+
var destinationDirectory = args.Length > 1 ? args[1] : Path.Combine(Directory.GetCurrentDirectory(), "output");
18+
Directory.CreateDirectory(destinationDirectory);
19+
20+
var path = await ConvertWithLibreOfficeOptions(sourceDirectory, destinationDirectory, options);
21+
Console.WriteLine($"PDF with LibreOffice options created: {path}");
22+
23+
static async Task<string> ConvertWithLibreOfficeOptions(string sourceDirectory, string destinationDirectory, GotenbergSharpClientOptions options)
24+
{
25+
var handler = new HttpClientHandler();
26+
HttpMessageHandler effectiveHandler = handler;
27+
if (!string.IsNullOrWhiteSpace(options.BasicAuthUsername) && !string.IsNullOrWhiteSpace(options.BasicAuthPassword))
28+
effectiveHandler = new BasicAuthHandler(options.BasicAuthUsername, options.BasicAuthPassword) { InnerHandler = handler };
29+
30+
using var httpClient = new HttpClient(effectiveHandler, disposeHandler: true)
31+
{
32+
BaseAddress = options.ServiceUrl,
33+
Timeout = options.TimeOut
34+
};
35+
36+
var client = new GotenbergSharpClient(httpClient);
37+
38+
// Demonstrates LibreOffice-specific conversion options
39+
var builder = new MergeOfficeBuilder()
40+
.WithAsyncAssets(async b => b.AddItems(await GetDocsAsync(sourceDirectory)))
41+
.SetLibreOfficeOptions(o => o
42+
// Image compression
43+
.SetQuality(85)
44+
.SetReduceImageResolution()
45+
.SetMaxImageResolution(300)
46+
// Export options
47+
.SetExportBookmarks()
48+
.SetExportFormFields(false)
49+
.SetUpdateIndexes()
50+
// Native watermark
51+
.SetNativeWatermarkText("DRAFT")
52+
.SetNativeWatermarkFontName("Arial")
53+
)
54+
.SetPdfOutputOptions(o => o.SetPdfFormat(PdfFormat.A2b));
55+
56+
var response = await client.MergeOfficeDocsAsync(builder).ConfigureAwait(false);
57+
58+
var resultPath = Path.Combine(destinationDirectory, $"LibreOfficeOptions-{DateTime.Now:yyyyMMddHHmmss}.pdf");
59+
60+
await using var destinationStream = File.Create(resultPath);
61+
await response.CopyToAsync(destinationStream, CancellationToken.None);
62+
63+
return resultPath;
64+
}
65+
66+
static async Task<IEnumerable<KeyValuePair<string, byte[]>>> GetDocsAsync(string sourceDirectory)
67+
{
68+
var paths = Directory.GetFiles(sourceDirectory, "*.*", SearchOption.TopDirectoryOnly);
69+
var names = paths.Select(p => new FileInfo(p).Name);
70+
var tasks = paths.Select(f => File.ReadAllBytesAsync(f));
71+
var docs = await Task.WhenAll(tasks);
72+
73+
return names.Select((name, index) => KeyValuePair.Create(name, docs[index])).Take(10);
74+
}
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// Copyright 2019-2026 Chris Mohan, Jaben Cargman
2+
// and GotenbergSharpApiClient Contributors
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
using Gotenberg.Sharp.API.Client.Domain.ValueObjects;
17+
18+
namespace Gotenberg.Sharp.API.Client.Domain.Builders.Faceted;
19+
20+
/// <summary>
21+
/// Configures LibreOffice-specific conversion options for the /forms/libreoffice/convert route.
22+
/// </summary>
23+
public sealed class LibreOfficeOptionsBuilder
24+
{
25+
private readonly LibreOfficeOptions _options;
26+
27+
internal LibreOfficeOptionsBuilder(LibreOfficeOptions options)
28+
{
29+
_options = options;
30+
}
31+
32+
// Layout
33+
34+
public LibreOfficeOptionsBuilder SetSinglePageSheets(bool value = true)
35+
{
36+
_options.SinglePageSheets = value;
37+
return this;
38+
}
39+
40+
public LibreOfficeOptionsBuilder SetSkipEmptyPages(bool value = true)
41+
{
42+
_options.SkipEmptyPages = value;
43+
return this;
44+
}
45+
46+
public LibreOfficeOptionsBuilder SetExportPlaceholders(bool value = true)
47+
{
48+
_options.ExportPlaceholders = value;
49+
return this;
50+
}
51+
52+
// Image compression
53+
54+
public LibreOfficeOptionsBuilder SetLosslessImageCompression(bool value = true)
55+
{
56+
_options.LosslessImageCompression = value;
57+
return this;
58+
}
59+
60+
public LibreOfficeOptionsBuilder SetQuality(ImageQuality quality)
61+
{
62+
_options.Quality = quality ?? throw new ArgumentNullException(nameof(quality));
63+
return this;
64+
}
65+
66+
public LibreOfficeOptionsBuilder SetQuality(int quality)
67+
{
68+
return SetQuality(ImageQuality.Create(quality));
69+
}
70+
71+
public LibreOfficeOptionsBuilder SetReduceImageResolution(bool value = true)
72+
{
73+
_options.ReduceImageResolution = value;
74+
return this;
75+
}
76+
77+
public LibreOfficeOptionsBuilder SetMaxImageResolution(ImageResolution resolution)
78+
{
79+
_options.MaxImageResolution = resolution ?? throw new ArgumentNullException(nameof(resolution));
80+
return this;
81+
}
82+
83+
public LibreOfficeOptionsBuilder SetMaxImageResolution(int dpi)
84+
{
85+
return SetMaxImageResolution(ImageResolution.Create(dpi));
86+
}
87+
88+
// Notes & slides
89+
90+
public LibreOfficeOptionsBuilder SetExportNotes(bool value = true)
91+
{
92+
_options.ExportNotes = value;
93+
return this;
94+
}
95+
96+
public LibreOfficeOptionsBuilder SetExportNotesPages(bool value = true)
97+
{
98+
_options.ExportNotesPages = value;
99+
return this;
100+
}
101+
102+
public LibreOfficeOptionsBuilder SetExportOnlyNotesPages(bool value = true)
103+
{
104+
_options.ExportOnlyNotesPages = value;
105+
return this;
106+
}
107+
108+
public LibreOfficeOptionsBuilder SetExportNotesInMargin(bool value = true)
109+
{
110+
_options.ExportNotesInMargin = value;
111+
return this;
112+
}
113+
114+
public LibreOfficeOptionsBuilder SetExportHiddenSlides(bool value = true)
115+
{
116+
_options.ExportHiddenSlides = value;
117+
return this;
118+
}
119+
120+
// Links
121+
122+
public LibreOfficeOptionsBuilder SetConvertOooTargetToPdfTarget(bool value = true)
123+
{
124+
_options.ConvertOooTargetToPdfTarget = value;
125+
return this;
126+
}
127+
128+
public LibreOfficeOptionsBuilder SetExportLinksRelativeFsys(bool value = true)
129+
{
130+
_options.ExportLinksRelativeFsys = value;
131+
return this;
132+
}
133+
134+
// Document outline
135+
136+
public LibreOfficeOptionsBuilder SetUpdateIndexes(bool value = true)
137+
{
138+
_options.UpdateIndexes = value;
139+
return this;
140+
}
141+
142+
public LibreOfficeOptionsBuilder SetExportBookmarks(bool value = true)
143+
{
144+
_options.ExportBookmarks = value;
145+
return this;
146+
}
147+
148+
public LibreOfficeOptionsBuilder SetExportBookmarksToPdfDestination(bool value = true)
149+
{
150+
_options.ExportBookmarksToPdfDestination = value;
151+
return this;
152+
}
153+
154+
public LibreOfficeOptionsBuilder SetAddOriginalDocumentAsStream(bool value = true)
155+
{
156+
_options.AddOriginalDocumentAsStream = value;
157+
return this;
158+
}
159+
160+
// Form fields
161+
162+
public LibreOfficeOptionsBuilder SetExportFormFields(bool value = true)
163+
{
164+
_options.ExportFormFields = value;
165+
return this;
166+
}
167+
168+
public LibreOfficeOptionsBuilder SetAllowDuplicateFieldNames(bool value = true)
169+
{
170+
_options.AllowDuplicateFieldNames = value;
171+
return this;
172+
}
173+
174+
// Native watermark
175+
176+
public LibreOfficeOptionsBuilder SetNativeWatermarkText(string text)
177+
{
178+
if (string.IsNullOrWhiteSpace(text))
179+
throw new ArgumentException("Watermark text must not be null or empty.", nameof(text));
180+
181+
_options.NativeWatermarkText = text;
182+
return this;
183+
}
184+
185+
public LibreOfficeOptionsBuilder SetNativeWatermarkColor(int rgbDecimal)
186+
{
187+
_options.NativeWatermarkColor = rgbDecimal;
188+
return this;
189+
}
190+
191+
public LibreOfficeOptionsBuilder SetNativeWatermarkFontHeight(int fontHeight)
192+
{
193+
if (fontHeight < 0)
194+
throw new ArgumentOutOfRangeException(nameof(fontHeight), "Font height must be non-negative.");
195+
196+
_options.NativeWatermarkFontHeight = fontHeight;
197+
return this;
198+
}
199+
200+
public LibreOfficeOptionsBuilder SetNativeWatermarkRotateAngle(int angle)
201+
{
202+
_options.NativeWatermarkRotateAngle = angle;
203+
return this;
204+
}
205+
206+
public LibreOfficeOptionsBuilder SetNativeWatermarkFontName(string fontName)
207+
{
208+
if (string.IsNullOrWhiteSpace(fontName))
209+
throw new ArgumentException("Font name must not be null or empty.", nameof(fontName));
210+
211+
_options.NativeWatermarkFontName = fontName;
212+
return this;
213+
}
214+
215+
public LibreOfficeOptionsBuilder SetNativeTiledWatermarkText(string text)
216+
{
217+
if (string.IsNullOrWhiteSpace(text))
218+
throw new ArgumentException("Tiled watermark text must not be null or empty.", nameof(text));
219+
220+
_options.NativeTiledWatermarkText = text;
221+
return this;
222+
}
223+
224+
// Source file password
225+
226+
public LibreOfficeOptionsBuilder SetPassword(string password)
227+
{
228+
if (string.IsNullOrWhiteSpace(password))
229+
throw new ArgumentException("Password must not be null or empty.", nameof(password));
230+
231+
_options.Password = password;
232+
return this;
233+
}
234+
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,23 @@ public MergeOfficeBuilder SetPageRanges(string pageRanges)
4747
return this;
4848
}
4949

50+
/// <summary>
51+
/// Configures LibreOffice-specific conversion options such as image compression,
52+
/// notes export, bookmarks, form fields, and native watermarks.
53+
/// </summary>
54+
/// <param name="action">Configuration action for LibreOffice options.</param>
55+
/// <returns>The builder instance for method chaining.</returns>
56+
public MergeOfficeBuilder SetLibreOfficeOptions(Action<LibreOfficeOptionsBuilder> action)
57+
{
58+
if (action == null) throw new ArgumentNullException(nameof(action));
59+
60+
this.Request.LibreOfficeOptions ??= new LibreOfficeOptions();
61+
62+
action(new LibreOfficeOptionsBuilder(this.Request.LibreOfficeOptions));
63+
64+
return this;
65+
}
66+
5067
/// <summary>
5168
/// Converts the resulting merged PDF to the specified PDF/A format for long-term archival.
5269
/// </summary>

0 commit comments

Comments
 (0)