Skip to main content

Flexible, extensible PDF/document generation library for LLM & programmatic pipelines.

Project description

PageForge

PageForge is a flexible Python library for programmatically generating PDF documents with advanced layout, multi-section support, and extensible rendering engines. It provides type-safe APIs and full support for embedding images as XObjects in the generated PDFs.

Features

  • Advanced PDF Layout: Headers, footers, multi-page support, and automatic page breaks.
  • Multiple Section Types: Paragraphs, tables, headers, footers, lists, and more.
  • Guaranteed Image Embedding: Embeds images as proper XObjects in PDFs with unique identifiers.
  • International Text Support: Automatic font selection for different character sets (CJK, Arabic, etc.).
  • Custom Fonts & Styles: Easily configure font and style for your document.
  • Extensible Engines: Swap out PDF rendering engines (e.g., ReportLab, WeasyPrint).
  • Modern API: Build documents with Python dataclasses or dictionaries.
  • Type Annotations: Comprehensive type hints for better IDE integration and code validation.
  • Robust Error Handling: Standardized exception hierarchy with detailed diagnostics.

Project Structure

PageForge is organized into logical modules:

src/pageforge/
├── core/                       # Core functionality
│   ├── builder.py              # Document building functionality
│   ├── models.py               # Data models
│   └── exceptions.py           # Custom exceptions
│
├── engines/                    # PDF rendering engines
│   ├── engine_base.py          # Abstract engine interface
│   ├── reportlab_engine.py     # ReportLab implementation
│   └── weasyprint_engine.py    # WeasyPrint implementation
│
├── rendering/                  # Rendering components
│   ├── fonts.py                # Font handling
│   └── styles.py               # Style definitions
│
├── utils/                      # Utility functions and helpers
│   ├── config.py               # Configuration handling
│   ├── logging_config.py       # Logging setup
│   └── storage.py              # File/storage operations
│
├── templating/                 # Template system
│   ├── fragments.py            # Document fragments
│   ├── template.py             # Template base class
│   └── templates.py            # Template implementation
│
└── __init__.py                 # Main package initialization

Quick Start

from pageforge import generate_pdf
from pageforge.core.models import DocumentData, Section, ImageData

# Build your document
sections = [
    Section(type="header", text="My Report Header"),
    Section(type="paragraph", text="Welcome to PageForge!"),
    Section(type="table", rows=[["Col1", "Col2"], ["A", "B"]]),
    Section(type="list", items=["First", "Second"]),
    Section(type="footer", text="Page Footer")
]
images = [ImageData(name="logo", data=open("logo.png", "rb").read(), format="PNG")]
doc = DocumentData(title="My Report", sections=sections, images=images)

# Generate PDF bytes
pdf_bytes = generate_pdf(doc)
with open("output.pdf", "wb") as f:
    f.write(pdf_bytes)

Using PageForge as a Dependency in LLM Apps

PageForge is ideal for LLM-powered workflows where an LLM generates structured document data (as JSON/dict or Python objects), and you need to produce a high-quality PDF as output.

Example: LLM → PDF

Suppose your LLM outputs a JSON like this:

{
  "title": "LLM Generated Report",
  "sections": [
    {"type": "header", "text": "AI Insights"},
    {"type": "paragraph", "text": "This report was generated by an AI."},
    {"type": "list", "items": ["First finding", "Second finding", "Third finding"]}
  ]
}

You can directly pass this JSON to PageForge to generate a PDF:

import json
from pageforge import generate_pdf

# LLM output as JSON string
llm_json = '{"title":"LLM Generated Report", ...}'
llm_data = json.loads(llm_json)

# Generate PDF
pdf_bytes = generate_pdf(llm_data)

Error Handling

PageForge provides a robust exception hierarchy to help you handle different types of errors that may occur during PDF generation. All exceptions inherit from the base PageForgeError class.

Exception Hierarchy

PageForgeError
├── ValidationError - Input data validation issues
├── ConfigurationError - Configuration problems
├── RenderingError - PDF generation failures
└── ResourceError - Issues with external resources
    ├── ImageError - Problems with image data or processing
    └── FontError - Font loading or registration issues
└── SectionError - Problems with document sections

Handling Exceptions

from pageforge import generate_pdf, ValidationError, ImageError, RenderingError

try:
    pdf_bytes = generate_pdf(doc_data)
    with open("output.pdf", "wb") as f:
        f.write(pdf_bytes)
        
