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
- Structured Error Details: Access the
detailsdictionary on any exception for additional context. - Tracing with Render IDs: Each rendering operation has a unique
render_idthat can help trace issues across logs. - Exception Chaining: Original exceptions are preserved as
causein wrapped exceptions. - 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
pytestandpypdffor 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_IMAGESsetting) - Custom Rendering: Uses a custom
ImageXObjectFlowableto 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
pytestandpypdffor 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
35c9b9a2bfc7ed306fc552958d446a268cfa61e3d19cd4d0554573b8030accc8
|
|
| MD5 |
4ed6b2f337718b03b4225aed0690de0c
|
|
| BLAKE2b-256 |
67f63b72ebbd8cf47f884898587c661f23345bc3f18d65447dccdd7399a4bb4d
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a642c5ce8b16cdb129a2ac3e962550d2a351b112eda739181d0ab82c90767c4
|
|
| MD5 |
850f126adf88ae8f397b87d2127c8eab
|
|
| BLAKE2b-256 |
8a34a9f232b1283a9fe8708d9156f0d10cfd37d4cbc9adffa7e07524ac7d86bd
|