Skip to main content

PDF Conversion

DocuDesk provides a reusable file-to-PDF conversion service (PdfConversionService) that converts any supported input file to PDF/A-3b using a cascade of configurable backends.

Architecture

The service implements a cascade pattern: backends are tried in priority order. The first backend that is available, claims the input format, and successfully converts the file wins. If every backend fails or declines, ConversionFailedException is thrown with structured per-backend attempt records suitable for a 422 response body.

Cascade Order

PriorityBackendIdentifierHandles
1OfficeAppBackendoffice_appAny format supported by Collabora / OnlyOffice / Euro Office (via NC IConversionManager, NC 31+)
2LibreOfficeHeadlessBackendlibreoffice_headlessDOC, DOCX, XLS, XLSX, PPT, PPTX, ODT, ODS, ODP, RTF, HTML, TXT, PNG, JPG
3PhpWordBackendphpwordDOCX, ODT, RTF, HTML, DOC (via phpoffice/phpword + mPDF)
4MpdfBackendmpdfHTML, XHTML, TXT, Markdown (direct mPDF rendering)
5EmlBackendemlEML (stubbed — activates when OpenRegister ships EML extraction)

Backend Interface

Every backend implements ConversionBackendInterface:

interface ConversionBackendInterface {
public function name(): string; // stable identifier
public function isAvailable(): bool; // runtime + tenant check
public function canHandle(string $mimeType, string $extension): bool;
public function convert(File $source): File;
}

PDF/A-3b Output

All backends are configured to emit PDF/A-3b (archival-grade PDF) where technically possible:

  • mPDF: 'PDFA' => true in the mPDF config.
  • LibreOffice headless: pdf:writer_pdf_Export:UseTaggedPDF=true,SelectPdfVersion=2 filter.
  • Office app: uses the provider's PDF/A-3b export variant where the underlying API supports it.

Tenant Configuration

Configuration is read at request time from IAppConfig (no restart required):

KeyDefaultDescription
docudesk.conversion.backends.office_app_enabledtrueEnable/disable Office app backend
docudesk.conversion.backends.libreoffice_enabledtrueEnable/disable LibreOffice headless backend
docudesk.conversion.backends.phpword_enabledtrueEnable/disable PhpWord backend
docudesk.conversion.backends.mpdf_enabledtrueEnable/disable mPDF backend
docudesk.conversion.backends.eml_enabledtrueEnable/disable EML backend (no-op until OR EML lands)
docudesk.conversion.libreoffice_binary_pathsofficePath to the soffice binary
docudesk.conversion.timeout_seconds60Per-backend conversion timeout in seconds

To disable a backend and force the cascade to skip it:

php occ config:app:set docudesk docudesk.conversion.backends.libreoffice_enabled --value=false

LibreOffice Headless Serialisation

LibreOffice's --headless mode does not handle concurrent invocations safely (user-profile lock contention). The LibreOfficeHeadlessBackend acquires a Nextcloud ILockingProvider lock keyed soffice:headless:convert before invoking soffice and releases it when the process exits (success or failure). If the lock cannot be acquired, the backend reports failure and the cascade falls through to the next tier.

Error Handling

When the cascade exhausts all backends, ConversionFailedException is thrown. It exposes:

public function getAttempts(): array;
// Returns: [{name, available, supports, reason}, ...]

Consumers (e.g. the anonymise endpoint) use this to construct a structured HTTP 422 body identifying exactly which backends were tried and why each one failed.

Dependencies

  • mpdf/mpdf ^8.2 — already required for print-preview.
  • phpoffice/phpword ^1.2 — added as part of this change for in-process Word-family conversion.
  • LibreOffice (optional) — system-level package; not bundled.
  • A Nextcloud Office app (optional) — Collabora, OnlyOffice, or Euro Office.

Cross-App Dependencies

The EmlBackend has a soft dependency on OpenRegister's forthcoming TextExtractionService::extractFile() support for message/rfc822. Until that capability is shipped in OpenRegister, EmlBackend::isAvailable() always returns false and EML inputs reach a ConversionFailedException. Track progress in the eml-pdf-assembly OpenSpec change.