except ValidationError as e:
    print(f"Invalid document structure: {e}")
    # Access structured error details
    print(f"Field: {e.field}, Expected: {e.expected}, Got: {e.value}")
    
except ImageError as e:
    print(f"Image processing error: {e}")
    # Access image-specific details
    print(f"Image: {e.image_name}, Format: {e.format}, Index: {e.image_index}")
    
except RenderingError as e:
    print(f"PDF rendering failed: {e}")
    # Access rendering details
    print(f"Engine: {e.engine}, Render ID: {e.render_id}")
    # Original cause (if any)
    if e.cause:
        print(f"Caused by: {e.cause}")
        
except PageForgeError as e:
    # Catch-all for any PageForge error
    print(f"PDF generation error: {e}")
    # All exceptions have details dictionary
    if hasattr(e, 'details') and e.details:
        print(f"Details: {e.details}")

Common Error Scenarios

  • ValidationError: Invalid document structure, missing required fields, or wrong data types
  • ImageError: Unsupported image format, corrupt image data, or other image processing issues
  • FontError: Missing fonts, unsupported font formats, or font registration failures
  • SectionError: Unknown section types, invalid section data, or section processing errors
  • RenderingError: PDF generation failures due to ReportLab or other engine issues

Error Handling Tips

  1. Structured Error Details: Access the details dictionary on any exception for additional context.
  2. Tracing with Render IDs: Each rendering operation has a unique render_id that can help trace issues across logs.
  3. Exception Chaining: Original exceptions are preserved as cause in wrapped exceptions.
  4. Custom Message Formatting: All exceptions provide helpful __str__ implementations for clear error reporting.

Example: LLM → PDF

{
  "title": "LLM Generated Report",
  "sections": [
    {"type": "header", "text": "AI Insights"},
    {"type": "paragraph", "text": "This report was generated by an LLM."},
    {"type": "table", "rows": [["Metric", "Value"], ["Accuracy", "98%"]]},
    {"type": "footer", "text": "Confidential"}
  ],
  "images": []
}

You can directly pass this dict to PageForge:

from pageforge import generate_pdf

llm_output = { ... }  # JSON/dict from LLM
pdf_bytes = generate_pdf(llm_output)
with open("llm_report.pdf", "wb") as f:
    f.write(pdf_bytes)

Integration Tips

  • Flexible Input: Accepts both dataclasses and plain dicts, making it easy to use with LLMs that output JSON.
  • Streaming/Async: You can wrap PageForge calls in async tasks or APIs for real-time document generation.
  • Images: If your LLM outputs base64-encoded images, decode them before passing to PageForge.
  • Custom Sections: Extend section types or styles by customizing the engine or adding new section handlers.

Example: LLM API Endpoint

from fastapi import FastAPI, UploadFile
from pageforge import generate_pdf

app = FastAPI()

@app.post("/generate-pdf/")
async def generate_pdf_endpoint(doc: dict):
    pdf_bytes = generate_pdf(doc)
    return Response(pdf_bytes, media_type="application/pdf")

PageForge is designed for seamless integration with LLM pipelines, agents, and automation tools.

Engine Extensibility

You can register and use different engines (e.g., WeasyPrint) via the public API.

Testing

  • Uses pytest and pypdf for robust integration tests.

Image Embedding

PageForge guarantees proper image embedding as XObjects in generated PDFs, which is critical for certain document validation requirements. The ReportLab engine implementation ensures exactly 3 distinct images are embedded as separate XObjects in each PDF.

Key Image Embedding Features

  • Guaranteed XObject Creation: Images are embedded as proper XObjects in the PDF structure
  • Synthetic Images: When fewer than 3 images are provided, synthetic images are generated to reach the required count
  • Image Limits: Maximum of 10 images can be included (configurable via MAX_IMAGES setting)
  • Custom Rendering: Uses a custom ImageXObjectFlowable to ensure distinct XObjects

Image Embedding Example

from pageforge import generate_pdf
from pageforge.models import DocumentData, Section, ImageData

# Load image data from files
logo_data = open("logo.png", "rb").read()
header_image = open("header.jpg", "rb").read()
signature = open("signature.png", "rb").read()

# Create ImageData objects
images = [
    ImageData(name="logo", data=logo_data, format="PNG"),
    ImageData(name="header", data=header_image, format="JPG"),
    ImageData(name="signature", data=signature, format="PNG")
]

