Published on

Generate High-Quality PDFs with Playwright Custom Variables

Creating high-quality PDFs with dynamic content is essential for modern business applications—from personalized reports to branded invoices and complex documentation.

In this advanced guide, you'll learn how to generate professional PDFs using Playwright Java with custom variables, enabling you to create dynamic, branded documents with consistent styling and personalized content. We'll also explore how LetMePDF.com can simplify this process for production environments.


🎯 Why Custom Variables Matter

Custom variables in PDF generation allow you to:

  • Personalize content with user-specific data
  • Maintain brand consistency across all documents
  • Scale efficiently with reusable templates
  • Reduce code duplication through parameterized generation

🛠️ Advanced PDF Generation Setup

1. Enhanced Playwright Configuration

Start with a more robust Playwright setup for high-quality output:

import com.microsoft.playwright.*;
import java.nio.file.Paths;
import java.util.Map;
import java.util.HashMap;

public class AdvancedPdfGenerator {
    private final Playwright playwright;
    private final Browser browser;

    public AdvancedPdfGenerator() {
        this.playwright = Playwright.create();
        this.browser = playwright.chromium().launch(new BrowserType.LaunchOptions()
            .setHeadless(true)
            .setArgs("--no-sandbox", "--disable-dev-shm-usage"));
    }

    public void generatePdf(Map<String, Object> variables, String outputPath) {
        try (BrowserContext context = browser.newContext()) {
            Page page = context.newPage();

            // Set viewport for consistent rendering
            page.setViewportSize(1200, 800);

            String htmlContent = buildHtmlWithVariables(variables);
            page.setContent(htmlContent);

            // High-quality PDF options
            page.pdf(new Page.PdfOptions()
                .setPath(Paths.get(outputPath))
                .setFormat("A4")
                .setPrintBackground(true)
                .setMargin(new Page.PdfOptions.Margin()
                    .setTop("1in")
                    .setBottom("1in")
                    .setLeft("1in")
                    .setRight("1in"))
                .setPreferCssPageSize(true));
        }
    }

    private String buildHtmlWithVariables(Map<String, Object> variables) {
        return String.format("""
            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="UTF-8">
                <style>
                    @page {
                        size: A4;
                        margin: 1in;
                    }

                    body {
                        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                        line-height: 1.6;
                        color: #333;
                        margin: 0;
                        padding: 20px;
                    }

                    .header {
                        text-align: center;
                        border-bottom: 2px solid %s;
                        padding-bottom: 20px;
                        margin-bottom: 30px;
                    }

                    .company-logo {
                        max-width: 200px;
                        height: auto;
                    }

                    .invoice-details {
                        display: flex;
                        justify-content: space-between;
                        margin-bottom: 30px;
                    }

                    .customer-info {
                        background-color: #f8f9fa;
                        padding: 15px;
                        border-radius: 5px;
                        margin-bottom: 20px;
                    }

                    .items-table {
                        width: 100%%;
                        border-collapse: collapse;
                        margin: 20px 0;
                    }

                    .items-table th,
                    .items-table td {
                        border: 1px solid #ddd;
                        padding: 12px;
                        text-align: left;
                    }

                    .items-table th {
                        background-color: %s;
                        color: white;
                        font-weight: bold;
                    }

                    .total-section {
                        text-align: right;
                        margin-top: 30px;
                        font-size: 18px;
                        font-weight: bold;
                    }

                    .footer {
                        margin-top: 50px;
                        text-align: center;
                        font-size: 12px;
                        color: #666;
                        border-top: 1px solid #ddd;
                        padding-top: 20px;
                    }
                </style>
            </head>
            <body>
                <div class="header">
                    <h1>%s</h1>
                    <p>%s</p>
                </div>

                <div class="invoice-details">
                    <div>
                        <h3>Invoice Details</h3>
                        <p><strong>Invoice #:</strong> %s</p>
                        <p><strong>Date:</strong> %s</p>
                        <p><strong>Due Date:</strong> %s</p>
                    </div>
                    <div>
                        <h3>Company Info</h3>
                        <p>%s</p>
                        <p>%s</p>
                        <p>%s</p>
                    </div>
                </div>

                <div class="customer-info">
                    <h3>Bill To:</h3>
                    <p><strong>%s</strong></p>
                    <p>%s</p>
                    <p>%s</p>
                </div>

                <table class="items-table">
                    <thead>
                        <tr>
                            <th>Description</th>
                            <th>Quantity</th>
                            <th>Unit Price</th>
                            <th>Total</th>
                        </tr>
                    </thead>
                    <tbody>
                        %s
                    </tbody>
                </table>

                <div class="total-section">
                    <p>Subtotal: $%.2f</p>
                    <p>Tax (%.1f%%): $%.2f</p>
                    <p>Total: $%.2f</p>
                </div>

                <div class="footer">
                    <p>Thank you for your business!</p>
                    <p>%s</p>
                </div>
            </body>
            </html>
            """,
            variables.get("primaryColor"),
            variables.get("primaryColor"),
            variables.get("companyName"),
            variables.get("companyTagline"),
            variables.get("invoiceNumber"),
            variables.get("invoiceDate"),
            variables.get("dueDate"),
            variables.get("companyAddress"),
            variables.get("companyPhone"),
            variables.get("companyEmail"),
            variables.get("customerName"),
            variables.get("customerAddress"),
            variables.get("customerPhone"),
            variables.get("itemsHtml"),
            variables.get("subtotal"),
            variables.get("taxRate"),
            variables.get("taxAmount"),
            variables.get("total"),
            variables.get("footerText")
        );
    }
}

