Skip to content

Commit 09b6c02

Browse files
committed
Feature gap
1 parent 1dfe282 commit 09b6c02

1 file changed

Lines changed: 298 additions & 0 deletions

File tree

GOTENBERG_FEATURE_GAP.md

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
# Gotenberg Feature Gap Analysis
2+
3+
> **Purpose**: This document is a guide for continuing work on adding missing Gotenberg features to the GotenbergSharpApiClient. It reflects the state of the Gotenberg API as of March 2026 vs what the client supports after the PdfOutputOptions refactoring.
4+
5+
## Architecture Context
6+
7+
The client was recently refactored to unify PDF output options. Key patterns to follow:
8+
9+
- **Facet classes** extend `FacetBase` and use `[MultiFormHeader("fieldName")]` attributes on properties. The reflection-based `ToHttpContent()` in `FacetBase` handles serialization automatically. This is the preferred way to add new form fields.
10+
- **Facet builders** are fluent builder classes that wrap a facet instance (e.g., `PdfOutputOptionsBuilder` wraps `PdfOutputOptions`).
11+
- **`BuildRequestBase`** is the root of all requests. It holds shared concerns (`Config`, `Assets`, `PdfOutputOptions`). New cross-cutting facets should be added here.
12+
- **`BaseBuilder<TRequest, TBuilder>`** is the root of all builders. Methods added here are available on every builder type.
13+
- **`Constants.Gotenberg`** holds all form field name strings. Add new shared field names to `CrossCutting` (private) and expose via public nested classes.
14+
- **`FacetBase.GetValueAsInvariantCultureString()`** handles type-to-string conversion for form data. If you add a new enum or complex type, add a case there.
15+
16+
### Request Hierarchy
17+
```
18+
BuildRequestBase (has Config, Assets, PdfOutputOptions)
19+
ChromeRequest (has PageProperties, HtmlConversionBehaviors)
20+
HtmlRequest
21+
UrlRequest
22+
PdfRequestBase (thin wrapper, delegates to base)
23+
MergeRequest
24+
MergeOfficeRequest
25+
PdfConversionRequest
26+
```
27+
28+
### Builder Hierarchy
29+
```
30+
BaseBuilder<TRequest, TBuilder> (has ConfigureRequest, SetPdfOutputOptions)
31+
BaseChromiumBuilder (has WithPageProperties, SetConversionBehaviors, WithAssets)
32+
HtmlRequestBuilder
33+
UrlRequestBuilder
34+
BaseMergeBuilder (has WithAssets)
35+
MergeBuilder
36+
MergeOfficeBuilder
37+
PdfConversionBuilder
38+
```
39+
40+
### How Form Fields Get Sent
41+
1. Properties decorated with `[MultiFormHeader("fieldName")]` on `FacetBase` subclasses are auto-serialized via reflection.
42+
2. `IConvertToHttpContent.ToHttpContent()` returns `IEnumerable<HttpContent>` which gets assembled into `MultipartFormDataContent`.
43+
3. Headers (like `Gotenberg-Trace`, webhook headers) go through `RequestConfig.GetHeaders()` and are set on the `HttpRequestMessage` directly, not as form data.
44+
4. The `Gotenberg-Output-Filename` header is currently sent as a form field in `RequestConfig.ToHttpContent()` -- Gotenberg accepts it both ways.
45+
46+
---
47+
48+
## Missing Features by Category
49+
50+
### 1. Screenshot Routes (NEW - Entirely new capability)
51+
52+
**API Routes:**
53+
- `POST /forms/chromium/screenshot/url`
54+
- `POST /forms/chromium/screenshot/html`
55+
- `POST /forms/chromium/screenshot/markdown`
56+
57+
**New form fields (screenshot-specific):**
58+
| Field | Type | Default | Description |
59+
|-------|------|---------|-------------|
60+
| `width` | number | 800 | Device screen width in pixels |
61+
| `height` | number | 600 | Device screen height in pixels |
62+
| `clip` | boolean | false | Clip screenshot to device dimensions |
63+
| `format` | enum | png | Image format: `png`, `jpeg`, `webp` |
64+
| `quality` | number | 100 | Compression quality (0-100), JPEG only |
65+
| `optimizeForSpeed` | boolean | false | Optimize encoding for speed over size |
66+
67+
**Shares with Chromium PDF routes:** `omitBackground`, `waitDelay`, `waitForExpression`, `waitForSelector`, `cookies`, `extraHttpHeaders`, `userAgent`, `emulatedMediaFeatures`, `failOnHttpStatusCodes`, `failOnResourceHttpStatusCodes`, `ignoreResourceHttpStatusDomains`, `skipNetworkIdleEvent`, `failOnResourceLoadingFailed`, `failOnConsoleExceptions`.
68+
69+
**Does NOT use:** Page properties (paperWidth, margins, etc.), PDF output options, header/footer files.
70+
71+
**Implementation approach:**
72+
- Create `ScreenshotOptions` facet with the screenshot-specific fields.
73+
- Create a `ScreenshotRequest` base class (similar to `ChromeRequest` but with `ScreenshotOptions` + `HtmlConversionBehaviors`, no `PageProperties`).
74+
- Create `ScreenshotUrlRequest`, `ScreenshotHtmlRequest`, `ScreenshotMarkdownRequest`.
75+
- Create corresponding builders.
76+
- Add screenshot API paths to `Constants.Gotenberg.Chromium.ApiPaths`.
77+
- Add methods to `GotenbergSharpClient`: `ScreenshotUrlAsync()`, `ScreenshotHtmlAsync()`, `ScreenshotMarkdownAsync()`.
78+
- **Important**: The client constructor currently hardcodes `Accept: application/pdf`. Screenshot responses are images. Either don't set a default Accept header, or set it per-request. The `SendRequestAsync` method may need to handle non-PDF content types.
79+
80+
### 2. Missing Chromium Form Fields (add to existing facets)
81+
82+
These apply to all Chromium routes (URL/HTML/Markdown to PDF AND screenshots):
83+
84+
| Field | Add To | Type | Default | Description |
85+
|-------|--------|------|---------|-------------|
86+
| `waitForSelector` | `HtmlConversionBehaviors` | string | None | CSS selector; delays until element appears in DOM |
87+
| `emulatedMediaFeatures` | `HtmlConversionBehaviors` | json | None | JSON array to override CSS media features |
88+
| `failOnHttpStatusCodes` | `HtmlConversionBehaviors` | json | [499,599] | HTTP status code ranges that trigger 409 |
89+
| `failOnResourceHttpStatusCodes` | `HtmlConversionBehaviors` | json | None | Asset status codes that trigger failure |
90+
| `ignoreResourceHttpStatusDomains` | `HtmlConversionBehaviors` | json | None | Domains excluded from status checks |
91+
| `failOnResourceLoadingFailed` | `HtmlConversionBehaviors` | bool | false | Fail if any resource fails to load |
92+
93+
These are simple -- just add `[MultiFormHeader("fieldName")]` properties to `HtmlConversionBehaviors` and corresponding methods to `HtmlConversionBehaviorBuilder`.
94+
95+
### 3. Standalone PDF Manipulation Routes (NEW route category)
96+
97+
All under `/forms/pdfengines/...`. These are **independent operations**, not inline options on conversion routes.
98+
99+
| Route | API Path | Description |
100+
|-------|----------|-------------|
101+
| Write Metadata | `POST /forms/pdfengines/metadata/write` | Write metadata to existing PDFs |
102+
| Read Metadata | `POST /forms/pdfengines/metadata/read` | Read metadata from PDFs (returns JSON) |
103+
| Read Bookmarks | `POST /forms/pdfengines/bookmarks/read` | Read bookmarks (returns JSON) |
104+
| Write Bookmarks | `POST /forms/pdfengines/bookmarks/write` | Write bookmarks to PDFs |
105+
| Attachments | `POST /forms/pdfengines/attachments` | Embed files into PDFs |
106+
| Flatten | `POST /forms/pdfengines/flatten` | Flatten PDFs standalone |
107+
| Watermark | `POST /forms/pdfengines/watermark` | Add watermarks to PDFs |
108+
| Stamp | `POST /forms/pdfengines/stamp` | Add stamps (foreground) to PDFs |
109+
| Rotate | `POST /forms/pdfengines/rotate` | Rotate PDF pages |
110+
| Split | `POST /forms/pdfengines/split` | Split PDFs into parts |
111+
| PDF/A & PDF/UA | `POST /forms/pdfengines/convert` | Already supported |
112+
| Encrypt | `POST /forms/pdfengines/encrypt` | Password-protect PDFs |
113+
114+
**Implementation approach:** Each standalone route needs its own request class and builder. Some return JSON (read metadata, read bookmarks) rather than PDF streams -- the client will need methods that return parsed objects, not just `Stream`.
115+
116+
### 4. Watermark & Stamp Fields (cross-cutting, apply to many routes)
117+
118+
These apply as inline options on Chromium, LibreOffice, AND PDF engine routes:
119+
120+
**Watermark fields:**
121+
| Field | Type | Description |
122+
|-------|------|-------------|
123+
| `watermarkSource` | enum | `text`, `image`, or `pdf` |
124+
| `watermarkExpression` | string | Text content or uploaded filename |
125+
| `watermarkPages` | string | Page ranges (e.g., `1-3`, `5`) |
126+
| `watermarkOptions` | json | Advanced: font, color, rotation, opacity |
127+
| watermark file | file | Image or PDF file for watermark |
128+
129+
**Stamp fields:** Same structure as watermark with `stamp` prefix.
130+
131+
**Implementation approach:** Create a `WatermarkOptions` facet and `StampOptions` facet. Add to `BuildRequestBase` alongside `PdfOutputOptions` since they're cross-cutting.
132+
133+
### 5. Rotation Fields (cross-cutting)
134+
135+
| Field | Type | Description |
136+
|-------|------|-------------|
137+
| `rotateAngle` | enum | `90`, `180`, or `270` |
138+
| `rotatePages` | string | Page ranges to rotate |
139+
140+
Add as a `RotationOptions` facet on `BuildRequestBase`.
141+
142+
### 6. Split Fields (cross-cutting)
143+
144+
| Field | Type | Description |
145+
|-------|------|-------------|
146+
| `splitMode` | enum | `intervals` or `pages` |
147+
| `splitSpan` | string | The rule for splitting |
148+
| `splitUnify` | boolean | Merge extracted pages into single file |
149+
150+
Add as a `SplitOptions` facet on `BuildRequestBase`. **Note**: When splitting returns multiple files, Gotenberg returns a ZIP. The client will need to handle this.
151+
152+
### 7. Encryption Fields (cross-cutting)
153+
154+
| Field | Type | Description |
155+
|-------|------|-------------|
156+
| `userPassword` | string | Password required to open the PDF |
157+
| `ownerPassword` | string | Password for changing permissions |
158+
159+
Add to `PdfOutputOptions` (they're PDF output concerns).
160+
161+
### 8. Embed Files
162+
163+
| Field | Type | Description |
164+
|-------|------|-------------|
165+
| `embeds` | file[] | Files to embed/attach inside the PDF |
166+
167+
This is a file upload field, not a simple string. Needs special handling similar to how `Assets` works.
168+
169+
### 9. Output Filename Header
170+
171+
| Header | Description |
172+
|--------|-------------|
173+
| `Gotenberg-Output-Filename` | Control the output filename (auto-appends extension) |
174+
175+
Already partially supported via `RequestConfig.ResultFileName` which sends it as a form field. Gotenberg accepts it both ways. May want to verify this works correctly or switch to sending it as an HTTP header.
176+
177+
### 10. Missing LibreOffice-Specific Fields
178+
179+
These only apply to the LibreOffice route (`/forms/libreoffice/convert`):
180+
181+
**Layout:**
182+
| Field | Type | Default |
183+
|-------|------|---------|
184+
| `singlePageSheets` | bool | false |
185+
| `skipEmptyPages` | bool | false |
186+
| `exportPlaceholders` | bool | false |
187+
188+
**Image compression:**
189+
| Field | Type | Default |
190+
|-------|------|---------|
191+
| `losslessImageCompression` | bool | false |
192+
| `quality` | int | 90 |
193+
| `reduceImageResolution` | bool | false |
194+
| `maxImageResolution` | int | 300 |
195+
196+
**Notes/slides (Impress/Writer):**
197+
| Field | Type | Default |
198+
|-------|------|---------|
199+
| `exportNotes` | bool | false |
200+
| `exportNotesPages` | bool | false |
201+
| `exportOnlyNotesPages` | bool | false |
202+
| `exportNotesInMargin` | bool | false |
203+
| `exportHiddenSlides` | bool | false |
204+
205+
**Links:**
206+
| Field | Type | Default |
207+
|-------|------|---------|
208+
| `convertOooTargetToPdfTarget` | bool | false |
209+
| `exportLinksRelativeFsys` | bool | false |
210+
211+
**Document outline:**
212+
| Field | Type | Default |
213+
|-------|------|---------|
214+
| `updateIndexes` | bool | true |
215+
| `exportBookmarks` | bool | true |
216+
| `exportBookmarksToPdfDestination` | bool | false |
217+
| `addOriginalDocumentAsStream` | bool | false |
218+
219+
**Form fields:**
220+
| Field | Type | Default |
221+
|-------|------|---------|
222+
| `exportFormFields` | bool | true |
223+
| `allowDuplicateFieldNames` | bool | false |
224+
225+
**Native watermark (LibreOffice-specific, different from the cross-cutting watermark):**
226+
| Field | Type | Default |
227+
|-------|------|---------|
228+
| `nativeWatermarkText` | string | None |
229+
| `nativeWatermarkColor` | int | 8388223 |
230+
| `nativeWatermarkFontHeight` | int | 0 (auto) |
231+
| `nativeWatermarkRotateAngle` | int | 0 |
232+
| `nativeWatermarkFontName` | string | Helvetica |
233+
| `nativeTiledWatermarkText` | string | None |
234+
235+
**Source file password:**
236+
| Field | Type | Description |
237+
|-------|------|-------------|
238+
| `password` | string | Password to open password-protected source files |
239+
240+
**Implementation approach:** Create a `LibreOfficeOptions` facet using `FacetBase`/attributes. Add it to `MergeOfficeRequest` (not to `BuildRequestBase` -- these are LibreOffice-specific). Create a `LibreOfficeOptionsBuilder` and expose it on `MergeOfficeBuilder`.
241+
242+
---
243+
244+
## Client-Level Changes Needed
245+
246+
### `GotenbergSharpClient` Response Handling
247+
248+
The client currently:
249+
1. Hardcodes `Accept: application/pdf` in the constructor.
250+
2. Always returns `Stream` (via `MemoryStream`).
251+
252+
For new features, it needs to handle:
253+
- **Screenshots**: Returns PNG/JPEG/WebP image streams. Remove or make the Accept header conditional.
254+
- **Read Metadata / Read Bookmarks**: Returns JSON. Need methods that return deserialized objects.
255+
- **Split PDFs**: Can return a ZIP when multiple files result. Consider returning the raw stream and letting callers handle it, or providing a helper.
256+
257+
### Suggested Method Additions on `GotenbergSharpClient`
258+
259+
```csharp
260+
// Screenshots
261+
Task<Stream> ScreenshotUrlAsync(ScreenshotUrlRequest request, ...)
262+
Task<Stream> ScreenshotHtmlAsync(ScreenshotHtmlRequest request, ...)
263+
264+
// Standalone PDF operations
265+
Task<Stream> WatermarkPdfsAsync(WatermarkPdfRequest request, ...)
266+
Task<Stream> StampPdfsAsync(StampPdfRequest request, ...)
267+
Task<Stream> RotatePdfsAsync(RotatePdfRequest request, ...)
268+
Task<Stream> SplitPdfsAsync(SplitPdfRequest request, ...)
269+
Task<Stream> EncryptPdfsAsync(EncryptPdfRequest request, ...)
270+
Task<Stream> FlattenPdfsAsync(FlattenPdfRequest request, ...)
271+
Task<Stream> WriteMetadataAsync(WriteMetadataPdfRequest request, ...)
272+
Task<JObject> ReadMetadataAsync(ReadMetadataPdfRequest request, ...)
273+
Task<Stream> WriteBookmarksAsync(WriteBookmarksPdfRequest request, ...)
274+
Task<JArray> ReadBookmarksAsync(ReadBookmarksPdfRequest request, ...)
275+
Task<Stream> AttachFilesAsync(AttachFilesPdfRequest request, ...)
276+
```
277+
278+
---
279+
280+
## Suggested Implementation Order
281+
282+
1. **Chromium missing fields** (waitForSelector, failOn*, emulatedMediaFeatures) -- Easiest. Just add properties to existing facets.
283+
2. **Encryption fields** (userPassword, ownerPassword) -- Add to `PdfOutputOptions`.
284+
3. **LibreOffice options** -- New facet, lots of simple fields.
285+
4. **Cross-cutting options** (watermark, stamp, rotation, split) -- New facets on `BuildRequestBase`.
286+
5. **Screenshot routes** -- New request/builder hierarchy + client response handling changes.
287+
6. **Standalone PDF manipulation routes** -- New request classes for each route + JSON response handling.
288+
289+
---
290+
291+
## Reference URLs
292+
293+
- Gotenberg docs home: https://gotenberg.dev/docs/getting-started/introduction
294+
- Chromium URL to PDF: https://gotenberg.dev/docs/convert-with-chromium/convert-url-to-pdf
295+
- Chromium screenshots: https://gotenberg.dev/docs/convert-with-chromium/screenshot-url
296+
- LibreOffice: https://gotenberg.dev/docs/convert-with-libreoffice/convert-to-pdf
297+
- PDF manipulation: https://gotenberg.dev/docs/manipulate-pdfs/write-metadata (sidebar has all routes)
298+
- Webhook & Download: https://gotenberg.dev/docs/webhook-download

0 commit comments

Comments
 (0)