# PDF Smith A powerful REST API service that generates PDF documents from dynamic HTML templates. PDF Smith supports multiple template engines, offers flexible PDF configuration options, and provides secure API key-based authentication with rate limiting. [](https://github.com/marcominerva/PdfSmith/actions/workflows/github-code-scanning/codeql) [](https://github.com/marcominerva/PdfSmith/actions/workflows/linter.yml) [](https://github.com/marcominerva/PdfSmith/actions/workflows/copilot-swe-agent/copilot) [](https://github.com/marcominerva/PdfSmith/actions/workflows/deploy.yml) ## 🚀 Features - **Dynamic PDF Generation**: Create PDFs from HTML templates with dynamic data injection - **Multiple Template Engines**: Support for Razor, Scriban, and Handlebars template engines - **Flexible PDF Options**: Configure page size, orientation, margins, and more - **API Key Authentication**: Secure access with subscription-based API keys - **Rate Limiting**: Configurable request limits per subscription - **Localization Support**: Multi-language template rendering - **Time Zone Support**: Correct handling and conversion of dates/times based on the user's specified time zone - **OpenAPI Documentation**: Built-in Swagger documentation ## 📋 Table of Contents - [Installation](#️-installation) - [Authentication](#-authentication) - [API Reference](#-api-reference) - [Template Engines](#-template-engines) - [PDF Configuration](#-pdf-configuration) - [Time Zone Support](#-time-zone-support) - [Usage Examples](#-usage-examples) - [Error Handling](#️-error-handling) - [Rate Limiting](#️-rate-limiting) - [Configuration](#️-configuration) ## 🛠️ Installation ### Prerequisites - .NET 10.0 SDK or later - Chromium browser (automatically installed via Playwright) ### Setup 1. Clone the repository: ```bash git clone https://github.com/marcominerva/PdfSmith.git cd PdfSmith ``` 2. Build the solution: ```bash dotnet build ``` 3. Configure your database connection in `appsettings.json`: ```json { "ConnectionStrings": { "SqlConnection": "your-connection-string-here" } } ``` 4. Run the application: ```bash dotnet run --project src/PdfSmith ``` The API will be available at `https://localhost:7226` (or your configured port). ## 🔐 Authentication PdfSmith uses API key authentication with subscription-based access control. ### API Key Setup 1. Include your API key in the request header: ``` x-api-key: your-api-key-here ``` 2. Each subscription has configurable rate limits: - **Requests per window**: Number of allowed requests - **Window duration**: Time window in minutes - **Validity period**: API key expiration dates ### Default Administrator Account A default administrator account is created automatically with the following configuration: - Username: Set via `AppSettings:AdministratorUserName` - API Key: Set via `AppSettings:AdministratorApiKey` - Default limits: 10 requests per minute ## 📚 API Reference ### Generate PDF **Endpoint:** `POST /api/pdf` **Headers:** - `x-api-key`: Your API key (required) - `Accept-Language`: Language preference (optional, e.g., "en-US", "it-IT") - `x-time-zone`: The IANA time zone identifier to handle different time zones (optional, if not present UTC will be used) **Request Body:** ```json { "template": "HTML template string", "model": { "key": "value", "nested": { "data": "structure" } }, "templateEngine": "razor", "fileName": "document.pdf", "options": { "pageSize": "A4", "orientation": "Portrait", "margin": { "top": "2.5cm", "bottom": "2cm", "left": "2cm", "right": "2cm" } } } ``` **Response:** - **Content-Type:** `application/pdf` - **Content-Disposition:** `attachment; filename="document.pdf"` - **Body:** PDF file binary data ## 🎨 Template Engines PdfSmith supports three powerful template engines: ### Razor Template Engine Razor provides C#-based templating with full programming capabilities. **Key:** `razor` **Example:** ```html
Order Date: @Model.Date.ToString("dd/MM/yyyy")
Total: @Model.Total.ToString("C")
``` ### Scriban Template Engine Scriban is a fast, powerful, safe, and lightweight text templating language. **Key:** `scriban` **Example:** ```htmlOrder Date: {{ Model.Date | date.to_string '%d/%m/%Y' }}
Total: {{ Model.Total | object.format "C" }}
``` ### Handlebars Template Engine Handlebars provides logic-less templates with a designer-friendly syntax, ideal for collaborative development workflows. **Key:** `handlebars` **Example:** ```htmlOrder Date: {{formatDate Model.Date "dd/MM/yyyy"}}
Total: {{formatCurrency Model.Total}}
``` **Built-in Helpers:** - `formatNumber` - Formats number values as string using the specified format and the current culture - `formatCurrency` - Formats decimal values as currency using current culture - `formatDate` - Formats dates with optional format string - `now` - Gets the current datetime with optional format string - `utcNow` - Gets the current UTC datetime with optional format string - `add` - Adds two numeric values for calculations within templates - `subtract` - Subtracts two numeric values for calculations within templates - `multiply` - Multiplies two numeric values for calculations within templates - `divide` - Divides two numeric values for calculations within templates - `round` - Rounds a numberic value to the specified number of decimals ## 📄 PDF Configuration ### Page Size Options - Standard sizes: `"A4"`, `"A3"`, `"A5"`, `"Letter"`, `"Legal"` - Custom sizes: `"210mm x 297mm"` or `"8.5in x 11in"` ### Orientation - `"Portrait"` (default) - `"Landscape"` ### Margins Configure margins using CSS units: ```json { "margin": { "top": "2.5cm", "bottom": "2cm", "left": "2cm", "right": "2cm" } } ``` Supported units: `mm`, `cm`, `in`, `px`, `pt`, `pc` ## 🕒 Time Zone Support When generating PDFs that include dates and times, it is important to ensure that these values are represented in the correct time zone for the end user. By default, .NET's `DateTime.Now` returns the server's local time, which may not match the user's intended time zone. Similarly, when converting or displaying dates from the input model, the correct time zone context is essential to avoid confusion or errors in generated documents. **PdfSmith** allows clients to specify the desired time zone using the `x-time-zone` HTTP header (with an [IANA time zone identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)). If this header is not provided, UTC is used by default. This ensures that: - All date and time values (such as those produced by `DateTime.Now` or parsed from the model) are converted and rendered according to the specified time zone. - Templates using date/time expressions (e.g., `@Model.Date`, `date.now`, or similar) will reflect the correct local time for the user, not just the server. - Consistency is maintained across different users and regions, especially for time-sensitive documents like invoices, reports, or logs. ## 💡 Usage Examples ### Basic PDF Generation ```csharp using System.Net.Http.Json; using PdfSmith.Shared.Models; var httpClient = new HttpClient(); httpClient.DefaultRequestHeaders.Add("x-api-key", "your-api-key"); httpClient.DefaultRequestHeaders.Add("x-time-zone", "Europe/Rome") var request = new PdfGenerationRequest( template: "Invoice #@Model.InvoiceNumber
Customer: @Model.CustomerName
Date: @Model.Date.ToString("dd/MM/yyyy")
| Item | Quantity | Price | Total |
|---|---|---|---|
| @item.Name | @item.Quantity | @item.Price.ToString("C") | @((item.Quantity * item.Price).ToString("C")) |
| Total: | @Model.Total.ToString("C") | ||
Invoice #{{Model.InvoiceNumber}}
Customer: {{Model.CustomerName}}
Date: {{formatDate Model.Date "dd/MM/yyyy"}}
| Item | Quantity | Price | Total |
|---|---|---|---|
| {{Name}} | {{Quantity}} | {{formatCurrency Price}} | {{formatCurrency (multiply Price Quantity)}} |
| Total: | {{formatCurrency Model.Total}} | ||