2. Dynamic Content Generation

Create a service to handle variable substitution and content generation:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.ArrayList;

public class InvoicePdfService {

    public Map<String, Object> createInvoiceVariables(InvoiceData invoice) {
        Map<String, Object> variables = new HashMap<>();

        // Company branding
        variables.put("primaryColor", "#2563eb");
        variables.put("companyName", "LetMePDF Solutions");
        variables.put("companyTagline", "Professional PDF Generation Services");
        variables.put("companyAddress", "123 Business Street, Tech City, TC 12345");
        variables.put("companyPhone", "+1 (555) 123-4567");
        variables.put("companyEmail", "contact@letmepdf.com");

        // Invoice details
        variables.put("invoiceNumber", invoice.getInvoiceNumber());
        variables.put("invoiceDate", formatDate(invoice.getInvoiceDate()));
        variables.put("dueDate", formatDate(invoice.getDueDate()));

        // Customer information
        variables.put("customerName", invoice.getCustomerName());
        variables.put("customerAddress", invoice.getCustomerAddress());
        variables.put("customerPhone", invoice.getCustomerPhone());

        // Items and calculations
        variables.put("itemsHtml", generateItemsHtml(invoice.getItems()));
        variables.put("subtotal", calculateSubtotal(invoice.getItems()));
        variables.put("taxRate", invoice.getTaxRate());
        variables.put("taxAmount", calculateTax(invoice.getItems(), invoice.getTaxRate()));
        variables.put("total", calculateTotal(invoice.getItems(), invoice.getTaxRate()));

        // Footer
        variables.put("footerText", "Payment due within 30 days. Late payments subject to 1.5%% monthly interest.");

        return variables;
    }

    private String generateItemsHtml(List<InvoiceItem> items) {
        StringBuilder html = new StringBuilder();
        for (InvoiceItem item : items) {
            html.append(String.format("""
                <tr>
                    <td>%s</td>
                    <td>%d</td>
                    <td>$%.2f</td>
                    <td>$%.2f</td>
                </tr>
                """,
                item.getDescription(),
                item.getQuantity(),
                item.getUnitPrice(),
                item.getQuantity() * item.getUnitPrice()
            ));
        }
        return html.toString();
    }

    private double calculateSubtotal(List<InvoiceItem> items) {
        return items.stream()
            .mapToDouble(item -> item.getQuantity() * item.getUnitPrice())
            .sum();
    }