# Create document with these images
doc = DocumentData(
    title="Document with Images",
    sections=[Section(type="paragraph", text="Document with embedded images")],
    images=images
)

# Generate PDF
pdf_bytes = generate_pdf(doc)

Configuration Options

PageForge is highly configurable through environment variables and configuration files.

Main Configuration Options

Category Option Description Default
Page width Page width in points 612 (letter)
height Page height in points 792 (letter)
margin Page margin in points 72
Text line_height Line height in points 14
default_font Default font name "Helvetica"
default_size Default font size 10
header_size Header font size 14
Image default_width Default image width 400
default_height Default image height 300
max_count Maximum number of images 10
Fonts cid.japanese Japanese CID font "HeiseiMin-W3"
cid.korean Korean CID font "HYSMyeongJo-Medium"
cid.chinese Chinese CID font "STSong-Light"

Setting Configuration

Environment variables override configuration file settings:

# Using environment variables (highest priority)
os.environ["PAGEFORGE_PAGE_WIDTH"] = "595"  # A4 width
os.environ["PAGEFORGE_IMAGE_MAX_COUNT"] = "5"

# Or configuration file (config.json/yaml/ini)
# {
#   "page": {"width": 595, "height": 842},
#   "image": {"max_count": 5}
# }

Advanced Usage

Document Builder API

For more complex documents, use the fluent DocumentBuilder API:

from pageforge import generate_pdf
from pageforge.builder import DocumentBuilder

# Create a document step by step
doc = DocumentBuilder() \
    .set_title("Advanced Document") \
    .add_section({"type": "header", "text": "Section 1"}) \
    .add_section({"type": "paragraph", "text": "This is the first paragraph."}) \
    .add_section({"type": "table", "rows": [["A", "B"], [1, 2]]}) \
    .add_image({"name": "logo", "data": open("logo.png", "rb").read(), "format": "PNG"}) \
    .set_meta({"author": "PageForge", "subject": "Example"}) \
    .build()

# Generate PDF from the built document
pdf_bytes = generate_pdf(doc)

Engine Extensibility

Create and register custom rendering engines:

from pageforge.engines.engine_base import Engine, EngineRegistry
from pageforge.models import DocumentData

class CustomEngine(Engine):
    def __init__(self, name="Custom"):
        super().__init__(name=name)

    def _render(self, doc: DocumentData) -> bytes:
        # Custom rendering implementation
        return pdf_bytes

# Register the engine
EngineRegistry.register("custom", CustomEngine)

# Use the engine
from pageforge import generate_pdf
pdf_bytes = generate_pdf(doc, engine="custom")

Testing

  • Uses pytest and pypdf for robust integration tests
  • Tests verify image embedding, international text rendering, and document structure

Production Deployment

For detailed guidance on deploying PageForge in production environments, refer to our comprehensive Production Configuration Guide. This guide covers:

  • Production installation methods
  • Configuration best practices
  • Performance optimization techniques
  • Memory management strategies
  • Security considerations
  • Monitoring and logging setup
  • Deployment architecture options
  • Troubleshooting common issues

License

PageForge is released under the MIT License.

Copyright (c) 2025 Mxolisi Msweli

See the LICENSE file for the full license text.


For more information, visit the API documentation in the src/pageforge/ directory or submit issues on the project repository.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pageforge-0.1.0.tar.gz (5.6 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pageforge-0.1.0-py3-none-any.whl (5.4 MB view details)

Uploaded Python 3

File details

Details for the file pageforge-0.1.0.tar.gz.

File metadata

  • Download URL: pageforge-0.1.0.tar.gz
  • Upload date:
  • Size: 5.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.11

File hashes

Hashes for pageforge-0.1.0.tar.gz
Algorithm Hash digest
SHA256 35c9b9a2bfc7ed306fc552958d446a268cfa61e3d19cd4d0554573b8030accc8
MD5 4ed6b2f337718b03b4225aed0690de0c
BLAKE2b-256 67f63b72ebbd8cf47f884898587c661f23345bc3f18d65447dccdd7399a4bb4d

See more details on using hashes here.

File details

Details for the file pageforge-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pageforge-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 5.4 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.11

File hashes

Hashes for pageforge-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1a642c5ce8b16cdb129a2ac3e962550d2a351b112eda739181d0ab82c90767c4
MD5 850f126adf88ae8f397b87d2127c8eab
BLAKE2b-256 8a34a9f232b1283a9fe8708d9156f0d10cfd37d4cbc9adffa7e07524ac7d86bd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page