package gotenberg import ( "context" "errors" "fmt" "log/slog" ) var ( // ErrPdfEngineMethodNotSupported is returned when a specific method of the // PdfEngine interface is not supported by its current implementation. ErrPdfEngineMethodNotSupported = errors.New("method not supported") // ErrPdfSplitModeNotSupported is returned when the Split method of the // PdfEngine interface does not support a requested PDF split mode. ErrPdfSplitModeNotSupported = errors.New("split mode not supported") // ErrPdfFormatNotSupported is returned when the Convert method of the // PdfEngine interface does not support a requested PDF format conversion. ErrPdfFormatNotSupported = errors.New("PDF format not supported") // ErrPdfEngineMetadataValueNotSupported is returned when a metadata value // is not supported. ErrPdfEngineMetadataValueNotSupported = errors.New("metadata value not supported") // ErrPdfEncryptionNotSupported is returned when encryption // is not supported by the PDF engine. ErrPdfEncryptionNotSupported = errors.New("encryption not supported") // ErrPdfStampSourceNotSupported is returned when a stamp source type // is not supported by the PDF engine. ErrPdfStampSourceNotSupported = errors.New("stamp source not supported") // ErrPdfRotateAngleNotSupported is returned when the rotation angle is // not supported. ErrPdfRotateAngleNotSupported = errors.New("rotation angle not supported") // ErrPdfFacturXValueNotSupported is returned when a Factur-X field value // (e.g., an unknown conformance level or document type) is not supported. ErrPdfFacturXValueNotSupported = errors.New("Factur-X value not supported") ) // PdfEngineInvalidArgsError represents an error returned by a PDF engine when // invalid arguments are provided. It includes the name of the engine and a // detailed message describing the issue. type PdfEngineInvalidArgsError struct { engine string msg string } // Error implements the error interface. func (e *PdfEngineInvalidArgsError) Error() string { return fmt.Sprintf("%s: %s", e.engine, e.msg) } // NewPdfEngineInvalidArgs creates a new PdfEngineInvalidArgsError with the // given engine name and message. func NewPdfEngineInvalidArgs(engine, msg string) error { return &PdfEngineInvalidArgsError{engine, msg} } const ( // StampSourceText represents a text-based stamp source. StampSourceText string = "text" // StampSourceImage represents an image-based stamp source. StampSourceImage string = "image" // StampSourcePDF represents a PDF-based stamp source. StampSourcePDF string = "pdf" ) // Stamp gathers the data required to apply a watermark or stamp to a PDF. type Stamp struct { // Source is one of "text", "image", or "pdf". Source string // Expression is the text content (for text source) or file path (for // image/pdf source). Expression string // Pages is the optional page range to apply the stamp to. Pages string // Options holds engine-specific styling options. Options map[string]string } const ( // SplitModeIntervals represents a mode where a PDF is split at specific // intervals. SplitModeIntervals string = "intervals" // SplitModePages represents a mode where a PDF is split at specific page // ranges. SplitModePages string = "pages" ) // SplitMode gathers the data required to split a PDF into multiple parts. type SplitMode struct { // Mode is either "intervals" or "pages". Mode string // Span is either the intervals or the page ranges to extract, depending on // the selected mode. Span string // Unify specifies whether to put extracted pages into a single file or as // many files as there are page ranges. Only works with "pages" mode. Unify bool } const ( // PdfA1a represents the PDF/A-1a format. PdfA1a string = "PDF/A-1a" // PdfA1b represents the PDF/A-1b format. PdfA1b string = "PDF/A-1b" // PdfA2a represents the PDF/A-2a format. PdfA2a string = "PDF/A-2a" // PdfA2b represents the PDF/A-2b format. PdfA2b string = "PDF/A-2b" // PdfA2u represents the PDF/A-2u format. PdfA2u string = "PDF/A-2u" // PdfA3a represents the PDF/A-3a format. PdfA3a string = "PDF/A-3a" // PdfA3b represents the PDF/A-3b format. PdfA3b string = "PDF/A-3b" // PdfA3u represents the PDF/A-3u format. PdfA3u string = "PDF/A-3u" ) // PdfFormats specifies the target formats for a PDF conversion. type PdfFormats struct { // PdfA denotes the PDF/A standard format (e.g., PDF/A-1a). PdfA string // PdfUa indicates whether the PDF should comply // with the PDF/UA (Universal Accessibility) standard. PdfUa bool } // PdfPermissions gathers the document permissions enforced when a PDF is // encrypted. Each field defaults to true (the action is allowed); set a field // to false to restrict it. Restrictions are advisory: viewers honor them, but // they are not cryptographically enforced once the document opens. type PdfPermissions struct { // AllowPrinting permits printing the document. AllowPrinting bool // AllowCopying permits extracting text and graphics. AllowCopying bool // AllowModifying permits changing the document content. AllowModifying bool // AllowAnnotating permits adding or modifying annotations. AllowAnnotating bool // AllowFillingForms permits filling in form fields. AllowFillingForms bool // AllowAssembling permits inserting, deleting, and rotating pages. AllowAssembling bool } // Restricted reports whether at least one permission is denied. func (p PdfPermissions) Restricted() bool { return !p.AllowPrinting || !p.AllowCopying || !p.AllowModifying || !p.AllowAnnotating || !p.AllowFillingForms || !p.AllowAssembling } // EncryptOptions gathers the parameters for encrypting a PDF. An empty // UserPassword with a set OwnerPassword produces an owner-only document: it // opens without a password but enforces the [PdfPermissions]. type EncryptOptions struct { // UserPassword is required to open the document. Empty means no open // password. UserPassword string // OwnerPassword grants full access (lifts the permission restrictions). // When empty, it defaults to UserPassword. OwnerPassword string // Permissions are the actions allowed when opened with the user password. Permissions PdfPermissions } // Bookmark represents a node in the PDF document's outline // (table of contents). type Bookmark struct { Title string `json:"title"` Page int `json:"page"` Children []Bookmark `json:"children,omitempty"` } const ( // FacturXConformanceMinimum represents the MINIMUM Factur-X conformance level. FacturXConformanceMinimum string = "MINIMUM" // FacturXConformanceBasicWL represents the BASIC WL Factur-X conformance level. FacturXConformanceBasicWL string = "BASIC WL" // FacturXConformanceBasic represents the BASIC Factur-X conformance level. FacturXConformanceBasic string = "BASIC" // FacturXConformanceEN16931 represents the EN 16931 Factur-X conformance level. FacturXConformanceEN16931 string = "EN 16931" // FacturXConformanceExtended represents the EXTENDED Factur-X conformance level. FacturXConformanceExtended string = "EXTENDED" // FacturXConformanceXRechnung represents the XRECHNUNG Factur-X conformance level. FacturXConformanceXRechnung string = "XRECHNUNG" // FacturXDocumentTypeInvoice represents the INVOICE Factur-X document type. FacturXDocumentTypeInvoice string = "INVOICE" // FacturXDocumentTypeOrder represents the ORDER Factur-X document type. FacturXDocumentTypeOrder string = "ORDER" // FacturXDocumentTypeOrderResponse represents the ORDER_RESPONSE Factur-X document type. FacturXDocumentTypeOrderResponse string = "ORDER_RESPONSE" // FacturXDocumentTypeOrderChange represents the ORDER_CHANGE Factur-X document type. FacturXDocumentTypeOrderChange string = "ORDER_CHANGE" // FacturXDocumentFileName is the canonical name of the embedded XML invoice // mandated by the Factur-X standard. Validators expect this exact name. FacturXDocumentFileName string = "factur-x.xml" ) // FacturX gathers the properties required by the Factur-X/ZUGFeRD standard for // the document-level XMP metadata packet of a PDF/A-3. type FacturX struct { // ConformanceLevel is one of the FacturXConformance* values. ConformanceLevel string // DocumentType is one of the FacturXDocumentType* values. DocumentType string // DocumentFileName is the name of the embedded XML invoice. It is set // internally to the canonical [FacturXDocumentFileName], not by the caller. DocumentFileName string // Version is the Factur-X version (e.g., "1.0"). Version string } // PdfEngine provides an interface for operations on PDFs. Implementations // can use various tools like PDFtk, or implement functionality directly in // Go. // //nolint:dupl type PdfEngine interface { // Merge combines multiple PDFs into a single PDF. The resulting page order // is determined by the order of files provided in inputPaths. Merge(ctx context.Context, logger *slog.Logger, inputPaths []string, outputPath string) error // Split splits a given PDF file. Split(ctx context.Context, logger *slog.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) // Flatten merges existing annotation appearances with page content, // effectively deleting the original annotations. This process can flatten // forms as well as forms share a relationship with annotations. Note that // this operation is irreversible. Flatten(ctx context.Context, logger *slog.Logger, inputPath string) error // Convert transforms a given PDF to the specified formats defined in // PdfFormats. If no format, it does nothing. Convert(ctx context.Context, logger *slog.Logger, formats PdfFormats, inputPath, outputPath string) error // OptimizeImages re-encodes the raster images of a PDF in place to shrink // the file, leaving text, vectors, fonts and structure untouched. // imageQuality is the JPEG quality (1 to 100) applied to each re-encoded // image. OptimizeImages(ctx context.Context, logger *slog.Logger, imageQuality int, inputPath string) error // ReadMetadata extracts the metadata of a given PDF file. ReadMetadata(ctx context.Context, logger *slog.Logger, inputPath string) (map[string]any, error) // PageCount returns the number of pages in a PDF file. PageCount(ctx context.Context, logger *slog.Logger, inputPath string) (int, error) // WriteMetadata writes the metadata into a given PDF file. WriteMetadata(ctx context.Context, logger *slog.Logger, metadata map[string]any, inputPath string) error // ReadBookmarks reads the document outline (bookmarks) of a PDF file. ReadBookmarks(ctx context.Context, logger *slog.Logger, inputPath string) ([]Bookmark, error) // WriteBookmarks adds a document outline (bookmarks) to a PDF file. // The bookmarks parameter represents the hierarchical tree of the outline. WriteBookmarks(ctx context.Context, logger *slog.Logger, inputPath string, bookmarks []Bookmark) error // Encrypt adds password protection and permission restrictions to a PDF // file, as described by [EncryptOptions]. An empty user password with a set // owner password yields an owner-only document (opens without a password, // permissions enforced). Encrypt(ctx context.Context, logger *slog.Logger, inputPath string, opts EncryptOptions) error // EmbedFiles embeds files into a PDF. All files are embedded as file attachments // without modifying the main PDF content. // TODO: attachments instead? Rename the route? EmbedFiles(ctx context.Context, logger *slog.Logger, filePaths []string, inputPath string) error // EmbedFilesMetadata sets metadata (such as MIME type and AFRelationship) // on already-embedded files in a PDF. The metadata map is keyed by // filename, with each value being a map of property names to values // (e.g., "mimeType" and "relationship"). EmbedFilesMetadata(ctx context.Context, logger *slog.Logger, metadata map[string]map[string]string, inputPath string) error // Watermark applies a watermark (behind page content) to a PDF file. Watermark(ctx context.Context, logger *slog.Logger, inputPath string, stamp Stamp) error // Stamp applies a stamp (on top of page content) to a PDF file. Stamp(ctx context.Context, logger *slog.Logger, inputPath string, stamp Stamp) error // Rotate rotates pages of a PDF file by the given angle (90, 180, 270). // If pages is empty, all pages are rotated. Rotate(ctx context.Context, logger *slog.Logger, inputPath string, angle int, pages string) error // InjectFacturXXMP injects Factur-X/ZUGFeRD XMP metadata into the // document-level XMP packet (Catalog /Metadata stream) of a PDF/A-3. It // registers the fx namespace, the four fx properties, and the matching // PDF/A extension schema so the result stays PDF/A-valid. InjectFacturXXMP(ctx context.Context, logger *slog.Logger, facturX FacturX, inputPath string) error // ReadPdfAConformance reads the PDF/A part and conformance (e.g., "3" and // "B") from the document-level XMP packet (Catalog /Metadata stream, // pdfaid:part and pdfaid:conformance). It returns empty strings when the // document carries no PDF/A identification. ReadPdfAConformance(ctx context.Context, logger *slog.Logger, inputPath string) (part string, conformance string, err error) } // PdfEngineProvider offers an interface to instantiate a [PdfEngine]. // This is used to decouple the creation of a [PdfEngine] from its consumers. // // Example: // // func (m *YourModule) Provision(ctx *gotenberg.Context) error { // provider, _ := ctx.Module(new(gotenberg.PdfEngineProvider)) // engine, _ := provider.(gotenberg.PdfEngineProvider).PdfEngine() // } type PdfEngineProvider interface { // PdfEngine returns an instance of the [PdfEngine] interface for PDF operations. PdfEngine() (PdfEngine, error) }