    private double calculateTax(List<InvoiceItem> items, double taxRate) {
        return calculateSubtotal(items) * (taxRate / 100.0);
    }

    private double calculateTotal(List<InvoiceItem> items, double taxRate) {
        return calculateSubtotal(items) + calculateTax(items, taxRate);
    }

    private String formatDate(LocalDate date) {
        return date.format(DateTimeFormatter.ofPattern("MMMM dd, yyyy"));
    }
}

3. Complete Usage Example

Here's how to use the advanced PDF generator:

public class AdvancedPdfExample {
    public static void main(String[] args) {
        // Create sample invoice data
        InvoiceData invoice = new InvoiceData();
        invoice.setInvoiceNumber("INV-2025-001");
        invoice.setInvoiceDate(LocalDate.now());
        invoice.setDueDate(LocalDate.now().plusDays(30));
        invoice.setCustomerName("John Doe");
        invoice.setCustomerAddress("456 Customer Ave, Client City, CC 67890");
        invoice.setCustomerPhone("+1 (555) 987-6543");
        invoice.setTaxRate(8.5);

        List<InvoiceItem> items = new ArrayList<>();
        items.add(new InvoiceItem("Premium PDF Generation Service", 1, 299.99));
        items.add(new InvoiceItem("Custom Template Design", 2, 150.00));
        items.add(new InvoiceItem("API Integration Support", 1, 199.99));
        invoice.setItems(items);

        // Generate PDF
        InvoicePdfService pdfService = new InvoicePdfService();
        AdvancedPdfGenerator generator = new AdvancedPdfGenerator();

        Map<String, Object> variables = pdfService.createInvoiceVariables(invoice);
        generator.generatePdf(variables, "professional-invoice.pdf");

        System.out.println("✅ Professional invoice PDF generated!");
    }
}

🎨 Customization Options

Brand Customization

// Different brand themes
Map<String, Object> modernTheme = Map.of(
    "primaryColor", "#3b82f6",
    "fontFamily", "'Inter', sans-serif"
);

Map<String, Object> corporateTheme = Map.of(
    "primaryColor", "#1f2937",
    "fontFamily", "'Roboto', sans-serif"
);

Layout Customization

// Custom page sizes and margins
page.pdf(new Page.PdfOptions()
    .setFormat("Letter")
    .setMargin(new Page.PdfOptions.Margin()
        .setTop("0.5in")
        .setBottom("0.5in")
        .setLeft("0.75in")
        .setRight("0.75in"))
    .setPrintBackground(true));

🚀 Production-Ready Alternative

For production environments, consider LetMePDF.com which offers:

  • Template Management: Pre-built templates with variable substitution
  • Brand Consistency: Centralized styling and branding
  • API Integration: Simple REST API for PDF generation
  • Scalability: Handles high-volume PDF generation

Example API usage with custom variables:

curl -X POST https://api.letmepdf.com/html-to-pdf \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "html": "<h1>{{company_name}}</h1><p>Invoice #{{invoice_number}}</p>",
    "variables": {
      "company_name": "LetMePDF Solutions",
      "invoice_number": "INV-2025-001"
    },
    "options": {
      "format": "A4",
      "margin": "1in",
      "printBackground": true
    }
  }' --output invoice.pdf

✅ Best Practices

  1. Template Management: Store HTML templates separately from code
  2. Variable Validation: Always validate custom variables before substitution
  3. Error Handling: Implement proper error handling for missing variables
  4. Performance: Cache browser instances for high-volume generation
  5. Testing: Test PDF generation with various data scenarios

🎯 Conclusion

Custom variables in Playwright Java enable you to create professional, branded PDFs with dynamic content. This approach gives you complete control over styling, layout, and content generation.

For teams that need enterprise-grade PDF generation without the infrastructure overhead, LetMePDF.com provides a managed solution with advanced template features and variable substitution.

Start building professional PDFs today with Playwright Java, or explore LetMePDF's API for a production-ready alternative!

Further Reading: