mirror of
https://github.com/K-Dense-AI/claude-scientific-skills.git
synced 2026-03-27 07:09:27 +08:00
Add more scientific skills
This commit is contained in:
621
scientific-packages/reportlab/SKILL.md
Normal file
621
scientific-packages/reportlab/SKILL.md
Normal file
@@ -0,0 +1,621 @@
|
||||
---
|
||||
name: reportlab
|
||||
description: This skill provides comprehensive guidance for creating PDF documents using the ReportLab Python library. Use this skill when generating PDFs programmatically, including invoices, reports, certificates, labels, forms, and any document requiring precise layout control. The skill covers both low-level Canvas API for pixel-perfect positioning and high-level Platypus for flowing multi-page documents, along with tables, charts, barcodes, text formatting, and PDF features.
|
||||
---
|
||||
|
||||
# ReportLab PDF Generation
|
||||
|
||||
## Overview
|
||||
|
||||
ReportLab is a powerful Python library for programmatic PDF generation. Create anything from simple documents to complex reports with tables, charts, images, and interactive forms.
|
||||
|
||||
**Two main approaches:**
|
||||
- **Canvas API** (low-level): Direct drawing with coordinate-based positioning - use for precise layouts
|
||||
- **Platypus** (high-level): Flowing document layout with automatic page breaks - use for multi-page documents
|
||||
|
||||
**Core capabilities:**
|
||||
- Text with rich formatting and custom fonts
|
||||
- Tables with complex styling and cell spanning
|
||||
- Charts (bar, line, pie, area, scatter)
|
||||
- Barcodes and QR codes (Code128, EAN, QR, etc.)
|
||||
- Images with transparency
|
||||
- PDF features (links, bookmarks, forms, encryption)
|
||||
|
||||
## Choosing the Right Approach
|
||||
|
||||
### Use Canvas API when:
|
||||
- Creating labels, business cards, certificates
|
||||
- Precise positioning is critical (x, y coordinates)
|
||||
- Single-page documents or simple layouts
|
||||
- Drawing graphics, shapes, and custom designs
|
||||
- Adding barcodes or QR codes at specific locations
|
||||
|
||||
### Use Platypus when:
|
||||
- Creating multi-page documents (reports, articles, books)
|
||||
- Content should flow automatically across pages
|
||||
- Need headers/footers that repeat on each page
|
||||
- Working with paragraphs that can split across pages
|
||||
- Building complex documents with table of contents
|
||||
|
||||
### Use Both when:
|
||||
- Complex reports need both flowing content AND precise positioning
|
||||
- Adding headers/footers to Platypus documents (use `onPage` callback with Canvas)
|
||||
- Embedding custom graphics (Canvas) within flowing documents (Platypus)
|
||||
|
||||
## Quick Start Examples
|
||||
|
||||
### Simple Canvas Document
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
c = canvas.Canvas("output.pdf", pagesize=letter)
|
||||
width, height = letter
|
||||
|
||||
# Draw text
|
||||
c.setFont("Helvetica-Bold", 24)
|
||||
c.drawString(inch, height - inch, "Hello ReportLab!")
|
||||
|
||||
# Draw a rectangle
|
||||
c.setFillColorRGB(0.2, 0.4, 0.8)
|
||||
c.rect(inch, 5*inch, 4*inch, 2*inch, fill=1)
|
||||
|
||||
# Save
|
||||
c.showPage()
|
||||
c.save()
|
||||
```
|
||||
|
||||
### Simple Platypus Document
|
||||
|
||||
```python
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
doc = SimpleDocTemplate("output.pdf", pagesize=letter)
|
||||
story = []
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Add content
|
||||
story.append(Paragraph("Document Title", styles['Title']))
|
||||
story.append(Spacer(1, 0.2*inch))
|
||||
story.append(Paragraph("This is body text with <b>bold</b> and <i>italic</i>.", styles['BodyText']))
|
||||
|
||||
# Build PDF
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Creating Tables
|
||||
|
||||
Tables work with both Canvas (via Drawing) and Platypus (as Flowables):
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Table, TableStyle
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
# Define data
|
||||
data = [
|
||||
['Product', 'Q1', 'Q2', 'Q3', 'Q4'],
|
||||
['Widget A', '100', '150', '130', '180'],
|
||||
['Widget B', '80', '120', '110', '160'],
|
||||
]
|
||||
|
||||
# Create table
|
||||
table = Table(data, colWidths=[2*inch, 1*inch, 1*inch, 1*inch, 1*inch])
|
||||
|
||||
# Apply styling
|
||||
style = TableStyle([
|
||||
# Header row
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.darkblue),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
|
||||
# Data rows
|
||||
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.black),
|
||||
])
|
||||
|
||||
table.setStyle(style)
|
||||
|
||||
# Add to Platypus story
|
||||
story.append(table)
|
||||
|
||||
# Or draw on Canvas
|
||||
table.wrapOn(c, width, height)
|
||||
table.drawOn(c, x, y)
|
||||
```
|
||||
|
||||
**Detailed table reference:** See `references/tables_reference.md` for cell spanning, borders, alignment, and advanced styling.
|
||||
|
||||
### Creating Charts
|
||||
|
||||
Charts use the graphics framework and can be added to both Canvas and Platypus:
|
||||
|
||||
```python
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
||||
from reportlab.lib import colors
|
||||
|
||||
# Create drawing
|
||||
drawing = Drawing(400, 200)
|
||||
|
||||
# Create chart
|
||||
chart = VerticalBarChart()
|
||||
chart.x = 50
|
||||
chart.y = 50
|
||||
chart.width = 300
|
||||
chart.height = 125
|
||||
|
||||
# Set data
|
||||
chart.data = [[100, 150, 130, 180, 140]]
|
||||
chart.categoryAxis.categoryNames = ['Q1', 'Q2', 'Q3', 'Q4', 'Q5']
|
||||
|
||||
# Style
|
||||
chart.bars[0].fillColor = colors.blue
|
||||
chart.valueAxis.valueMin = 0
|
||||
chart.valueAxis.valueMax = 200
|
||||
|
||||
# Add to drawing
|
||||
drawing.add(chart)
|
||||
|
||||
# Use in Platypus
|
||||
story.append(drawing)
|
||||
|
||||
# Or render directly to PDF
|
||||
from reportlab.graphics import renderPDF
|
||||
renderPDF.drawToFile(drawing, 'chart.pdf', 'Chart Title')
|
||||
```
|
||||
|
||||
**Available chart types:** Bar (vertical/horizontal), Line, Pie, Area, Scatter
|
||||
**Detailed charts reference:** See `references/charts_reference.md` for all chart types, styling, legends, and customization.
|
||||
|
||||
### Adding Barcodes and QR Codes
|
||||
|
||||
```python
|
||||
from reportlab.graphics.barcode import code128
|
||||
from reportlab.graphics.barcode.qr import QrCodeWidget
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.graphics import renderPDF
|
||||
|
||||
# Code128 barcode (general purpose)
|
||||
barcode = code128.Code128("ABC123456789", barHeight=0.5*inch)
|
||||
|
||||
# On Canvas
|
||||
barcode.drawOn(c, x, y)
|
||||
|
||||
# QR Code
|
||||
qr = QrCodeWidget("https://example.com")
|
||||
qr.barWidth = 2*inch
|
||||
qr.barHeight = 2*inch
|
||||
|
||||
# Wrap in Drawing for Platypus
|
||||
d = Drawing()
|
||||
d.add(qr)
|
||||
story.append(d)
|
||||
```
|
||||
|
||||
**Supported formats:** Code128, Code39, EAN-13, EAN-8, UPC-A, ISBN, QR, Data Matrix, and 20+ more
|
||||
**Detailed barcode reference:** See `references/barcodes_reference.md` for all formats and usage examples.
|
||||
|
||||
### Working with Text and Fonts
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Paragraph
|
||||
from reportlab.lib.styles import ParagraphStyle
|
||||
from reportlab.lib.enums import TA_JUSTIFY
|
||||
|
||||
# Create custom style
|
||||
custom_style = ParagraphStyle(
|
||||
'CustomStyle',
|
||||
fontSize=12,
|
||||
leading=14, # Line spacing
|
||||
alignment=TA_JUSTIFY,
|
||||
spaceAfter=10,
|
||||
textColor=colors.black,
|
||||
)
|
||||
|
||||
# Paragraph with inline formatting
|
||||
text = """
|
||||
This paragraph has <b>bold</b>, <i>italic</i>, and <u>underlined</u> text.
|
||||
You can also use <font color="blue">colors</font> and <font size="14">different sizes</font>.
|
||||
Chemical formula: H<sub>2</sub>O, Einstein: E=mc<sup>2</sup>
|
||||
"""
|
||||
|
||||
para = Paragraph(text, custom_style)
|
||||
story.append(para)
|
||||
```
|
||||
|
||||
**Using custom fonts:**
|
||||
|
||||
```python
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
|
||||
# Register TrueType font
|
||||
pdfmetrics.registerFont(TTFont('CustomFont', 'CustomFont.ttf'))
|
||||
|
||||
# Use in Canvas
|
||||
c.setFont('CustomFont', 12)
|
||||
|
||||
# Use in Paragraph style
|
||||
style = ParagraphStyle('Custom', fontName='CustomFont', fontSize=12)
|
||||
```
|
||||
|
||||
**Detailed text reference:** See `references/text_and_fonts.md` for paragraph styles, font families, Asian languages, Greek letters, and formatting.
|
||||
|
||||
### Adding Images
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Image
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
# In Platypus
|
||||
img = Image('photo.jpg', width=4*inch, height=3*inch)
|
||||
story.append(img)
|
||||
|
||||
# Maintain aspect ratio
|
||||
img = Image('photo.jpg', width=4*inch, height=3*inch, kind='proportional')
|
||||
|
||||
# In Canvas
|
||||
c.drawImage('photo.jpg', x, y, width=4*inch, height=3*inch)
|
||||
|
||||
# With transparency (mask white background)
|
||||
c.drawImage('logo.png', x, y, mask=[255,255,255,255,255,255])
|
||||
```
|
||||
|
||||
### Creating Forms
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.colors import black, white, lightgrey
|
||||
|
||||
c = canvas.Canvas("form.pdf")
|
||||
|
||||
# Text field
|
||||
c.acroForm.textfield(
|
||||
name="name",
|
||||
tooltip="Enter your name",
|
||||
x=100, y=700,
|
||||
width=200, height=20,
|
||||
borderColor=black,
|
||||
fillColor=lightgrey,
|
||||
forceBorder=True
|
||||
)
|
||||
|
||||
# Checkbox
|
||||
c.acroForm.checkbox(
|
||||
name="agree",
|
||||
x=100, y=650,
|
||||
size=20,
|
||||
buttonStyle='check',
|
||||
checked=False
|
||||
)
|
||||
|
||||
# Dropdown
|
||||
c.acroForm.choice(
|
||||
name="country",
|
||||
x=100, y=600,
|
||||
width=150, height=20,
|
||||
options=[("United States", "US"), ("Canada", "CA")],
|
||||
forceBorder=True
|
||||
)
|
||||
|
||||
c.save()
|
||||
```
|
||||
|
||||
**Detailed PDF features reference:** See `references/pdf_features.md` for forms, links, bookmarks, encryption, and metadata.
|
||||
|
||||
### Headers and Footers
|
||||
|
||||
For Platypus documents, use page callbacks:
|
||||
|
||||
```python
|
||||
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame
|
||||
|
||||
def add_header_footer(canvas, doc):
|
||||
"""Called on each page"""
|
||||
canvas.saveState()
|
||||
|
||||
# Header
|
||||
canvas.setFont('Helvetica', 9)
|
||||
canvas.drawString(inch, height - 0.5*inch, "Document Title")
|
||||
|
||||
# Footer
|
||||
canvas.drawRightString(width - inch, 0.5*inch, f"Page {doc.page}")
|
||||
|
||||
canvas.restoreState()
|
||||
|
||||
# Set up document
|
||||
doc = BaseDocTemplate("output.pdf")
|
||||
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height, id='normal')
|
||||
template = PageTemplate(id='normal', frames=[frame], onPage=add_header_footer)
|
||||
doc.addPageTemplates([template])
|
||||
|
||||
# Build with story
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
## Helper Scripts
|
||||
|
||||
This skill includes helper scripts for common tasks:
|
||||
|
||||
### Quick Document Generator
|
||||
|
||||
Use `scripts/quick_document.py` for rapid document creation:
|
||||
|
||||
```python
|
||||
from scripts.quick_document import create_simple_document, create_styled_table
|
||||
|
||||
# Simple document from content blocks
|
||||
content = [
|
||||
{'type': 'heading', 'content': 'Introduction'},
|
||||
{'type': 'paragraph', 'content': 'Your text here...'},
|
||||
{'type': 'bullet', 'content': 'Bullet point'},
|
||||
]
|
||||
|
||||
create_simple_document("output.pdf", "My Document", content_blocks=content)
|
||||
|
||||
# Styled tables with presets
|
||||
data = [['Header1', 'Header2'], ['Data1', 'Data2']]
|
||||
table = create_styled_table(data, style_name='striped') # 'default', 'striped', 'minimal', 'report'
|
||||
```
|
||||
|
||||
## Template Examples
|
||||
|
||||
Complete working examples in `assets/`:
|
||||
|
||||
### Invoice Template
|
||||
|
||||
`assets/invoice_template.py` - Professional invoice with:
|
||||
- Company and client information
|
||||
- Itemized table with calculations
|
||||
- Tax and totals
|
||||
- Terms and notes
|
||||
- Logo placement
|
||||
|
||||
```python
|
||||
from assets.invoice_template import create_invoice
|
||||
|
||||
create_invoice(
|
||||
filename="invoice.pdf",
|
||||
invoice_number="INV-2024-001",
|
||||
invoice_date="January 15, 2024",
|
||||
due_date="February 15, 2024",
|
||||
company_info={'name': 'Acme Corp', 'address': '...', 'phone': '...', 'email': '...'},
|
||||
client_info={'name': 'Client Name', ...},
|
||||
items=[
|
||||
{'description': 'Service', 'quantity': 1, 'unit_price': 500.00},
|
||||
...
|
||||
],
|
||||
tax_rate=0.08,
|
||||
notes="Thank you for your business!",
|
||||
)
|
||||
```
|
||||
|
||||
### Report Template
|
||||
|
||||
`assets/report_template.py` - Multi-page business report with:
|
||||
- Cover page
|
||||
- Table of contents
|
||||
- Multiple sections with subsections
|
||||
- Charts and tables
|
||||
- Headers and footers
|
||||
|
||||
```python
|
||||
from assets.report_template import create_report
|
||||
|
||||
report_data = {
|
||||
'title': 'Quarterly Report',
|
||||
'subtitle': 'Q4 2023',
|
||||
'author': 'Analytics Team',
|
||||
'sections': [
|
||||
{
|
||||
'title': 'Executive Summary',
|
||||
'content': 'Report content...',
|
||||
'table_data': {...},
|
||||
'chart_data': {...}
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
create_report("report.pdf", report_data)
|
||||
```
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
Comprehensive API references organized by feature:
|
||||
|
||||
- **`references/canvas_api.md`** - Low-level Canvas: drawing primitives, coordinates, transformations, state management, images, paths
|
||||
- **`references/platypus_guide.md`** - High-level Platypus: document templates, frames, flowables, page layouts, TOC
|
||||
- **`references/text_and_fonts.md`** - Text formatting: paragraph styles, inline markup, custom fonts, Asian languages, bullets, sequences
|
||||
- **`references/tables_reference.md`** - Tables: creation, styling, cell spanning, borders, alignment, colors, gradients
|
||||
- **`references/charts_reference.md`** - Charts: all chart types, data handling, axes, legends, colors, rendering
|
||||
- **`references/barcodes_reference.md`** - Barcodes: Code128, QR codes, EAN, UPC, postal codes, and 20+ formats
|
||||
- **`references/pdf_features.md`** - PDF features: links, bookmarks, forms, encryption, metadata, page transitions
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Coordinate System (Canvas)
|
||||
- Origin (0, 0) is **lower-left corner** (not top-left)
|
||||
- Y-axis points **upward**
|
||||
- Units are in **points** (72 points = 1 inch)
|
||||
- Always specify page size explicitly
|
||||
|
||||
```python
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
width, height = letter
|
||||
margin = inch
|
||||
|
||||
# Top of page
|
||||
y_top = height - margin
|
||||
|
||||
# Bottom of page
|
||||
y_bottom = margin
|
||||
```
|
||||
|
||||
### Choosing Page Size
|
||||
|
||||
```python
|
||||
from reportlab.lib.pagesizes import letter, A4, landscape
|
||||
|
||||
# US Letter (8.5" x 11")
|
||||
pagesize=letter
|
||||
|
||||
# ISO A4 (210mm x 297mm)
|
||||
pagesize=A4
|
||||
|
||||
# Landscape
|
||||
pagesize=landscape(letter)
|
||||
|
||||
# Custom
|
||||
pagesize=(6*inch, 9*inch)
|
||||
```
|
||||
|
||||
### Performance Tips
|
||||
|
||||
1. **Use `drawImage()` over `drawInlineImage()`** - caches images for reuse
|
||||
2. **Enable compression for large files:** `canvas.Canvas("file.pdf", pageCompression=1)`
|
||||
3. **Reuse styles** - create once, use throughout document
|
||||
4. **Use Forms/XObjects** for repeated graphics
|
||||
|
||||
### Common Patterns
|
||||
|
||||
**Centering text on Canvas:**
|
||||
```python
|
||||
text = "Centered Text"
|
||||
text_width = c.stringWidth(text, "Helvetica", 12)
|
||||
x = (width - text_width) / 2
|
||||
c.drawString(x, y, text)
|
||||
|
||||
# Or use built-in
|
||||
c.drawCentredString(width/2, y, text)
|
||||
```
|
||||
|
||||
**Page breaks in Platypus:**
|
||||
```python
|
||||
from reportlab.platypus import PageBreak
|
||||
|
||||
story.append(PageBreak())
|
||||
```
|
||||
|
||||
**Keep content together (no split):**
|
||||
```python
|
||||
from reportlab.platypus import KeepTogether
|
||||
|
||||
story.append(KeepTogether([
|
||||
heading,
|
||||
paragraph1,
|
||||
paragraph2,
|
||||
]))
|
||||
```
|
||||
|
||||
**Alternate row colors:**
|
||||
```python
|
||||
style = TableStyle([
|
||||
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
|
||||
])
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Text overlaps or disappears:**
|
||||
- Check Y-coordinates - remember origin is bottom-left
|
||||
- Ensure text fits within page bounds
|
||||
- Verify `leading` (line spacing) is greater than `fontSize`
|
||||
|
||||
**Table doesn't fit on page:**
|
||||
- Reduce column widths
|
||||
- Decrease font size
|
||||
- Use landscape orientation
|
||||
- Enable table splitting with `repeatRows`
|
||||
|
||||
**Barcode not scanning:**
|
||||
- Increase `barHeight` (try 0.5 inch minimum)
|
||||
- Set `quiet=1` for quiet zones
|
||||
- Test print quality (300+ DPI recommended)
|
||||
- Validate data format for barcode type
|
||||
|
||||
**Font not found:**
|
||||
- Register TrueType fonts with `pdfmetrics.registerFont()`
|
||||
- Use font family name exactly as registered
|
||||
- Check font file path is correct
|
||||
|
||||
**Images have white background:**
|
||||
- Use `mask` parameter to make white transparent
|
||||
- Provide RGB range to mask: `mask=[255,255,255,255,255,255]`
|
||||
- Or use PNG with alpha channel
|
||||
|
||||
## Example Workflows
|
||||
|
||||
### Creating an Invoice
|
||||
|
||||
1. Start with invoice template from `assets/invoice_template.py`
|
||||
2. Customize company info, logo path
|
||||
3. Add items with descriptions, quantities, prices
|
||||
4. Set tax rate if applicable
|
||||
5. Add notes and payment terms
|
||||
6. Generate PDF
|
||||
|
||||
### Creating a Report
|
||||
|
||||
1. Start with report template from `assets/report_template.py`
|
||||
2. Define sections with titles and content
|
||||
3. Add tables for data using `create_styled_table()`
|
||||
4. Add charts using graphics framework
|
||||
5. Build with `doc.multiBuild(story)` for TOC
|
||||
|
||||
### Creating a Certificate
|
||||
|
||||
1. Use Canvas API for precise positioning
|
||||
2. Load custom fonts for elegant typography
|
||||
3. Add border graphics or image background
|
||||
4. Position text elements (name, date, achievement)
|
||||
5. Optional: Add QR code for verification
|
||||
|
||||
### Creating Labels with Barcodes
|
||||
|
||||
1. Use Canvas with custom page size (label dimensions)
|
||||
2. Calculate grid positions for multiple labels per page
|
||||
3. Draw label content (text, images)
|
||||
4. Add barcode at specific position
|
||||
5. Use `showPage()` between labels or grids
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install reportlab
|
||||
|
||||
# For image support
|
||||
pip install pillow
|
||||
|
||||
# For charts
|
||||
pip install reportlab[renderPM]
|
||||
|
||||
# For barcode support (included in reportlab)
|
||||
# QR codes require: pip install qrcode
|
||||
```
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Invoke this skill when:
|
||||
- Generating PDF documents programmatically
|
||||
- Creating invoices, receipts, or billing documents
|
||||
- Building reports with tables and charts
|
||||
- Generating certificates, badges, or credentials
|
||||
- Creating shipping labels or product labels with barcodes
|
||||
- Designing forms or fillable PDFs
|
||||
- Producing multi-page documents with consistent formatting
|
||||
- Converting data to PDF format for archival or distribution
|
||||
- Creating custom layouts that require precise positioning
|
||||
|
||||
This skill provides comprehensive guidance for all ReportLab capabilities, from simple documents to complex multi-page reports with charts, tables, and interactive elements.
|
||||
256
scientific-packages/reportlab/assets/invoice_template.py
Normal file
256
scientific-packages/reportlab/assets/invoice_template.py
Normal file
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Invoice Template - Complete example of a professional invoice
|
||||
|
||||
This template demonstrates:
|
||||
- Company header with logo placement
|
||||
- Client information
|
||||
- Invoice details table
|
||||
- Calculations (subtotal, tax, total)
|
||||
- Professional styling
|
||||
- Terms and conditions footer
|
||||
"""
|
||||
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.lib import colors
|
||||
from reportlab.platypus import (
|
||||
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Image
|
||||
)
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def create_invoice(
|
||||
filename,
|
||||
invoice_number,
|
||||
invoice_date,
|
||||
due_date,
|
||||
company_info,
|
||||
client_info,
|
||||
items,
|
||||
tax_rate=0.0,
|
||||
notes="",
|
||||
terms="Payment due within 30 days.",
|
||||
logo_path=None
|
||||
):
|
||||
"""
|
||||
Create a professional invoice PDF.
|
||||
|
||||
Args:
|
||||
filename: Output PDF filename
|
||||
invoice_number: Invoice number (e.g., "INV-2024-001")
|
||||
invoice_date: Date of invoice (datetime or string)
|
||||
due_date: Payment due date (datetime or string)
|
||||
company_info: Dict with company details
|
||||
{'name': 'Company Name', 'address': 'Address', 'phone': 'Phone', 'email': 'Email'}
|
||||
client_info: Dict with client details (same structure as company_info)
|
||||
items: List of dicts with item details
|
||||
[{'description': 'Item', 'quantity': 1, 'unit_price': 100.00}, ...]
|
||||
tax_rate: Tax rate as decimal (e.g., 0.08 for 8%)
|
||||
notes: Additional notes to client
|
||||
terms: Payment terms
|
||||
logo_path: Path to company logo image (optional)
|
||||
"""
|
||||
# Create document
|
||||
doc = SimpleDocTemplate(filename, pagesize=letter,
|
||||
rightMargin=0.5*inch, leftMargin=0.5*inch,
|
||||
topMargin=0.5*inch, bottomMargin=0.5*inch)
|
||||
|
||||
# Container for elements
|
||||
story = []
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Create custom styles
|
||||
title_style = ParagraphStyle(
|
||||
'InvoiceTitle',
|
||||
parent=styles['Heading1'],
|
||||
fontSize=24,
|
||||
textColor=colors.HexColor('#2C3E50'),
|
||||
spaceAfter=12,
|
||||
)
|
||||
|
||||
header_style = ParagraphStyle(
|
||||
'Header',
|
||||
parent=styles['Normal'],
|
||||
fontSize=10,
|
||||
textColor=colors.HexColor('#34495E'),
|
||||
)
|
||||
|
||||
# --- HEADER SECTION ---
|
||||
header_data = []
|
||||
|
||||
# Company info (left side)
|
||||
company_text = f"""
|
||||
<b><font size="14">{company_info['name']}</font></b><br/>
|
||||
{company_info.get('address', '')}<br/>
|
||||
Phone: {company_info.get('phone', '')}<br/>
|
||||
Email: {company_info.get('email', '')}
|
||||
"""
|
||||
|
||||
# Invoice title and number (right side)
|
||||
invoice_text = f"""
|
||||
<b><font size="16" color="#2C3E50">INVOICE</font></b><br/>
|
||||
<font size="10">Invoice #: {invoice_number}</font><br/>
|
||||
<font size="10">Date: {invoice_date}</font><br/>
|
||||
<font size="10">Due Date: {due_date}</font>
|
||||
"""
|
||||
|
||||
if logo_path:
|
||||
logo = Image(logo_path, width=1.5*inch, height=1*inch)
|
||||
header_data = [[logo, Paragraph(company_text, header_style), Paragraph(invoice_text, header_style)]]
|
||||
header_table = Table(header_data, colWidths=[1.5*inch, 3*inch, 2.5*inch])
|
||||
else:
|
||||
header_data = [[Paragraph(company_text, header_style), Paragraph(invoice_text, header_style)]]
|
||||
header_table = Table(header_data, colWidths=[4.5*inch, 2.5*inch])
|
||||
|
||||
header_table.setStyle(TableStyle([
|
||||
('VALIGN', (0, 0), (-1, -1), 'TOP'),
|
||||
('ALIGN', (-1, 0), (-1, -1), 'RIGHT'),
|
||||
]))
|
||||
|
||||
story.append(header_table)
|
||||
story.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# --- CLIENT INFORMATION ---
|
||||
client_label = Paragraph("<b>Bill To:</b>", header_style)
|
||||
client_text = f"""
|
||||
<b>{client_info['name']}</b><br/>
|
||||
{client_info.get('address', '')}<br/>
|
||||
Phone: {client_info.get('phone', '')}<br/>
|
||||
Email: {client_info.get('email', '')}
|
||||
"""
|
||||
client_para = Paragraph(client_text, header_style)
|
||||
|
||||
client_table = Table([[client_label, client_para]], colWidths=[1*inch, 6*inch])
|
||||
story.append(client_table)
|
||||
story.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# --- ITEMS TABLE ---
|
||||
# Table header
|
||||
items_data = [['Description', 'Quantity', 'Unit Price', 'Amount']]
|
||||
|
||||
# Calculate items
|
||||
subtotal = 0
|
||||
for item in items:
|
||||
desc = item['description']
|
||||
qty = item['quantity']
|
||||
price = item['unit_price']
|
||||
amount = qty * price
|
||||
subtotal += amount
|
||||
|
||||
items_data.append([
|
||||
desc,
|
||||
str(qty),
|
||||
f"${price:,.2f}",
|
||||
f"${amount:,.2f}"
|
||||
])
|
||||
|
||||
# Create items table
|
||||
items_table = Table(items_data, colWidths=[3.5*inch, 1*inch, 1.5*inch, 1*inch])
|
||||
|
||||
items_table.setStyle(TableStyle([
|
||||
# Header row
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#34495E')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 11),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
|
||||
# Data rows
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
||||
('ALIGN', (1, 1), (-1, -1), 'RIGHT'),
|
||||
('ALIGN', (0, 1), (0, -1), 'LEFT'),
|
||||
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
|
||||
('FONTSIZE', (0, 1), (-1, -1), 10),
|
||||
('TOPPADDING', (0, 1), (-1, -1), 6),
|
||||
('BOTTOMPADDING', (0, 1), (-1, -1), 6),
|
||||
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
|
||||
]))
|
||||
|
||||
story.append(items_table)
|
||||
story.append(Spacer(1, 0.2*inch))
|
||||
|
||||
# --- TOTALS SECTION ---
|
||||
tax_amount = subtotal * tax_rate
|
||||
total = subtotal + tax_amount
|
||||
|
||||
totals_data = [
|
||||
['Subtotal:', f"${subtotal:,.2f}"],
|
||||
]
|
||||
|
||||
if tax_rate > 0:
|
||||
totals_data.append([f'Tax ({tax_rate*100:.1f}%):', f"${tax_amount:,.2f}"])
|
||||
|
||||
totals_data.append(['<b>Total:</b>', f"<b>${total:,.2f}</b>"])
|
||||
|
||||
totals_table = Table(totals_data, colWidths=[5*inch, 2*inch])
|
||||
totals_table.setStyle(TableStyle([
|
||||
('ALIGN', (0, 0), (-1, -1), 'RIGHT'),
|
||||
('FONTNAME', (0, 0), (-1, -2), 'Helvetica'),
|
||||
('FONTNAME', (0, -1), (-1, -1), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, -1), 11),
|
||||
('TOPPADDING', (0, 0), (-1, -1), 6),
|
||||
('LINEABOVE', (1, -1), (1, -1), 2, colors.HexColor('#34495E')),
|
||||
]))
|
||||
|
||||
story.append(totals_table)
|
||||
story.append(Spacer(1, 0.4*inch))
|
||||
|
||||
# --- NOTES ---
|
||||
if notes:
|
||||
notes_style = ParagraphStyle('Notes', parent=styles['Normal'], fontSize=9)
|
||||
story.append(Paragraph(f"<b>Notes:</b><br/>{notes}", notes_style))
|
||||
story.append(Spacer(1, 0.2*inch))
|
||||
|
||||
# --- TERMS ---
|
||||
terms_style = ParagraphStyle('Terms', parent=styles['Normal'],
|
||||
fontSize=9, textColor=colors.grey)
|
||||
story.append(Paragraph(f"<b>Payment Terms:</b><br/>{terms}", terms_style))
|
||||
|
||||
# Build PDF
|
||||
doc.build(story)
|
||||
return filename
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
# Sample data
|
||||
company = {
|
||||
'name': 'Acme Corporation',
|
||||
'address': '123 Business St, Suite 100\nNew York, NY 10001',
|
||||
'phone': '(555) 123-4567',
|
||||
'email': 'info@acme.com'
|
||||
}
|
||||
|
||||
client = {
|
||||
'name': 'John Doe',
|
||||
'address': '456 Client Ave\nLos Angeles, CA 90001',
|
||||
'phone': '(555) 987-6543',
|
||||
'email': 'john@example.com'
|
||||
}
|
||||
|
||||
items = [
|
||||
{'description': 'Web Design Services', 'quantity': 1, 'unit_price': 2500.00},
|
||||
{'description': 'Content Writing (10 pages)', 'quantity': 10, 'unit_price': 50.00},
|
||||
{'description': 'SEO Optimization', 'quantity': 1, 'unit_price': 750.00},
|
||||
{'description': 'Hosting Setup', 'quantity': 1, 'unit_price': 200.00},
|
||||
]
|
||||
|
||||
create_invoice(
|
||||
filename="sample_invoice.pdf",
|
||||
invoice_number="INV-2024-001",
|
||||
invoice_date="January 15, 2024",
|
||||
due_date="February 15, 2024",
|
||||
company_info=company,
|
||||
client_info=client,
|
||||
items=items,
|
||||
tax_rate=0.08,
|
||||
notes="Thank you for your business! We appreciate your prompt payment.",
|
||||
terms="Payment due within 30 days. Late payments subject to 1.5% monthly fee.",
|
||||
logo_path=None # Set to your logo path if available
|
||||
)
|
||||
|
||||
print("Invoice created: sample_invoice.pdf")
|
||||
343
scientific-packages/reportlab/assets/report_template.py
Normal file
343
scientific-packages/reportlab/assets/report_template.py
Normal file
@@ -0,0 +1,343 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Report Template - Complete example of a professional multi-page report
|
||||
|
||||
This template demonstrates:
|
||||
- Cover page
|
||||
- Table of contents
|
||||
- Multiple sections with headers
|
||||
- Charts and graphs integration
|
||||
- Tables with data
|
||||
- Headers and footers
|
||||
- Professional styling
|
||||
"""
|
||||
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.lib import colors
|
||||
from reportlab.platypus import (
|
||||
BaseDocTemplate, PageTemplate, Frame, Paragraph, Spacer,
|
||||
Table, TableStyle, PageBreak, KeepTogether, TableOfContents
|
||||
)
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
||||
from reportlab.graphics.charts.linecharts import HorizontalLineChart
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def header_footer(canvas, doc):
|
||||
"""Draw header and footer on each page (except cover)"""
|
||||
canvas.saveState()
|
||||
|
||||
# Skip header/footer on cover page (page 1)
|
||||
if doc.page > 1:
|
||||
# Header
|
||||
canvas.setFont('Helvetica', 9)
|
||||
canvas.setFillColor(colors.grey)
|
||||
canvas.drawString(inch, letter[1] - 0.5*inch, "Quarterly Business Report")
|
||||
canvas.line(inch, letter[1] - 0.55*inch, letter[0] - inch, letter[1] - 0.55*inch)
|
||||
|
||||
# Footer
|
||||
canvas.drawString(inch, 0.5*inch, f"Generated: {datetime.now().strftime('%B %d, %Y')}")
|
||||
canvas.drawRightString(letter[0] - inch, 0.5*inch, f"Page {doc.page - 1}")
|
||||
|
||||
canvas.restoreState()
|
||||
|
||||
|
||||
def create_report(filename, report_data):
|
||||
"""
|
||||
Create a comprehensive business report.
|
||||
|
||||
Args:
|
||||
filename: Output PDF filename
|
||||
report_data: Dict containing report information
|
||||
{
|
||||
'title': 'Report Title',
|
||||
'subtitle': 'Report Subtitle',
|
||||
'author': 'Author Name',
|
||||
'date': 'Date',
|
||||
'sections': [
|
||||
{
|
||||
'title': 'Section Title',
|
||||
'content': 'Section content...',
|
||||
'subsections': [...],
|
||||
'table': {...},
|
||||
'chart': {...}
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
"""
|
||||
# Create document with custom page template
|
||||
doc = BaseDocTemplate(filename, pagesize=letter,
|
||||
rightMargin=72, leftMargin=72,
|
||||
topMargin=inch, bottomMargin=inch)
|
||||
|
||||
# Define frame for content
|
||||
frame = Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height - 0.5*inch, id='normal')
|
||||
|
||||
# Create page template with header/footer
|
||||
template = PageTemplate(id='normal', frames=[frame], onPage=header_footer)
|
||||
doc.addPageTemplates([template])
|
||||
|
||||
# Get styles
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Custom styles
|
||||
title_style = ParagraphStyle(
|
||||
'ReportTitle',
|
||||
parent=styles['Title'],
|
||||
fontSize=28,
|
||||
textColor=colors.HexColor('#2C3E50'),
|
||||
spaceAfter=20,
|
||||
alignment=TA_CENTER,
|
||||
)
|
||||
|
||||
subtitle_style = ParagraphStyle(
|
||||
'ReportSubtitle',
|
||||
parent=styles['Normal'],
|
||||
fontSize=14,
|
||||
textColor=colors.grey,
|
||||
alignment=TA_CENTER,
|
||||
spaceAfter=30,
|
||||
)
|
||||
|
||||
heading1_style = ParagraphStyle(
|
||||
'CustomHeading1',
|
||||
parent=styles['Heading1'],
|
||||
fontSize=18,
|
||||
textColor=colors.HexColor('#2C3E50'),
|
||||
spaceAfter=12,
|
||||
spaceBefore=12,
|
||||
)
|
||||
|
||||
heading2_style = ParagraphStyle(
|
||||
'CustomHeading2',
|
||||
parent=styles['Heading2'],
|
||||
fontSize=14,
|
||||
textColor=colors.HexColor('#34495E'),
|
||||
spaceAfter=10,
|
||||
spaceBefore=10,
|
||||
)
|
||||
|
||||
body_style = ParagraphStyle(
|
||||
'ReportBody',
|
||||
parent=styles['BodyText'],
|
||||
fontSize=11,
|
||||
alignment=TA_JUSTIFY,
|
||||
spaceAfter=12,
|
||||
leading=14,
|
||||
)
|
||||
|
||||
# Build story
|
||||
story = []
|
||||
|
||||
# --- COVER PAGE ---
|
||||
story.append(Spacer(1, 2*inch))
|
||||
story.append(Paragraph(report_data['title'], title_style))
|
||||
story.append(Paragraph(report_data.get('subtitle', ''), subtitle_style))
|
||||
story.append(Spacer(1, inch))
|
||||
|
||||
# Cover info table
|
||||
cover_info = [
|
||||
['Prepared by:', report_data.get('author', '')],
|
||||
['Date:', report_data.get('date', datetime.now().strftime('%B %d, %Y'))],
|
||||
['Period:', report_data.get('period', 'Q4 2023')],
|
||||
]
|
||||
|
||||
cover_table = Table(cover_info, colWidths=[2*inch, 4*inch])
|
||||
cover_table.setStyle(TableStyle([
|
||||
('ALIGN', (0, 0), (0, -1), 'RIGHT'),
|
||||
('ALIGN', (1, 0), (1, -1), 'LEFT'),
|
||||
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, -1), 11),
|
||||
('TOPPADDING', (0, 0), (-1, -1), 6),
|
||||
]))
|
||||
|
||||
story.append(cover_table)
|
||||
story.append(PageBreak())
|
||||
|
||||
# --- TABLE OF CONTENTS ---
|
||||
toc = TableOfContents()
|
||||
toc.levelStyles = [
|
||||
ParagraphStyle(name='TOCHeading1', fontSize=14, leftIndent=20, spaceBefore=10, spaceAfter=5),
|
||||
ParagraphStyle(name='TOCHeading2', fontSize=12, leftIndent=40, spaceBefore=3, spaceAfter=3),
|
||||
]
|
||||
|
||||
story.append(Paragraph("Table of Contents", heading1_style))
|
||||
story.append(toc)
|
||||
story.append(PageBreak())
|
||||
|
||||
# --- SECTIONS ---
|
||||
for section in report_data.get('sections', []):
|
||||
# Section heading
|
||||
section_title = section['title']
|
||||
story.append(Paragraph(f'<a name="{section_title}"/>{section_title}', heading1_style))
|
||||
|
||||
# Add to TOC
|
||||
toc.addEntry(0, section_title, doc.page)
|
||||
|
||||
# Section content
|
||||
if 'content' in section:
|
||||
for para in section['content'].split('\n\n'):
|
||||
if para.strip():
|
||||
story.append(Paragraph(para.strip(), body_style))
|
||||
|
||||
story.append(Spacer(1, 0.2*inch))
|
||||
|
||||
# Subsections
|
||||
for subsection in section.get('subsections', []):
|
||||
story.append(Paragraph(subsection['title'], heading2_style))
|
||||
|
||||
if 'content' in subsection:
|
||||
story.append(Paragraph(subsection['content'], body_style))
|
||||
|
||||
story.append(Spacer(1, 0.1*inch))
|
||||
|
||||
# Add table if provided
|
||||
if 'table_data' in section:
|
||||
table = create_section_table(section['table_data'])
|
||||
story.append(table)
|
||||
story.append(Spacer(1, 0.2*inch))
|
||||
|
||||
# Add chart if provided
|
||||
if 'chart_data' in section:
|
||||
chart = create_section_chart(section['chart_data'])
|
||||
story.append(chart)
|
||||
story.append(Spacer(1, 0.2*inch))
|
||||
|
||||
story.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# Build PDF (twice for TOC to populate)
|
||||
doc.multiBuild(story)
|
||||
return filename
|
||||
|
||||
|
||||
def create_section_table(table_data):
|
||||
"""Create a styled table for report sections"""
|
||||
data = table_data['data']
|
||||
table = Table(data, colWidths=table_data.get('colWidths'))
|
||||
|
||||
table.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#34495E')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 11),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
||||
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
|
||||
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
|
||||
('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
|
||||
('FONTSIZE', (0, 1), (-1, -1), 10),
|
||||
]))
|
||||
|
||||
return table
|
||||
|
||||
|
||||
def create_section_chart(chart_data):
|
||||
"""Create a chart for report sections"""
|
||||
chart_type = chart_data.get('type', 'bar')
|
||||
drawing = Drawing(400, 200)
|
||||
|
||||
if chart_type == 'bar':
|
||||
chart = VerticalBarChart()
|
||||
chart.x = 50
|
||||
chart.y = 30
|
||||
chart.width = 300
|
||||
chart.height = 150
|
||||
chart.data = chart_data['data']
|
||||
chart.categoryAxis.categoryNames = chart_data.get('categories', [])
|
||||
chart.valueAxis.valueMin = 0
|
||||
|
||||
# Style bars
|
||||
for i in range(len(chart_data['data'])):
|
||||
chart.bars[i].fillColor = colors.HexColor(['#3498db', '#e74c3c', '#2ecc71'][i % 3])
|
||||
|
||||
elif chart_type == 'line':
|
||||
chart = HorizontalLineChart()
|
||||
chart.x = 50
|
||||
chart.y = 30
|
||||
chart.width = 300
|
||||
chart.height = 150
|
||||
chart.data = chart_data['data']
|
||||
chart.categoryAxis.categoryNames = chart_data.get('categories', [])
|
||||
|
||||
# Style lines
|
||||
for i in range(len(chart_data['data'])):
|
||||
chart.lines[i].strokeColor = colors.HexColor(['#3498db', '#e74c3c', '#2ecc71'][i % 3])
|
||||
chart.lines[i].strokeWidth = 2
|
||||
|
||||
drawing.add(chart)
|
||||
return drawing
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
report = {
|
||||
'title': 'Quarterly Business Report',
|
||||
'subtitle': 'Q4 2023 Performance Analysis',
|
||||
'author': 'Analytics Team',
|
||||
'date': 'January 15, 2024',
|
||||
'period': 'October - December 2023',
|
||||
'sections': [
|
||||
{
|
||||
'title': 'Executive Summary',
|
||||
'content': """
|
||||
This report provides a comprehensive analysis of our Q4 2023 performance.
|
||||
Overall, the quarter showed strong growth across all key metrics, with
|
||||
revenue increasing by 25% year-over-year and customer satisfaction
|
||||
scores reaching an all-time high of 4.8/5.0.
|
||||
|
||||
Key highlights include the successful launch of three new products,
|
||||
expansion into two new markets, and the completion of our digital
|
||||
transformation initiative.
|
||||
""",
|
||||
'subsections': [
|
||||
{
|
||||
'title': 'Key Achievements',
|
||||
'content': 'Successfully launched Product X with 10,000 units sold in first month.'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'title': 'Financial Performance',
|
||||
'content': """
|
||||
The financial results for Q4 exceeded expectations across all categories.
|
||||
Revenue growth was driven primarily by strong product sales and increased
|
||||
market share in key regions.
|
||||
""",
|
||||
'table_data': {
|
||||
'data': [
|
||||
['Metric', 'Q3 2023', 'Q4 2023', 'Change'],
|
||||
['Revenue', '$2.5M', '$3.1M', '+24%'],
|
||||
['Profit', '$500K', '$680K', '+36%'],
|
||||
['Expenses', '$2.0M', '$2.4M', '+20%'],
|
||||
],
|
||||
'colWidths': [2*inch, 1.5*inch, 1.5*inch, 1*inch]
|
||||
},
|
||||
'chart_data': {
|
||||
'type': 'bar',
|
||||
'data': [[2.5, 3.1], [0.5, 0.68], [2.0, 2.4]],
|
||||
'categories': ['Q3', 'Q4']
|
||||
}
|
||||
},
|
||||
{
|
||||
'title': 'Market Analysis',
|
||||
'content': """
|
||||
Market conditions remained favorable throughout the quarter, with
|
||||
strong consumer confidence and increasing demand for our products.
|
||||
""",
|
||||
'chart_data': {
|
||||
'type': 'line',
|
||||
'data': [[100, 120, 115, 140, 135, 150]],
|
||||
'categories': ['Oct', 'Nov', 'Dec', 'Oct', 'Nov', 'Dec']
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
create_report("sample_report.pdf", report)
|
||||
print("Report created: sample_report.pdf")
|
||||
504
scientific-packages/reportlab/references/barcodes_reference.md
Normal file
504
scientific-packages/reportlab/references/barcodes_reference.md
Normal file
@@ -0,0 +1,504 @@
|
||||
# Barcodes Reference
|
||||
|
||||
Comprehensive guide to creating barcodes and QR codes in ReportLab.
|
||||
|
||||
## Available Barcode Types
|
||||
|
||||
ReportLab supports a wide range of 1D and 2D barcode formats.
|
||||
|
||||
### 1D Barcodes (Linear)
|
||||
|
||||
- **Code128** - Compact, encodes full ASCII
|
||||
- **Code39** (Standard39) - Alphanumeric, widely supported
|
||||
- **Code93** (Standard93) - Compressed Code39
|
||||
- **EAN-13** - European Article Number (retail)
|
||||
- **EAN-8** - Short form of EAN
|
||||
- **EAN-5** - 5-digit add-on (pricing)
|
||||
- **UPC-A** - Universal Product Code (North America)
|
||||
- **ISBN** - International Standard Book Number
|
||||
- **Code11** - Telecommunications
|
||||
- **Codabar** - Blood banks, FedEx, libraries
|
||||
- **I2of5** (Interleaved 2 of 5) - Warehouse/distribution
|
||||
- **MSI** - Inventory control
|
||||
- **POSTNET** - US Postal Service
|
||||
- **USPS_4State** - US Postal Service
|
||||
- **FIM** (A, B, C, D) - Facing Identification Mark (mail sorting)
|
||||
|
||||
### 2D Barcodes
|
||||
|
||||
- **QR** - QR Code (widely used for URLs, contact info)
|
||||
- **ECC200DataMatrix** - Data Matrix format
|
||||
|
||||
## Using Barcodes with Canvas
|
||||
|
||||
### Code128 (Recommended for General Use)
|
||||
|
||||
Code128 is versatile and compact - encodes full ASCII character set with mandatory checksum.
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.graphics.barcode import code128
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
c = canvas.Canvas("barcode.pdf")
|
||||
|
||||
# Create barcode
|
||||
barcode = code128.Code128("HELLO123")
|
||||
|
||||
# Draw on canvas
|
||||
barcode.drawOn(c, 1*inch, 5*inch)
|
||||
|
||||
c.save()
|
||||
```
|
||||
|
||||
### Code128 Options
|
||||
|
||||
```python
|
||||
barcode = code128.Code128(
|
||||
value="ABC123", # Required: data to encode
|
||||
barWidth=0.01*inch, # Width of narrowest bar
|
||||
barHeight=0.5*inch, # Height of bars
|
||||
quiet=1, # Add quiet zones (margins)
|
||||
lquiet=None, # Left quiet zone width
|
||||
rquiet=None, # Right quiet zone width
|
||||
stop=1, # Show stop symbol
|
||||
)
|
||||
|
||||
# Draw with specific size
|
||||
barcode.drawOn(canvas, x, y)
|
||||
|
||||
# Get dimensions
|
||||
width = barcode.width
|
||||
height = barcode.height
|
||||
```
|
||||
|
||||
### Code39 (Standard39)
|
||||
|
||||
Supports: 0-9, A-Z (uppercase), space, and special chars (-.$/+%*).
|
||||
|
||||
```python
|
||||
from reportlab.graphics.barcode import code39
|
||||
|
||||
barcode = code39.Standard39(
|
||||
value="HELLO",
|
||||
barWidth=0.01*inch,
|
||||
barHeight=0.5*inch,
|
||||
quiet=1,
|
||||
checksum=0, # 0 or 1
|
||||
)
|
||||
|
||||
barcode.drawOn(canvas, x, y)
|
||||
```
|
||||
|
||||
### Extended Code39
|
||||
|
||||
Encodes full ASCII (pairs of Code39 characters).
|
||||
|
||||
```python
|
||||
from reportlab.graphics.barcode import code39
|
||||
|
||||
barcode = code39.Extended39(
|
||||
value="Hello World!", # Can include lowercase and symbols
|
||||
barWidth=0.01*inch,
|
||||
barHeight=0.5*inch,
|
||||
)
|
||||
|
||||
barcode.drawOn(canvas, x, y)
|
||||
```
|
||||
|
||||
### Code93
|
||||
|
||||
```python
|
||||
from reportlab.graphics.barcode import code93
|
||||
|
||||
# Standard93 - uppercase, digits, some symbols
|
||||
barcode = code93.Standard93(
|
||||
value="HELLO93",
|
||||
barWidth=0.01*inch,
|
||||
barHeight=0.5*inch,
|
||||
)
|
||||
|
||||
# Extended93 - full ASCII
|
||||
barcode = code93.Extended93(
|
||||
value="Hello 93!",
|
||||
barWidth=0.01*inch,
|
||||
barHeight=0.5*inch,
|
||||
)
|
||||
|
||||
barcode.drawOn(canvas, x, y)
|
||||
```
|
||||
|
||||
### EAN-13 (European Article Number)
|
||||
|
||||
13-digit barcode for retail products.
|
||||
|
||||
```python
|
||||
from reportlab.graphics.barcode import eanbc
|
||||
|
||||
# Must be exactly 12 digits (13th is calculated checksum)
|
||||
barcode = eanbc.Ean13BarcodeWidget(
|
||||
value="123456789012"
|
||||
)
|
||||
|
||||
# Draw
|
||||
from reportlab.graphics import renderPDF
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
|
||||
d = Drawing()
|
||||
d.add(barcode)
|
||||
renderPDF.draw(d, canvas, x, y)
|
||||
```
|
||||
|
||||
### EAN-8
|
||||
|
||||
Short form, 8 digits.
|
||||
|
||||
```python
|
||||
from reportlab.graphics.barcode import eanbc
|
||||
|
||||
# Must be exactly 7 digits (8th is calculated)
|
||||
barcode = eanbc.Ean8BarcodeWidget(
|
||||
value="1234567"
|
||||
)
|
||||
```
|
||||
|
||||
### UPC-A
|
||||
|
||||
12-digit barcode used in North America.
|
||||
|
||||
```python
|
||||
from reportlab.graphics.barcode import usps
|
||||
|
||||
# 11 digits (12th is checksum)
|
||||
barcode = usps.UPCA(
|
||||
value="01234567890"
|
||||
)
|
||||
|
||||
barcode.drawOn(canvas, x, y)
|
||||
```
|
||||
|
||||
### ISBN (Books)
|
||||
|
||||
```python
|
||||
from reportlab.graphics.barcode.widgets import ISBNBarcodeWidget
|
||||
|
||||
# 10 or 13 digit ISBN
|
||||
barcode = ISBNBarcodeWidget(
|
||||
value="978-0-123456-78-9"
|
||||
)
|
||||
|
||||
# With pricing (EAN-5 add-on)
|
||||
barcode = ISBNBarcodeWidget(
|
||||
value="978-0-123456-78-9",
|
||||
price=True,
|
||||
)
|
||||
```
|
||||
|
||||
### QR Codes
|
||||
|
||||
Most versatile 2D barcode - can encode URLs, text, contact info, etc.
|
||||
|
||||
```python
|
||||
from reportlab.graphics.barcode.qr import QrCodeWidget
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.graphics import renderPDF
|
||||
|
||||
# Create QR code
|
||||
qr = QrCodeWidget("https://example.com")
|
||||
|
||||
# Size in pixels (QR codes are square)
|
||||
qr.barWidth = 100 # Width in points
|
||||
qr.barHeight = 100 # Height in points
|
||||
|
||||
# Error correction level
|
||||
# L = 7% recovery, M = 15%, Q = 25%, H = 30%
|
||||
qr.qrVersion = 1 # Auto-size (1-40, or None for auto)
|
||||
qr.errorLevel = 'M' # L, M, Q, H
|
||||
|
||||
# Draw
|
||||
d = Drawing()
|
||||
d.add(qr)
|
||||
renderPDF.draw(d, canvas, x, y)
|
||||
```
|
||||
|
||||
### QR Code - More Options
|
||||
|
||||
```python
|
||||
# URL QR Code
|
||||
qr = QrCodeWidget("https://example.com")
|
||||
|
||||
# Contact information (vCard)
|
||||
vcard_data = """BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
FN:John Doe
|
||||
TEL:+1-555-1234
|
||||
EMAIL:john@example.com
|
||||
END:VCARD"""
|
||||
qr = QrCodeWidget(vcard_data)
|
||||
|
||||
# WiFi credentials
|
||||
wifi_data = "WIFI:T:WPA;S:NetworkName;P:Password;;"
|
||||
qr = QrCodeWidget(wifi_data)
|
||||
|
||||
# Plain text
|
||||
qr = QrCodeWidget("Any text here")
|
||||
```
|
||||
|
||||
### Data Matrix (ECC200)
|
||||
|
||||
Compact 2D barcode for small items.
|
||||
|
||||
```python
|
||||
from reportlab.graphics.barcode.datamatrix import DataMatrixWidget
|
||||
|
||||
barcode = DataMatrixWidget(
|
||||
value="DATA123"
|
||||
)
|
||||
|
||||
d = Drawing()
|
||||
d.add(barcode)
|
||||
renderPDF.draw(d, canvas, x, y)
|
||||
```
|
||||
|
||||
### Postal Barcodes
|
||||
|
||||
```python
|
||||
from reportlab.graphics.barcode import usps
|
||||
|
||||
# POSTNET (older format)
|
||||
barcode = usps.POSTNET(
|
||||
value="55555-1234", # ZIP or ZIP+4
|
||||
)
|
||||
|
||||
# USPS 4-State (newer)
|
||||
barcode = usps.USPS_4State(
|
||||
value="12345678901234567890", # 20-digit routing code
|
||||
routing="12345678901"
|
||||
)
|
||||
|
||||
barcode.drawOn(canvas, x, y)
|
||||
```
|
||||
|
||||
### FIM (Facing Identification Mark)
|
||||
|
||||
Used for mail sorting.
|
||||
|
||||
```python
|
||||
from reportlab.graphics.barcode import usps
|
||||
|
||||
# FIM-A, FIM-B, FIM-C, or FIM-D
|
||||
barcode = usps.FIM(
|
||||
value="A" # A, B, C, or D
|
||||
)
|
||||
|
||||
barcode.drawOn(canvas, x, y)
|
||||
```
|
||||
|
||||
## Using Barcodes with Platypus
|
||||
|
||||
For flowing documents, wrap barcodes in Flowables.
|
||||
|
||||
### Simple Approach - Drawing Flowable
|
||||
|
||||
```python
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.graphics.barcode.qr import QrCodeWidget
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
# Create drawing
|
||||
d = Drawing(2*inch, 2*inch)
|
||||
|
||||
# Create barcode
|
||||
qr = QrCodeWidget("https://example.com")
|
||||
qr.barWidth = 2*inch
|
||||
qr.barHeight = 2*inch
|
||||
qr.x = 0
|
||||
qr.y = 0
|
||||
|
||||
d.add(qr)
|
||||
|
||||
# Add to story
|
||||
story.append(d)
|
||||
```
|
||||
|
||||
### Custom Flowable Wrapper
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Flowable
|
||||
from reportlab.graphics.barcode import code128
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
class BarcodeFlowable(Flowable):
|
||||
def __init__(self, code, barcode_type='code128', width=2*inch, height=0.5*inch):
|
||||
Flowable.__init__(self)
|
||||
self.code = code
|
||||
self.barcode_type = barcode_type
|
||||
self.width_val = width
|
||||
self.height_val = height
|
||||
|
||||
# Create barcode
|
||||
if barcode_type == 'code128':
|
||||
self.barcode = code128.Code128(code, barWidth=width/100, barHeight=height)
|
||||
# Add other types as needed
|
||||
|
||||
def draw(self):
|
||||
self.barcode.drawOn(self.canv, 0, 0)
|
||||
|
||||
def wrap(self, availWidth, availHeight):
|
||||
return (self.barcode.width, self.barcode.height)
|
||||
|
||||
# Use in story
|
||||
story.append(BarcodeFlowable("PRODUCT123"))
|
||||
```
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Product Label with Barcode
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.graphics.barcode import code128
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
def create_product_label(filename, product_code, product_name):
|
||||
c = canvas.Canvas(filename, pagesize=(4*inch, 2*inch))
|
||||
|
||||
# Product name
|
||||
c.setFont("Helvetica-Bold", 14)
|
||||
c.drawCentredString(2*inch, 1.5*inch, product_name)
|
||||
|
||||
# Barcode
|
||||
barcode = code128.Code128(product_code)
|
||||
barcode_width = barcode.width
|
||||
barcode_height = barcode.height
|
||||
|
||||
# Center barcode
|
||||
x = (4*inch - barcode_width) / 2
|
||||
y = 0.5*inch
|
||||
|
||||
barcode.drawOn(c, x, y)
|
||||
|
||||
# Code text
|
||||
c.setFont("Courier", 10)
|
||||
c.drawCentredString(2*inch, 0.3*inch, product_code)
|
||||
|
||||
c.save()
|
||||
|
||||
create_product_label("label.pdf", "ABC123456789", "Premium Widget")
|
||||
```
|
||||
|
||||
### QR Code Contact Card
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.graphics.barcode.qr import QrCodeWidget
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.graphics import renderPDF
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
def create_contact_card(filename, name, phone, email):
|
||||
c = canvas.Canvas(filename, pagesize=(3.5*inch, 2*inch))
|
||||
|
||||
# Contact info
|
||||
c.setFont("Helvetica-Bold", 12)
|
||||
c.drawString(0.5*inch, 1.5*inch, name)
|
||||
c.setFont("Helvetica", 10)
|
||||
c.drawString(0.5*inch, 1.3*inch, phone)
|
||||
c.drawString(0.5*inch, 1.1*inch, email)
|
||||
|
||||
# Create vCard data
|
||||
vcard = f"""BEGIN:VCARD
|
||||
VERSION:3.0
|
||||
FN:{name}
|
||||
TEL:{phone}
|
||||
EMAIL:{email}
|
||||
END:VCARD"""
|
||||
|
||||
# QR code
|
||||
qr = QrCodeWidget(vcard)
|
||||
qr.barWidth = 1.5*inch
|
||||
qr.barHeight = 1.5*inch
|
||||
|
||||
d = Drawing()
|
||||
d.add(qr)
|
||||
|
||||
renderPDF.draw(d, c, 1.8*inch, 0.2*inch)
|
||||
|
||||
c.save()
|
||||
|
||||
create_contact_card("contact.pdf", "John Doe", "+1-555-1234", "john@example.com")
|
||||
```
|
||||
|
||||
### Shipping Label with Multiple Barcodes
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.graphics.barcode import code128
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
def create_shipping_label(filename, tracking_code, zip_code):
|
||||
c = canvas.Canvas(filename, pagesize=(6*inch, 4*inch))
|
||||
|
||||
# Title
|
||||
c.setFont("Helvetica-Bold", 16)
|
||||
c.drawString(0.5*inch, 3.5*inch, "SHIPPING LABEL")
|
||||
|
||||
# Tracking barcode
|
||||
c.setFont("Helvetica", 10)
|
||||
c.drawString(0.5*inch, 2.8*inch, "Tracking Number:")
|
||||
|
||||
tracking_barcode = code128.Code128(tracking_code, barHeight=0.5*inch)
|
||||
tracking_barcode.drawOn(c, 0.5*inch, 2*inch)
|
||||
|
||||
c.setFont("Courier", 9)
|
||||
c.drawString(0.5*inch, 1.8*inch, tracking_code)
|
||||
|
||||
# Additional info can be added
|
||||
|
||||
c.save()
|
||||
|
||||
create_shipping_label("shipping.pdf", "1Z999AA10123456784", "12345")
|
||||
```
|
||||
|
||||
## Barcode Selection Guide
|
||||
|
||||
**Choose Code128 when:**
|
||||
- General purpose encoding
|
||||
- Need to encode numbers and letters
|
||||
- Want compact size
|
||||
- Widely supported
|
||||
|
||||
**Choose Code39 when:**
|
||||
- Older systems require it
|
||||
- Don't need lowercase letters
|
||||
- Want maximum compatibility
|
||||
|
||||
**Choose QR Code when:**
|
||||
- Need to encode URLs
|
||||
- Want mobile device scanning
|
||||
- Need high data capacity
|
||||
- Want error correction
|
||||
|
||||
**Choose EAN/UPC when:**
|
||||
- Retail product identification
|
||||
- Need industry-standard format
|
||||
- Global distribution
|
||||
|
||||
**Choose Data Matrix when:**
|
||||
- Very limited space
|
||||
- Small items (PCB, electronics)
|
||||
- Need 2D compact format
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Test scanning** early with actual barcode scanners/readers
|
||||
2. **Add quiet zones** (white space) around barcodes - set `quiet=1`
|
||||
3. **Choose appropriate height** - taller barcodes are easier to scan
|
||||
4. **Include human-readable text** below barcode for manual entry
|
||||
5. **Use Code128** as default for general purpose - it's compact and versatile
|
||||
6. **For URLs, use QR codes** - much easier for mobile users
|
||||
7. **Check barcode standards** for your industry (retail uses EAN/UPC)
|
||||
8. **Test print quality** - low DPI can make barcodes unscannable
|
||||
9. **Validate data** before encoding - wrong check digits cause issues
|
||||
10. **Consider error correction** for QR codes - use 'M' or 'H' for important data
|
||||
241
scientific-packages/reportlab/references/canvas_api.md
Normal file
241
scientific-packages/reportlab/references/canvas_api.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# Canvas API Reference
|
||||
|
||||
The Canvas API provides low-level, precise control over PDF generation using coordinate-based drawing.
|
||||
|
||||
## Coordinate System
|
||||
|
||||
- Origin (0, 0) is at the **lower-left corner** (not top-left like web graphics)
|
||||
- X-axis points right, Y-axis points upward
|
||||
- Units are in points (72 points = 1 inch)
|
||||
- Default page size is A4; explicitly specify page size for consistency
|
||||
|
||||
## Basic Setup
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.pagesizes import letter, A4
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
# Create canvas
|
||||
c = canvas.Canvas("output.pdf", pagesize=letter)
|
||||
|
||||
# Get page dimensions
|
||||
width, height = letter
|
||||
|
||||
# Draw content
|
||||
c.drawString(100, 100, "Hello World")
|
||||
|
||||
# Finish page and save
|
||||
c.showPage() # Complete current page
|
||||
c.save() # Write PDF to disk
|
||||
```
|
||||
|
||||
## Text Drawing
|
||||
|
||||
### Basic String Methods
|
||||
```python
|
||||
# Basic text placement
|
||||
c.drawString(x, y, text) # Left-aligned at x, y
|
||||
c.drawRightString(x, y, text) # Right-aligned at x, y
|
||||
c.drawCentredString(x, y, text) # Center-aligned at x, y
|
||||
|
||||
# Font control
|
||||
c.setFont(fontname, size) # e.g., "Helvetica", 12
|
||||
c.setFillColor(color) # Text color
|
||||
```
|
||||
|
||||
### Text Objects (Advanced)
|
||||
For complex text operations with multiple lines and precise control:
|
||||
|
||||
```python
|
||||
t = c.beginText(x, y)
|
||||
t.setFont("Times-Roman", 14)
|
||||
t.textLine("First line")
|
||||
t.textLine("Second line")
|
||||
t.setTextOrigin(x, y) # Reset position
|
||||
c.drawText(t)
|
||||
```
|
||||
|
||||
## Drawing Primitives
|
||||
|
||||
### Lines
|
||||
```python
|
||||
c.line(x1, y1, x2, y2) # Single line
|
||||
c.lines([(x1,y1,x2,y2), (x3,y3,x4,y4)]) # Multiple lines
|
||||
c.grid(xlist, ylist) # Grid from coordinate lists
|
||||
```
|
||||
|
||||
### Shapes
|
||||
```python
|
||||
c.rect(x, y, width, height, stroke=1, fill=0)
|
||||
c.roundRect(x, y, width, height, radius, stroke=1, fill=0)
|
||||
c.circle(x_ctr, y_ctr, r, stroke=1, fill=0)
|
||||
c.ellipse(x1, y1, x2, y2, stroke=1, fill=0)
|
||||
c.wedge(x, y, radius, startAng, extent, stroke=1, fill=0)
|
||||
```
|
||||
|
||||
### Bezier Curves
|
||||
```python
|
||||
c.bezier(x1, y1, x2, y2, x3, y3, x4, y4)
|
||||
```
|
||||
|
||||
## Path Objects
|
||||
|
||||
For complex shapes, use path objects:
|
||||
|
||||
```python
|
||||
p = c.beginPath()
|
||||
p.moveTo(x, y) # Move without drawing
|
||||
p.lineTo(x, y) # Draw line to point
|
||||
p.curveTo(x1, y1, x2, y2, x3, y3) # Bezier curve
|
||||
p.arc(x1, y1, x2, y2, startAng, extent)
|
||||
p.arcTo(x1, y1, x2, y2, startAng, extent)
|
||||
p.close() # Close path to start point
|
||||
|
||||
# Draw the path
|
||||
c.drawPath(p, stroke=1, fill=0)
|
||||
```
|
||||
|
||||
## Colors
|
||||
|
||||
### RGB (Screen Display)
|
||||
```python
|
||||
from reportlab.lib.colors import red, blue, Color
|
||||
|
||||
c.setFillColorRGB(r, g, b) # r, g, b are 0-1
|
||||
c.setStrokeColorRGB(r, g, b)
|
||||
c.setFillColor(red) # Named colors
|
||||
c.setStrokeColor(blue)
|
||||
|
||||
# Custom with alpha transparency
|
||||
c.setFillColor(Color(0.5, 0, 0, alpha=0.5))
|
||||
```
|
||||
|
||||
### CMYK (Professional Printing)
|
||||
```python
|
||||
from reportlab.lib.colors import CMYKColor, PCMYKColor
|
||||
|
||||
c.setFillColorCMYK(c, m, y, k) # 0-1 range
|
||||
c.setStrokeColorCMYK(c, m, y, k)
|
||||
|
||||
# Integer percentages (0-100)
|
||||
c.setFillColor(PCMYKColor(100, 50, 0, 0))
|
||||
```
|
||||
|
||||
## Line Styling
|
||||
|
||||
```python
|
||||
c.setLineWidth(width) # Thickness in points
|
||||
c.setLineCap(mode) # 0=butt, 1=round, 2=square
|
||||
c.setLineJoin(mode) # 0=miter, 1=round, 2=bevel
|
||||
c.setDash(array, phase) # e.g., [3, 3] for dotted line
|
||||
```
|
||||
|
||||
## Coordinate Transformations
|
||||
|
||||
**IMPORTANT:** Transformations are incremental and cumulative.
|
||||
|
||||
```python
|
||||
# Translation (move origin)
|
||||
c.translate(dx, dy)
|
||||
|
||||
# Rotation (in degrees, counterclockwise)
|
||||
c.rotate(theta)
|
||||
|
||||
# Scaling
|
||||
c.scale(xscale, yscale)
|
||||
|
||||
# Skewing
|
||||
c.skew(alpha, beta)
|
||||
```
|
||||
|
||||
### State Management
|
||||
```python
|
||||
# Save current graphics state
|
||||
c.saveState()
|
||||
|
||||
# ... apply transformations and draw ...
|
||||
|
||||
# Restore previous state
|
||||
c.restoreState()
|
||||
```
|
||||
|
||||
**Note:** State cannot be preserved across `showPage()` calls.
|
||||
|
||||
## Images
|
||||
|
||||
```python
|
||||
from reportlab.lib.utils import ImageReader
|
||||
|
||||
# Preferred method (with caching)
|
||||
c.drawImage(image_source, x, y, width=None, height=None,
|
||||
mask=None, preserveAspectRatio=False)
|
||||
|
||||
# image_source can be:
|
||||
# - Filename string
|
||||
# - PIL Image object
|
||||
# - ImageReader object
|
||||
|
||||
# For transparency, specify RGB mask range
|
||||
c.drawImage("logo.png", 100, 500, mask=[255, 255, 255, 255, 255, 255])
|
||||
|
||||
# Inline (inefficient, no caching)
|
||||
c.drawInlineImage(image_source, x, y, width=None, height=None)
|
||||
```
|
||||
|
||||
## Page Management
|
||||
|
||||
```python
|
||||
# Complete current page
|
||||
c.showPage()
|
||||
|
||||
# Set page size for next page
|
||||
c.setPageSize(size) # e.g., letter, A4
|
||||
|
||||
# Page compression (smaller files, slower generation)
|
||||
c = canvas.Canvas("output.pdf", pageCompression=1)
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Margins and Layout
|
||||
```python
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.lib.pagesizes import letter
|
||||
|
||||
width, height = letter
|
||||
margin = inch
|
||||
|
||||
# Draw within margins
|
||||
content_width = width - 2*margin
|
||||
content_height = height - 2*margin
|
||||
|
||||
# Text at top margin
|
||||
c.drawString(margin, height - margin, "Header")
|
||||
|
||||
# Text at bottom margin
|
||||
c.drawString(margin, margin, "Footer")
|
||||
```
|
||||
|
||||
### Headers and Footers
|
||||
```python
|
||||
def draw_header_footer(c, width, height):
|
||||
c.saveState()
|
||||
c.setFont("Helvetica", 9)
|
||||
c.drawString(inch, height - 0.5*inch, "Company Name")
|
||||
c.drawRightString(width - inch, 0.5*inch, f"Page {c.getPageNumber()}")
|
||||
c.restoreState()
|
||||
|
||||
# Call on each page
|
||||
draw_header_footer(c, width, height)
|
||||
c.showPage()
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always specify page size** - Different platforms have different defaults
|
||||
2. **Use variables for measurements** - `margin = inch` instead of hardcoded values
|
||||
3. **Match saveState/restoreState** - Always balance these calls
|
||||
4. **Apply transformations externally** for engineering drawings to prevent line width scaling
|
||||
5. **Use drawImage over drawInlineImage** for better performance with repeated images
|
||||
6. **Draw from bottom-up** - Remember Y-axis points upward
|
||||
624
scientific-packages/reportlab/references/charts_reference.md
Normal file
624
scientific-packages/reportlab/references/charts_reference.md
Normal file
@@ -0,0 +1,624 @@
|
||||
# Charts and Graphics Reference
|
||||
|
||||
Comprehensive guide to creating charts and data visualizations in ReportLab.
|
||||
|
||||
## Graphics Architecture
|
||||
|
||||
ReportLab's graphics system provides platform-independent drawing:
|
||||
|
||||
- **Drawings** - Container for shapes and charts
|
||||
- **Shapes** - Primitives (rectangles, circles, lines, polygons, paths)
|
||||
- **Renderers** - Convert to PDF, PostScript, SVG, or bitmaps (PNG, GIF, JPG)
|
||||
- **Coordinate System** - Y-axis points upward (like PDF, unlike web graphics)
|
||||
|
||||
## Quick Start
|
||||
|
||||
```python
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
||||
from reportlab.graphics import renderPDF
|
||||
|
||||
# Create drawing (canvas for chart)
|
||||
drawing = Drawing(400, 200)
|
||||
|
||||
# Create chart
|
||||
chart = VerticalBarChart()
|
||||
chart.x = 50
|
||||
chart.y = 50
|
||||
chart.width = 300
|
||||
chart.height = 125
|
||||
chart.data = [[100, 150, 130, 180]]
|
||||
chart.categoryAxis.categoryNames = ['Q1', 'Q2', 'Q3', 'Q4']
|
||||
|
||||
# Add chart to drawing
|
||||
drawing.add(chart)
|
||||
|
||||
# Render to PDF
|
||||
renderPDF.drawToFile(drawing, 'chart.pdf', 'Chart Title')
|
||||
|
||||
# Or add as flowable to Platypus document
|
||||
story.append(drawing)
|
||||
```
|
||||
|
||||
## Available Chart Types
|
||||
|
||||
### Bar Charts
|
||||
|
||||
```python
|
||||
from reportlab.graphics.charts.barcharts import (
|
||||
VerticalBarChart,
|
||||
HorizontalBarChart,
|
||||
)
|
||||
|
||||
# Vertical bar chart
|
||||
chart = VerticalBarChart()
|
||||
chart.x = 50
|
||||
chart.y = 50
|
||||
chart.width = 300
|
||||
chart.height = 150
|
||||
|
||||
# Single series
|
||||
chart.data = [[100, 150, 130, 180, 140]]
|
||||
|
||||
# Multiple series (grouped bars)
|
||||
chart.data = [
|
||||
[100, 150, 130, 180], # Series 1
|
||||
[80, 120, 110, 160], # Series 2
|
||||
]
|
||||
|
||||
# Categories
|
||||
chart.categoryAxis.categoryNames = ['Q1', 'Q2', 'Q3', 'Q4']
|
||||
|
||||
# Colors for each series
|
||||
chart.bars[0].fillColor = colors.blue
|
||||
chart.bars[1].fillColor = colors.red
|
||||
|
||||
# Bar spacing
|
||||
chart.barWidth = 10
|
||||
chart.groupSpacing = 10
|
||||
chart.barSpacing = 2
|
||||
```
|
||||
|
||||
### Stacked Bar Charts
|
||||
|
||||
```python
|
||||
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
||||
|
||||
chart = VerticalBarChart()
|
||||
# ... set position and size ...
|
||||
|
||||
chart.data = [
|
||||
[100, 150, 130, 180], # Bottom layer
|
||||
[50, 70, 60, 90], # Top layer
|
||||
]
|
||||
chart.categoryAxis.categoryNames = ['Q1', 'Q2', 'Q3', 'Q4']
|
||||
|
||||
# Enable stacking
|
||||
chart.barLabelFormat = 'values'
|
||||
chart.valueAxis.visible = 1
|
||||
```
|
||||
|
||||
### Horizontal Bar Charts
|
||||
|
||||
```python
|
||||
from reportlab.graphics.charts.barcharts import HorizontalBarChart
|
||||
|
||||
chart = HorizontalBarChart()
|
||||
chart.x = 50
|
||||
chart.y = 50
|
||||
chart.width = 300
|
||||
chart.height = 150
|
||||
|
||||
chart.data = [[100, 150, 130, 180]]
|
||||
chart.categoryAxis.categoryNames = ['Product A', 'Product B', 'Product C', 'Product D']
|
||||
|
||||
# Horizontal charts use valueAxis horizontally
|
||||
chart.valueAxis.valueMin = 0
|
||||
chart.valueAxis.valueMax = 200
|
||||
```
|
||||
|
||||
### Line Charts
|
||||
|
||||
```python
|
||||
from reportlab.graphics.charts.linecharts import HorizontalLineChart
|
||||
|
||||
chart = HorizontalLineChart()
|
||||
chart.x = 50
|
||||
chart.y = 50
|
||||
chart.width = 300
|
||||
chart.height = 150
|
||||
|
||||
# Multiple lines
|
||||
chart.data = [
|
||||
[100, 150, 130, 180, 140], # Line 1
|
||||
[80, 120, 110, 160, 130], # Line 2
|
||||
]
|
||||
|
||||
chart.categoryAxis.categoryNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May']
|
||||
|
||||
# Line styling
|
||||
chart.lines[0].strokeColor = colors.blue
|
||||
chart.lines[0].strokeWidth = 2
|
||||
chart.lines[1].strokeColor = colors.red
|
||||
chart.lines[1].strokeWidth = 2
|
||||
|
||||
# Show/hide points
|
||||
chart.lines[0].symbol = None # No symbols
|
||||
# Or use symbols from makeMarker()
|
||||
```
|
||||
|
||||
### Line Plots (X-Y Plots)
|
||||
|
||||
```python
|
||||
from reportlab.graphics.charts.lineplots import LinePlot
|
||||
|
||||
chart = LinePlot()
|
||||
chart.x = 50
|
||||
chart.y = 50
|
||||
chart.width = 300
|
||||
chart.height = 150
|
||||
|
||||
# Data as (x, y) tuples
|
||||
chart.data = [
|
||||
[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16)], # y = x^2
|
||||
[(0, 0), (1, 2), (2, 4), (3, 6), (4, 8)], # y = 2x
|
||||
]
|
||||
|
||||
# Both axes are value axes (not category)
|
||||
chart.xValueAxis.valueMin = 0
|
||||
chart.xValueAxis.valueMax = 5
|
||||
chart.yValueAxis.valueMin = 0
|
||||
chart.yValueAxis.valueMax = 20
|
||||
|
||||
# Line styling
|
||||
chart.lines[0].strokeColor = colors.blue
|
||||
chart.lines[1].strokeColor = colors.red
|
||||
```
|
||||
|
||||
### Pie Charts
|
||||
|
||||
```python
|
||||
from reportlab.graphics.charts.piecharts import Pie
|
||||
|
||||
chart = Pie()
|
||||
chart.x = 100
|
||||
chart.y = 50
|
||||
chart.width = 200
|
||||
chart.height = 200
|
||||
|
||||
chart.data = [25, 35, 20, 20]
|
||||
chart.labels = ['Q1', 'Q2', 'Q3', 'Q4']
|
||||
|
||||
# Slice colors
|
||||
chart.slices[0].fillColor = colors.blue
|
||||
chart.slices[1].fillColor = colors.red
|
||||
chart.slices[2].fillColor = colors.green
|
||||
chart.slices[3].fillColor = colors.yellow
|
||||
|
||||
# Pop out a slice
|
||||
chart.slices[1].popout = 10
|
||||
|
||||
# Label positioning
|
||||
chart.slices.strokeColor = colors.white
|
||||
chart.slices.strokeWidth = 2
|
||||
```
|
||||
|
||||
### Pie Chart with Side Labels
|
||||
|
||||
```python
|
||||
from reportlab.graphics.charts.piecharts import Pie
|
||||
|
||||
chart = Pie()
|
||||
# ... set position, data, labels ...
|
||||
|
||||
# Side label mode (labels in columns beside pie)
|
||||
chart.sideLabels = 1
|
||||
chart.sideLabelsOffset = 0.1 # Distance from pie
|
||||
|
||||
# Simple labels (not fancy layout)
|
||||
chart.simpleLabels = 1
|
||||
```
|
||||
|
||||
### Area Charts
|
||||
|
||||
```python
|
||||
from reportlab.graphics.charts.areacharts import HorizontalAreaChart
|
||||
|
||||
chart = HorizontalAreaChart()
|
||||
chart.x = 50
|
||||
chart.y = 50
|
||||
chart.width = 300
|
||||
chart.height = 150
|
||||
|
||||
# Areas stack on top of each other
|
||||
chart.data = [
|
||||
[100, 150, 130, 180], # Bottom area
|
||||
[50, 70, 60, 90], # Top area
|
||||
]
|
||||
|
||||
chart.categoryAxis.categoryNames = ['Q1', 'Q2', 'Q3', 'Q4']
|
||||
|
||||
# Area colors
|
||||
chart.strands[0].fillColor = colors.lightblue
|
||||
chart.strands[1].fillColor = colors.pink
|
||||
```
|
||||
|
||||
### Scatter Charts
|
||||
|
||||
```python
|
||||
from reportlab.graphics.charts.lineplots import ScatterPlot
|
||||
|
||||
chart = ScatterPlot()
|
||||
chart.x = 50
|
||||
chart.y = 50
|
||||
chart.width = 300
|
||||
chart.height = 150
|
||||
|
||||
# Data points
|
||||
chart.data = [
|
||||
[(1, 2), (2, 3), (3, 5), (4, 4), (5, 6)], # Series 1
|
||||
[(1, 1), (2, 2), (3, 3), (4, 3), (5, 4)], # Series 2
|
||||
]
|
||||
|
||||
# Hide lines, show points only
|
||||
chart.lines[0].strokeColor = None
|
||||
chart.lines[1].strokeColor = None
|
||||
|
||||
# Marker symbols
|
||||
from reportlab.graphics.widgets.markers import makeMarker
|
||||
chart.lines[0].symbol = makeMarker('Circle')
|
||||
chart.lines[1].symbol = makeMarker('Square')
|
||||
```
|
||||
|
||||
## Axes Configuration
|
||||
|
||||
### Category Axis (XCategoryAxis)
|
||||
|
||||
For categorical data (labels, not numbers):
|
||||
|
||||
```python
|
||||
# Access via chart
|
||||
axis = chart.categoryAxis
|
||||
|
||||
# Labels
|
||||
axis.categoryNames = ['Jan', 'Feb', 'Mar', 'Apr']
|
||||
|
||||
# Label angle (for long labels)
|
||||
axis.labels.angle = 45
|
||||
axis.labels.dx = 0
|
||||
axis.labels.dy = -5
|
||||
|
||||
# Label formatting
|
||||
axis.labels.fontSize = 10
|
||||
axis.labels.fontName = 'Helvetica'
|
||||
|
||||
# Visibility
|
||||
axis.visible = 1
|
||||
```
|
||||
|
||||
### Value Axis (YValueAxis)
|
||||
|
||||
For numeric data:
|
||||
|
||||
```python
|
||||
# Access via chart
|
||||
axis = chart.valueAxis
|
||||
|
||||
# Range
|
||||
axis.valueMin = 0
|
||||
axis.valueMax = 200
|
||||
axis.valueStep = 50 # Tick interval
|
||||
|
||||
# Or auto-configure
|
||||
axis.valueSteps = [0, 50, 100, 150, 200] # Explicit steps
|
||||
|
||||
# Label formatting
|
||||
axis.labels.fontSize = 10
|
||||
axis.labelTextFormat = '%d%%' # Add percentage sign
|
||||
|
||||
# Grid lines
|
||||
axis.strokeWidth = 1
|
||||
axis.strokeColor = colors.black
|
||||
```
|
||||
|
||||
## Styling and Customization
|
||||
|
||||
### Colors
|
||||
|
||||
```python
|
||||
from reportlab.lib import colors
|
||||
|
||||
# Named colors
|
||||
colors.blue, colors.red, colors.green, colors.yellow
|
||||
|
||||
# RGB
|
||||
colors.Color(0.5, 0.5, 0.5) # Grey
|
||||
|
||||
# With alpha
|
||||
colors.Color(1, 0, 0, alpha=0.5) # Semi-transparent red
|
||||
|
||||
# Hex colors
|
||||
colors.HexColor('#FF5733')
|
||||
```
|
||||
|
||||
### Line Styling
|
||||
|
||||
```python
|
||||
# For line charts
|
||||
chart.lines[0].strokeColor = colors.blue
|
||||
chart.lines[0].strokeWidth = 2
|
||||
chart.lines[0].strokeDashArray = [2, 2] # Dashed line
|
||||
```
|
||||
|
||||
### Bar Labels
|
||||
|
||||
```python
|
||||
# Show values on bars
|
||||
chart.barLabels.nudge = 5 # Offset from bar top
|
||||
chart.barLabels.fontSize = 8
|
||||
chart.barLabelFormat = '%d' # Number format
|
||||
|
||||
# For negative values
|
||||
chart.barLabels.dy = -5 # Position below bar
|
||||
```
|
||||
|
||||
## Legends
|
||||
|
||||
Charts can have associated legends:
|
||||
|
||||
```python
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
|
||||
# Create legend
|
||||
legend = Legend()
|
||||
legend.x = 350
|
||||
legend.y = 150
|
||||
legend.columnMaximum = 10
|
||||
|
||||
# Link to chart (share colors)
|
||||
legend.colorNamePairs = [
|
||||
(chart.bars[0].fillColor, 'Series 1'),
|
||||
(chart.bars[1].fillColor, 'Series 2'),
|
||||
]
|
||||
|
||||
# Add to drawing
|
||||
drawing.add(legend)
|
||||
```
|
||||
|
||||
## Drawing Shapes
|
||||
|
||||
### Basic Shapes
|
||||
|
||||
```python
|
||||
from reportlab.graphics.shapes import (
|
||||
Drawing, Rect, Circle, Ellipse, Line, Polygon, String
|
||||
)
|
||||
from reportlab.lib import colors
|
||||
|
||||
drawing = Drawing(400, 200)
|
||||
|
||||
# Rectangle
|
||||
rect = Rect(50, 50, 100, 50)
|
||||
rect.fillColor = colors.blue
|
||||
rect.strokeColor = colors.black
|
||||
rect.strokeWidth = 1
|
||||
drawing.add(rect)
|
||||
|
||||
# Circle
|
||||
circle = Circle(200, 100, 30)
|
||||
circle.fillColor = colors.red
|
||||
drawing.add(circle)
|
||||
|
||||
# Line
|
||||
line = Line(50, 150, 350, 150)
|
||||
line.strokeColor = colors.black
|
||||
line.strokeWidth = 2
|
||||
drawing.add(line)
|
||||
|
||||
# Text
|
||||
text = String(50, 175, "Label Text")
|
||||
text.fontSize = 12
|
||||
text.fontName = 'Helvetica'
|
||||
drawing.add(text)
|
||||
```
|
||||
|
||||
### Paths (Complex Shapes)
|
||||
|
||||
```python
|
||||
from reportlab.graphics.shapes import Path
|
||||
|
||||
path = Path()
|
||||
path.moveTo(50, 50)
|
||||
path.lineTo(100, 100)
|
||||
path.curveTo(120, 120, 140, 100, 150, 50)
|
||||
path.closePath()
|
||||
|
||||
path.fillColor = colors.lightblue
|
||||
path.strokeColor = colors.blue
|
||||
path.strokeWidth = 2
|
||||
|
||||
drawing.add(path)
|
||||
```
|
||||
|
||||
## Rendering Options
|
||||
|
||||
### Render to PDF
|
||||
|
||||
```python
|
||||
from reportlab.graphics import renderPDF
|
||||
|
||||
# Direct to file
|
||||
renderPDF.drawToFile(drawing, 'output.pdf', 'Chart Title')
|
||||
|
||||
# As flowable in Platypus
|
||||
story.append(drawing)
|
||||
```
|
||||
|
||||
### Render to Image
|
||||
|
||||
```python
|
||||
from reportlab.graphics import renderPM
|
||||
|
||||
# PNG
|
||||
renderPM.drawToFile(drawing, 'chart.png', fmt='PNG')
|
||||
|
||||
# GIF
|
||||
renderPM.drawToFile(drawing, 'chart.gif', fmt='GIF')
|
||||
|
||||
# JPG
|
||||
renderPM.drawToFile(drawing, 'chart.jpg', fmt='JPG')
|
||||
|
||||
# With specific DPI
|
||||
renderPM.drawToFile(drawing, 'chart.png', fmt='PNG', dpi=150)
|
||||
```
|
||||
|
||||
### Render to SVG
|
||||
|
||||
```python
|
||||
from reportlab.graphics import renderSVG
|
||||
|
||||
renderSVG.drawToFile(drawing, 'chart.svg')
|
||||
```
|
||||
|
||||
## Advanced Customization
|
||||
|
||||
### Inspect Properties
|
||||
|
||||
```python
|
||||
# List all properties
|
||||
print(chart.getProperties())
|
||||
|
||||
# Dump properties (for debugging)
|
||||
chart.dumpProperties()
|
||||
|
||||
# Set multiple properties
|
||||
chart.setProperties({
|
||||
'width': 400,
|
||||
'height': 200,
|
||||
'data': [[100, 150, 130]],
|
||||
})
|
||||
```
|
||||
|
||||
### Custom Colors for Series
|
||||
|
||||
```python
|
||||
# Define color scheme
|
||||
from reportlab.lib.colors import PCMYKColor
|
||||
|
||||
colors_list = [
|
||||
PCMYKColor(100, 67, 0, 23), # Blue
|
||||
PCMYKColor(0, 100, 100, 0), # Red
|
||||
PCMYKColor(66, 13, 0, 22), # Green
|
||||
]
|
||||
|
||||
# Apply to chart
|
||||
for i, color in enumerate(colors_list):
|
||||
chart.bars[i].fillColor = color
|
||||
```
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Sales Report Bar Chart
|
||||
|
||||
```python
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.graphics.charts.barcharts import VerticalBarChart
|
||||
from reportlab.graphics.charts.legends import Legend
|
||||
from reportlab.lib import colors
|
||||
|
||||
drawing = Drawing(400, 250)
|
||||
|
||||
# Create chart
|
||||
chart = VerticalBarChart()
|
||||
chart.x = 50
|
||||
chart.y = 50
|
||||
chart.width = 300
|
||||
chart.height = 150
|
||||
|
||||
# Data
|
||||
chart.data = [
|
||||
[120, 150, 180, 200], # 2023
|
||||
[100, 130, 160, 190], # 2022
|
||||
]
|
||||
chart.categoryAxis.categoryNames = ['Q1', 'Q2', 'Q3', 'Q4']
|
||||
|
||||
# Styling
|
||||
chart.bars[0].fillColor = colors.HexColor('#3498db')
|
||||
chart.bars[1].fillColor = colors.HexColor('#e74c3c')
|
||||
chart.valueAxis.valueMin = 0
|
||||
chart.valueAxis.valueMax = 250
|
||||
chart.categoryAxis.labels.fontSize = 10
|
||||
chart.valueAxis.labels.fontSize = 10
|
||||
|
||||
# Add legend
|
||||
legend = Legend()
|
||||
legend.x = 325
|
||||
legend.y = 200
|
||||
legend.columnMaximum = 2
|
||||
legend.colorNamePairs = [
|
||||
(chart.bars[0].fillColor, '2023'),
|
||||
(chart.bars[1].fillColor, '2022'),
|
||||
]
|
||||
|
||||
drawing.add(chart)
|
||||
drawing.add(legend)
|
||||
|
||||
# Add to story or save
|
||||
story.append(drawing)
|
||||
```
|
||||
|
||||
### Multi-Line Trend Chart
|
||||
|
||||
```python
|
||||
from reportlab.graphics.shapes import Drawing
|
||||
from reportlab.graphics.charts.linecharts import HorizontalLineChart
|
||||
from reportlab.lib import colors
|
||||
|
||||
drawing = Drawing(400, 250)
|
||||
|
||||
chart = HorizontalLineChart()
|
||||
chart.x = 50
|
||||
chart.y = 50
|
||||
chart.width = 320
|
||||
chart.height = 170
|
||||
|
||||
# Data
|
||||
chart.data = [
|
||||
[10, 15, 12, 18, 20, 25], # Product A
|
||||
[8, 10, 14, 16, 18, 22], # Product B
|
||||
[12, 11, 13, 15, 17, 19], # Product C
|
||||
]
|
||||
|
||||
chart.categoryAxis.categoryNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']
|
||||
|
||||
# Line styling
|
||||
chart.lines[0].strokeColor = colors.blue
|
||||
chart.lines[0].strokeWidth = 2
|
||||
chart.lines[1].strokeColor = colors.red
|
||||
chart.lines[1].strokeWidth = 2
|
||||
chart.lines[2].strokeColor = colors.green
|
||||
chart.lines[2].strokeWidth = 2
|
||||
|
||||
# Axes
|
||||
chart.valueAxis.valueMin = 0
|
||||
chart.valueAxis.valueMax = 30
|
||||
chart.categoryAxis.labels.angle = 0
|
||||
chart.categoryAxis.labels.fontSize = 9
|
||||
chart.valueAxis.labels.fontSize = 9
|
||||
|
||||
drawing.add(chart)
|
||||
story.append(drawing)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Set explicit dimensions** for Drawing to ensure consistent sizing
|
||||
2. **Position charts** with enough margin (x, y at least 30-50 from edge)
|
||||
3. **Use consistent color schemes** throughout document
|
||||
4. **Set valueMin and valueMax** explicitly for consistent scales
|
||||
5. **Test with realistic data** to ensure labels fit and don't overlap
|
||||
6. **Add legends** for multi-series charts
|
||||
7. **Angle category labels** if they're long (45° works well)
|
||||
8. **Keep it simple** - fewer data series are easier to read
|
||||
9. **Use appropriate chart types** - bars for comparisons, lines for trends, pies for proportions
|
||||
10. **Consider colorblind-friendly palettes** - avoid red/green combinations
|
||||
561
scientific-packages/reportlab/references/pdf_features.md
Normal file
561
scientific-packages/reportlab/references/pdf_features.md
Normal file
@@ -0,0 +1,561 @@
|
||||
# PDF Features Reference
|
||||
|
||||
Advanced PDF capabilities: links, bookmarks, forms, encryption, and metadata.
|
||||
|
||||
## Document Metadata
|
||||
|
||||
Set PDF document properties viewable in PDF readers.
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
c = canvas.Canvas("output.pdf")
|
||||
|
||||
# Set metadata
|
||||
c.setAuthor("John Doe")
|
||||
c.setTitle("Annual Report 2024")
|
||||
c.setSubject("Financial Analysis")
|
||||
c.setKeywords("finance, annual, report, 2024")
|
||||
c.setCreator("MyApp v1.0")
|
||||
|
||||
# ... draw content ...
|
||||
|
||||
c.save()
|
||||
```
|
||||
|
||||
With Platypus:
|
||||
|
||||
```python
|
||||
from reportlab.platypus import SimpleDocTemplate
|
||||
|
||||
doc = SimpleDocTemplate(
|
||||
"output.pdf",
|
||||
title="Annual Report 2024",
|
||||
author="John Doe",
|
||||
subject="Financial Analysis",
|
||||
)
|
||||
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
## Bookmarks and Destinations
|
||||
|
||||
Create internal navigation structure.
|
||||
|
||||
### Simple Bookmarks
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
c = canvas.Canvas("output.pdf")
|
||||
|
||||
# Create bookmark for current page
|
||||
c.bookmarkPage("intro") # Internal key
|
||||
c.addOutlineEntry("Introduction", "intro", level=0)
|
||||
|
||||
c.showPage()
|
||||
|
||||
# Another bookmark
|
||||
c.bookmarkPage("chapter1")
|
||||
c.addOutlineEntry("Chapter 1", "chapter1", level=0)
|
||||
|
||||
# Sub-sections
|
||||
c.bookmarkPage("section1_1")
|
||||
c.addOutlineEntry("Section 1.1", "section1_1", level=1) # Nested
|
||||
|
||||
c.save()
|
||||
```
|
||||
|
||||
### Bookmark Levels
|
||||
|
||||
```python
|
||||
# Create hierarchical outline
|
||||
c.bookmarkPage("ch1")
|
||||
c.addOutlineEntry("Chapter 1", "ch1", level=0)
|
||||
|
||||
c.bookmarkPage("ch1_s1")
|
||||
c.addOutlineEntry("Section 1.1", "ch1_s1", level=1)
|
||||
|
||||
c.bookmarkPage("ch1_s1_1")
|
||||
c.addOutlineEntry("Subsection 1.1.1", "ch1_s1_1", level=2)
|
||||
|
||||
c.bookmarkPage("ch2")
|
||||
c.addOutlineEntry("Chapter 2", "ch2", level=0)
|
||||
```
|
||||
|
||||
### Destination Fit Modes
|
||||
|
||||
Control how the page displays when navigating:
|
||||
|
||||
```python
|
||||
# bookmarkPage with fit mode
|
||||
c.bookmarkPage(
|
||||
key="chapter1",
|
||||
fit="Fit" # Fit entire page in window
|
||||
)
|
||||
|
||||
# Or use bookmarkHorizontalAbsolute
|
||||
c.bookmarkHorizontalAbsolute(key="section", top=500)
|
||||
|
||||
# Available fit modes:
|
||||
# "Fit" - Fit whole page
|
||||
# "FitH" - Fit horizontally
|
||||
# "FitV" - Fit vertically
|
||||
# "FitR" - Fit rectangle
|
||||
# "XYZ" - Specific position and zoom
|
||||
```
|
||||
|
||||
## Hyperlinks
|
||||
|
||||
### External Links
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
c = canvas.Canvas("output.pdf")
|
||||
|
||||
# Draw link rectangle
|
||||
c.linkURL(
|
||||
"https://www.example.com",
|
||||
rect=(1*inch, 5*inch, 3*inch, 5.5*inch), # (x1, y1, x2, y2)
|
||||
relative=0, # 0 for absolute positioning
|
||||
thickness=1,
|
||||
color=(0, 0, 1), # Blue
|
||||
dashArray=None
|
||||
)
|
||||
|
||||
# Draw text over link area
|
||||
c.setFillColorRGB(0, 0, 1) # Blue text
|
||||
c.drawString(1*inch, 5.2*inch, "Click here to visit example.com")
|
||||
|
||||
c.save()
|
||||
```
|
||||
|
||||
### Internal Links
|
||||
|
||||
Link to bookmarked locations within the document:
|
||||
|
||||
```python
|
||||
# Create destination
|
||||
c.bookmarkPage("target_section")
|
||||
|
||||
# Later, create link to that destination
|
||||
c.linkRect(
|
||||
"Link Text",
|
||||
"target_section", # Bookmark key
|
||||
rect=(1*inch, 3*inch, 2*inch, 3.2*inch),
|
||||
relative=0
|
||||
)
|
||||
```
|
||||
|
||||
### Links in Paragraphs
|
||||
|
||||
For Platypus documents:
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Paragraph
|
||||
|
||||
# External link
|
||||
text = '<link href="https://example.com" color="blue">Visit our website</link>'
|
||||
para = Paragraph(text, style)
|
||||
|
||||
# Internal link (to anchor)
|
||||
text = '<link href="#section1" color="blue">Go to Section 1</link>'
|
||||
para1 = Paragraph(text, style)
|
||||
|
||||
# Create anchor
|
||||
text = '<a name="section1"/>Section 1 Heading'
|
||||
para2 = Paragraph(text, heading_style)
|
||||
|
||||
story.append(para1)
|
||||
story.append(para2)
|
||||
```
|
||||
|
||||
## Interactive Forms
|
||||
|
||||
Create fillable PDF forms.
|
||||
|
||||
### Text Fields
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.pdfbase import pdfform
|
||||
from reportlab.lib.colors import black, white
|
||||
|
||||
c = canvas.Canvas("form.pdf")
|
||||
|
||||
# Create text field
|
||||
c.acroForm.textfield(
|
||||
name="name",
|
||||
tooltip="Enter your name",
|
||||
x=100,
|
||||
y=700,
|
||||
width=200,
|
||||
height=20,
|
||||
borderColor=black,
|
||||
fillColor=white,
|
||||
textColor=black,
|
||||
forceBorder=True,
|
||||
fontSize=12,
|
||||
maxlen=100, # Maximum character length
|
||||
)
|
||||
|
||||
# Label
|
||||
c.drawString(100, 725, "Name:")
|
||||
|
||||
c.save()
|
||||
```
|
||||
|
||||
### Checkboxes
|
||||
|
||||
```python
|
||||
# Create checkbox
|
||||
c.acroForm.checkbox(
|
||||
name="agree",
|
||||
tooltip="I agree to terms",
|
||||
x=100,
|
||||
y=650,
|
||||
size=20,
|
||||
buttonStyle='check', # 'check', 'circle', 'cross', 'diamond', 'square', 'star'
|
||||
borderColor=black,
|
||||
fillColor=white,
|
||||
textColor=black,
|
||||
forceBorder=True,
|
||||
checked=False, # Initial state
|
||||
)
|
||||
|
||||
c.drawString(130, 655, "I agree to the terms and conditions")
|
||||
```
|
||||
|
||||
### Radio Buttons
|
||||
|
||||
```python
|
||||
# Radio button group - only one can be selected
|
||||
c.acroForm.radio(
|
||||
name="payment", # Same name for group
|
||||
tooltip="Credit Card",
|
||||
value="credit", # Value when selected
|
||||
x=100,
|
||||
y=600,
|
||||
size=15,
|
||||
selected=False,
|
||||
)
|
||||
c.drawString(125, 603, "Credit Card")
|
||||
|
||||
c.acroForm.radio(
|
||||
name="payment", # Same name
|
||||
tooltip="PayPal",
|
||||
value="paypal",
|
||||
x=100,
|
||||
y=580,
|
||||
size=15,
|
||||
selected=False,
|
||||
)
|
||||
c.drawString(125, 583, "PayPal")
|
||||
```
|
||||
|
||||
### List Boxes
|
||||
|
||||
```python
|
||||
# Listbox with multiple options
|
||||
c.acroForm.listbox(
|
||||
name="country",
|
||||
tooltip="Select your country",
|
||||
value="US", # Default selected
|
||||
x=100,
|
||||
y=500,
|
||||
width=150,
|
||||
height=80,
|
||||
borderColor=black,
|
||||
fillColor=white,
|
||||
textColor=black,
|
||||
forceBorder=True,
|
||||
options=[
|
||||
("United States", "US"),
|
||||
("Canada", "CA"),
|
||||
("Mexico", "MX"),
|
||||
("Other", "OTHER"),
|
||||
], # List of (label, value) tuples
|
||||
multiple=False, # Allow multiple selections
|
||||
)
|
||||
```
|
||||
|
||||
### Choice (Dropdown)
|
||||
|
||||
```python
|
||||
# Dropdown menu
|
||||
c.acroForm.choice(
|
||||
name="state",
|
||||
tooltip="Select state",
|
||||
value="CA",
|
||||
x=100,
|
||||
y=450,
|
||||
width=150,
|
||||
height=20,
|
||||
borderColor=black,
|
||||
fillColor=white,
|
||||
textColor=black,
|
||||
forceBorder=True,
|
||||
options=[
|
||||
("California", "CA"),
|
||||
("New York", "NY"),
|
||||
("Texas", "TX"),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### Complete Form Example
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.lib.colors import black, white, lightgrey
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
def create_registration_form(filename):
|
||||
c = canvas.Canvas(filename, pagesize=letter)
|
||||
c.setFont("Helvetica-Bold", 16)
|
||||
c.drawString(inch, 10*inch, "Registration Form")
|
||||
|
||||
y = 9*inch
|
||||
c.setFont("Helvetica", 12)
|
||||
|
||||
# Name field
|
||||
c.drawString(inch, y, "Full Name:")
|
||||
c.acroForm.textfield(
|
||||
name="fullname",
|
||||
x=2*inch, y=y-5, width=4*inch, height=20,
|
||||
borderColor=black, fillColor=lightgrey, forceBorder=True
|
||||
)
|
||||
|
||||
# Email field
|
||||
y -= 0.5*inch
|
||||
c.drawString(inch, y, "Email:")
|
||||
c.acroForm.textfield(
|
||||
name="email",
|
||||
x=2*inch, y=y-5, width=4*inch, height=20,
|
||||
borderColor=black, fillColor=lightgrey, forceBorder=True
|
||||
)
|
||||
|
||||
# Age dropdown
|
||||
y -= 0.5*inch
|
||||
c.drawString(inch, y, "Age Group:")
|
||||
c.acroForm.choice(
|
||||
name="age_group",
|
||||
x=2*inch, y=y-5, width=2*inch, height=20,
|
||||
borderColor=black, fillColor=lightgrey, forceBorder=True,
|
||||
options=[("18-25", "18-25"), ("26-35", "26-35"),
|
||||
("36-50", "36-50"), ("51+", "51+")]
|
||||
)
|
||||
|
||||
# Newsletter checkbox
|
||||
y -= 0.5*inch
|
||||
c.acroForm.checkbox(
|
||||
name="newsletter",
|
||||
x=inch, y=y-5, size=15,
|
||||
buttonStyle='check', borderColor=black, forceBorder=True
|
||||
)
|
||||
c.drawString(inch + 25, y, "Subscribe to newsletter")
|
||||
|
||||
c.save()
|
||||
|
||||
create_registration_form("registration.pdf")
|
||||
```
|
||||
|
||||
## Encryption and Security
|
||||
|
||||
Protect PDFs with passwords and permissions.
|
||||
|
||||
### Basic Encryption
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
c = canvas.Canvas("secure.pdf")
|
||||
|
||||
# Encrypt with user password
|
||||
c.encrypt(
|
||||
userPassword="user123", # Password to open
|
||||
ownerPassword="owner456", # Password to change permissions
|
||||
canPrint=1, # Allow printing
|
||||
canModify=0, # Disallow modifications
|
||||
canCopy=1, # Allow text copying
|
||||
canAnnotate=0, # Disallow annotations
|
||||
strength=128, # 40 or 128 bit encryption
|
||||
)
|
||||
|
||||
# ... draw content ...
|
||||
|
||||
c.save()
|
||||
```
|
||||
|
||||
### Permission Settings
|
||||
|
||||
```python
|
||||
c.encrypt(
|
||||
userPassword="user123",
|
||||
ownerPassword="owner456",
|
||||
canPrint=1, # 1 = allow, 0 = deny
|
||||
canModify=0, # Prevent content modification
|
||||
canCopy=1, # Allow text/graphics copying
|
||||
canAnnotate=0, # Prevent comments/annotations
|
||||
strength=128, # Use 128-bit encryption
|
||||
)
|
||||
```
|
||||
|
||||
### Advanced Encryption
|
||||
|
||||
```python
|
||||
from reportlab.lib.pdfencrypt import StandardEncryption
|
||||
|
||||
# Create encryption object
|
||||
encrypt = StandardEncryption(
|
||||
userPassword="user123",
|
||||
ownerPassword="owner456",
|
||||
canPrint=1,
|
||||
canModify=0,
|
||||
canCopy=1,
|
||||
canAnnotate=1,
|
||||
strength=128,
|
||||
)
|
||||
|
||||
# Use with canvas
|
||||
c = canvas.Canvas("secure.pdf")
|
||||
c._doc.encrypt = encrypt
|
||||
|
||||
# ... draw content ...
|
||||
|
||||
c.save()
|
||||
```
|
||||
|
||||
### Platypus with Encryption
|
||||
|
||||
```python
|
||||
from reportlab.platypus import SimpleDocTemplate
|
||||
|
||||
doc = SimpleDocTemplate("secure.pdf")
|
||||
|
||||
# Set encryption
|
||||
doc.encrypt = True
|
||||
doc.canPrint = 1
|
||||
doc.canModify = 0
|
||||
|
||||
# Or use encrypt() method
|
||||
doc.encrypt = encrypt_object
|
||||
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
## Page Transitions
|
||||
|
||||
Add visual effects for presentations.
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
c = canvas.Canvas("presentation.pdf")
|
||||
|
||||
# Set transition for current page
|
||||
c.setPageTransition(
|
||||
effectname="Wipe", # Transition effect
|
||||
duration=1, # Duration in seconds
|
||||
direction=0 # Direction (effect-specific)
|
||||
)
|
||||
|
||||
# Available effects:
|
||||
# "Split", "Blinds", "Box", "Wipe", "Dissolve",
|
||||
# "Glitter", "R" (Replace), "Fly", "Push", "Cover",
|
||||
# "Uncover", "Fade"
|
||||
|
||||
# Direction values (effect-dependent):
|
||||
# 0, 90, 180, 270 for most directional effects
|
||||
|
||||
# Example: Slide with fade transition
|
||||
c.setFont("Helvetica-Bold", 24)
|
||||
c.drawString(100, 400, "Slide 1")
|
||||
c.setPageTransition("Fade", 0.5)
|
||||
c.showPage()
|
||||
|
||||
c.drawString(100, 400, "Slide 2")
|
||||
c.setPageTransition("Wipe", 1, 90)
|
||||
c.showPage()
|
||||
|
||||
c.save()
|
||||
```
|
||||
|
||||
## PDF/A Compliance
|
||||
|
||||
Create archival-quality PDFs.
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
c = canvas.Canvas("pdfa.pdf")
|
||||
|
||||
# Enable PDF/A-1b compliance
|
||||
c.setPageCompression(0) # PDF/A requires uncompressed
|
||||
# Note: Full PDF/A requires additional XMP metadata
|
||||
# This is simplified - full compliance needs more setup
|
||||
|
||||
# ... draw content ...
|
||||
|
||||
c.save()
|
||||
```
|
||||
|
||||
## Compression
|
||||
|
||||
Control file size vs generation speed.
|
||||
|
||||
```python
|
||||
# Enable page compression
|
||||
c = canvas.Canvas("output.pdf", pageCompression=1)
|
||||
|
||||
# Compression reduces file size but slows generation
|
||||
# 0 = no compression (faster, larger files)
|
||||
# 1 = compression (slower, smaller files)
|
||||
```
|
||||
|
||||
## Forms and XObjects
|
||||
|
||||
Reusable graphics elements.
|
||||
|
||||
```python
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
c = canvas.Canvas("output.pdf")
|
||||
|
||||
# Begin form (reusable object)
|
||||
c.beginForm("logo")
|
||||
c.setFillColorRGB(0, 0, 1)
|
||||
c.rect(0, 0, 100, 50, fill=1)
|
||||
c.setFillColorRGB(1, 1, 1)
|
||||
c.drawString(10, 20, "LOGO")
|
||||
c.endForm()
|
||||
|
||||
# Use form multiple times
|
||||
c.doForm("logo") # At current position
|
||||
c.translate(200, 0)
|
||||
c.doForm("logo") # At translated position
|
||||
c.translate(200, 0)
|
||||
c.doForm("logo")
|
||||
|
||||
c.save()
|
||||
|
||||
# Benefits: Smaller file size, faster rendering
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always set metadata** for professional documents
|
||||
2. **Use bookmarks** for documents > 10 pages
|
||||
3. **Make links visually distinct** (blue, underlined)
|
||||
4. **Test forms** in multiple PDF readers (behavior varies)
|
||||
5. **Use strong encryption (128-bit)** for sensitive data
|
||||
6. **Set both user and owner passwords** for full security
|
||||
7. **Enable printing** unless specifically restricted
|
||||
8. **Test page transitions** - some readers don't support all effects
|
||||
9. **Use meaningful bookmark titles** for navigation
|
||||
10. **Consider PDF/A** for long-term archival needs
|
||||
11. **Validate form field names** - must be unique and valid identifiers
|
||||
12. **Add tooltips** to form fields for better UX
|
||||
343
scientific-packages/reportlab/references/platypus_guide.md
Normal file
343
scientific-packages/reportlab/references/platypus_guide.md
Normal file
@@ -0,0 +1,343 @@
|
||||
# Platypus Guide - High-Level Page Layout
|
||||
|
||||
Platypus ("Page Layout and Typography Using Scripts") provides high-level document layout for complex, flowing documents with minimal code.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Platypus uses a layered design:
|
||||
|
||||
1. **DocTemplates** - Document container with page formatting rules
|
||||
2. **PageTemplates** - Specifications for different page layouts
|
||||
3. **Frames** - Regions where content flows
|
||||
4. **Flowables** - Content elements (paragraphs, tables, images, spacers)
|
||||
5. **Canvas** - Underlying rendering engine (usually hidden)
|
||||
|
||||
## Quick Start
|
||||
|
||||
```python
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
# Create document
|
||||
doc = SimpleDocTemplate("output.pdf", pagesize=letter,
|
||||
rightMargin=72, leftMargin=72,
|
||||
topMargin=72, bottomMargin=18)
|
||||
|
||||
# Create story (list of flowables)
|
||||
story = []
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Add content
|
||||
story.append(Paragraph("Title", styles['Title']))
|
||||
story.append(Spacer(1, 0.2*inch))
|
||||
story.append(Paragraph("Body text here", styles['BodyText']))
|
||||
story.append(PageBreak())
|
||||
|
||||
# Build PDF
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
## Core Components
|
||||
|
||||
### DocTemplates
|
||||
|
||||
#### SimpleDocTemplate
|
||||
Most common template for standard documents:
|
||||
|
||||
```python
|
||||
doc = SimpleDocTemplate(
|
||||
filename,
|
||||
pagesize=letter,
|
||||
rightMargin=72, # 1 inch = 72 points
|
||||
leftMargin=72,
|
||||
topMargin=72,
|
||||
bottomMargin=18,
|
||||
title=None, # PDF metadata
|
||||
author=None,
|
||||
subject=None
|
||||
)
|
||||
```
|
||||
|
||||
#### BaseDocTemplate (Advanced)
|
||||
For complex documents with multiple page layouts:
|
||||
|
||||
```python
|
||||
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame
|
||||
from reportlab.lib.pagesizes import letter
|
||||
|
||||
doc = BaseDocTemplate("output.pdf", pagesize=letter)
|
||||
|
||||
# Define frames (content regions)
|
||||
frame1 = Frame(doc.leftMargin, doc.bottomMargin,
|
||||
doc.width/2-6, doc.height, id='col1')
|
||||
frame2 = Frame(doc.leftMargin+doc.width/2+6, doc.bottomMargin,
|
||||
doc.width/2-6, doc.height, id='col2')
|
||||
|
||||
# Create page template
|
||||
template = PageTemplate(id='TwoCol', frames=[frame1, frame2])
|
||||
doc.addPageTemplates([template])
|
||||
|
||||
# Build with story
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
### Frames
|
||||
|
||||
Frames define regions where content flows:
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Frame
|
||||
|
||||
frame = Frame(
|
||||
x1, y1, # Lower-left corner
|
||||
width, height, # Dimensions
|
||||
leftPadding=6, # Internal padding
|
||||
bottomPadding=6,
|
||||
rightPadding=6,
|
||||
topPadding=6,
|
||||
id=None, # Optional identifier
|
||||
showBoundary=0 # 1 to show frame border (debugging)
|
||||
)
|
||||
```
|
||||
|
||||
### PageTemplates
|
||||
|
||||
Define page layouts with frames and optional functions:
|
||||
|
||||
```python
|
||||
def header_footer(canvas, doc):
|
||||
"""Called on each page for headers/footers"""
|
||||
canvas.saveState()
|
||||
canvas.setFont('Helvetica', 9)
|
||||
canvas.drawString(inch, 0.75*inch, f"Page {doc.page}")
|
||||
canvas.restoreState()
|
||||
|
||||
template = PageTemplate(
|
||||
id='Normal',
|
||||
frames=[frame],
|
||||
onPage=header_footer, # Function called for each page
|
||||
onPageEnd=None,
|
||||
pagesize=letter
|
||||
)
|
||||
```
|
||||
|
||||
## Flowables
|
||||
|
||||
Flowables are content elements that flow through frames.
|
||||
|
||||
### Common Flowables
|
||||
|
||||
```python
|
||||
from reportlab.platypus import (
|
||||
Paragraph, Spacer, PageBreak, FrameBreak,
|
||||
Image, Table, KeepTogether, CondPageBreak
|
||||
)
|
||||
|
||||
# Spacer - vertical whitespace
|
||||
Spacer(width, height)
|
||||
|
||||
# Page break - force new page
|
||||
PageBreak()
|
||||
|
||||
# Frame break - move to next frame
|
||||
FrameBreak()
|
||||
|
||||
# Conditional page break - break if less than N space remaining
|
||||
CondPageBreak(height)
|
||||
|
||||
# Keep together - prevent splitting across pages
|
||||
KeepTogether([flowable1, flowable2, ...])
|
||||
```
|
||||
|
||||
### Paragraph Flowable
|
||||
See `text_and_fonts.md` for detailed Paragraph usage.
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Paragraph
|
||||
from reportlab.lib.styles import ParagraphStyle
|
||||
|
||||
style = ParagraphStyle(
|
||||
'CustomStyle',
|
||||
fontSize=12,
|
||||
leading=14,
|
||||
alignment=0 # 0=left, 1=center, 2=right, 4=justify
|
||||
)
|
||||
|
||||
para = Paragraph("Text with <b>bold</b> and <i>italic</i>", style)
|
||||
story.append(para)
|
||||
```
|
||||
|
||||
### Image Flowable
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Image
|
||||
|
||||
# Auto-size to fit
|
||||
img = Image('photo.jpg')
|
||||
|
||||
# Fixed size
|
||||
img = Image('photo.jpg', width=2*inch, height=2*inch)
|
||||
|
||||
# Maintain aspect ratio with max width
|
||||
img = Image('photo.jpg', width=4*inch, height=3*inch,
|
||||
kind='proportional')
|
||||
|
||||
story.append(img)
|
||||
```
|
||||
|
||||
### Table Flowable
|
||||
See `tables_reference.md` for detailed Table usage.
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Table
|
||||
|
||||
data = [['Header1', 'Header2'],
|
||||
['Row1Col1', 'Row1Col2'],
|
||||
['Row2Col1', 'Row2Col2']]
|
||||
|
||||
table = Table(data, colWidths=[2*inch, 2*inch])
|
||||
story.append(table)
|
||||
```
|
||||
|
||||
## Page Layouts
|
||||
|
||||
### Single Column Document
|
||||
|
||||
```python
|
||||
doc = SimpleDocTemplate("output.pdf", pagesize=letter)
|
||||
story = []
|
||||
# Add flowables...
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
### Two-Column Layout
|
||||
|
||||
```python
|
||||
from reportlab.platypus import BaseDocTemplate, PageTemplate, Frame
|
||||
|
||||
doc = BaseDocTemplate("output.pdf", pagesize=letter)
|
||||
width, height = letter
|
||||
margin = inch
|
||||
|
||||
# Two side-by-side frames
|
||||
frame1 = Frame(margin, margin, width/2 - 1.5*margin, height - 2*margin, id='col1')
|
||||
frame2 = Frame(width/2 + 0.5*margin, margin, width/2 - 1.5*margin, height - 2*margin, id='col2')
|
||||
|
||||
template = PageTemplate(id='TwoCol', frames=[frame1, frame2])
|
||||
doc.addPageTemplates([template])
|
||||
|
||||
story = []
|
||||
# Content flows left column first, then right column
|
||||
# Add flowables...
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
### Multiple Page Templates
|
||||
|
||||
```python
|
||||
from reportlab.platypus import NextPageTemplate
|
||||
|
||||
# Define templates
|
||||
cover_template = PageTemplate(id='Cover', frames=[cover_frame])
|
||||
body_template = PageTemplate(id='Body', frames=[body_frame])
|
||||
|
||||
doc.addPageTemplates([cover_template, body_template])
|
||||
|
||||
story = []
|
||||
# Cover page content
|
||||
story.append(Paragraph("Cover", title_style))
|
||||
story.append(NextPageTemplate('Body')) # Switch to body template
|
||||
story.append(PageBreak())
|
||||
|
||||
# Body content
|
||||
story.append(Paragraph("Chapter 1", heading_style))
|
||||
# ...
|
||||
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
## Headers and Footers
|
||||
|
||||
Headers and footers are added via `onPage` callback functions:
|
||||
|
||||
```python
|
||||
def header_footer(canvas, doc):
|
||||
"""Draw header and footer on each page"""
|
||||
canvas.saveState()
|
||||
|
||||
# Header
|
||||
canvas.setFont('Helvetica-Bold', 12)
|
||||
canvas.drawCentredString(letter[0]/2, letter[1] - 0.5*inch,
|
||||
"Document Title")
|
||||
|
||||
# Footer
|
||||
canvas.setFont('Helvetica', 9)
|
||||
canvas.drawString(inch, 0.75*inch, "Left Footer")
|
||||
canvas.drawRightString(letter[0] - inch, 0.75*inch,
|
||||
f"Page {doc.page}")
|
||||
|
||||
canvas.restoreState()
|
||||
|
||||
# Apply to template
|
||||
template = PageTemplate(id='Normal', frames=[frame], onPage=header_footer)
|
||||
```
|
||||
|
||||
## Table of Contents
|
||||
|
||||
```python
|
||||
from reportlab.platypus import TableOfContents
|
||||
from reportlab.lib.styles import ParagraphStyle
|
||||
|
||||
# Create TOC
|
||||
toc = TableOfContents()
|
||||
toc.levelStyles = [
|
||||
ParagraphStyle(name='TOC1', fontSize=14, leftIndent=0),
|
||||
ParagraphStyle(name='TOC2', fontSize=12, leftIndent=20),
|
||||
]
|
||||
|
||||
story = []
|
||||
story.append(toc)
|
||||
story.append(PageBreak())
|
||||
|
||||
# Add entries
|
||||
story.append(Paragraph("Chapter 1<a name='ch1'/>", heading_style))
|
||||
toc.addEntry(0, "Chapter 1", doc.page, 'ch1')
|
||||
|
||||
# Must call build twice for TOC to populate
|
||||
doc.build(story)
|
||||
```
|
||||
|
||||
## Document Properties
|
||||
|
||||
```python
|
||||
from reportlab.lib.pagesizes import letter, A4
|
||||
from reportlab.lib.units import inch, cm, mm
|
||||
|
||||
# Page sizes
|
||||
letter # US Letter (8.5" x 11")
|
||||
A4 # ISO A4 (210mm x 297mm)
|
||||
landscape(letter) # Rotate to landscape
|
||||
|
||||
# Units
|
||||
inch # 72 points
|
||||
cm # 28.35 points
|
||||
mm # 2.835 points
|
||||
|
||||
# Custom page size
|
||||
custom_size = (6*inch, 9*inch)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use SimpleDocTemplate** for most documents - it handles common layouts
|
||||
2. **Build story list** completely before calling `doc.build(story)`
|
||||
3. **Use Spacer** for vertical spacing instead of empty Paragraphs
|
||||
4. **Group related content** with KeepTogether to prevent awkward page breaks
|
||||
5. **Test page breaks** early with realistic content amounts
|
||||
6. **Use styles consistently** - create style once, reuse throughout document
|
||||
7. **Set showBoundary=1** on Frames during development to visualize layout
|
||||
8. **Headers/footers go in onPage** callback, not in story
|
||||
9. **For long documents**, use BaseDocTemplate with multiple page templates
|
||||
10. **Build TOC documents twice** to properly populate table of contents
|
||||
442
scientific-packages/reportlab/references/tables_reference.md
Normal file
442
scientific-packages/reportlab/references/tables_reference.md
Normal file
@@ -0,0 +1,442 @@
|
||||
# Tables Reference
|
||||
|
||||
Comprehensive guide to creating and styling tables in ReportLab.
|
||||
|
||||
## Basic Table Creation
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Table, TableStyle
|
||||
from reportlab.lib import colors
|
||||
|
||||
# Simple data (list of lists or tuples)
|
||||
data = [
|
||||
['Header 1', 'Header 2', 'Header 3'],
|
||||
['Row 1, Col 1', 'Row 1, Col 2', 'Row 1, Col 3'],
|
||||
['Row 2, Col 1', 'Row 2, Col 2', 'Row 2, Col 3'],
|
||||
]
|
||||
|
||||
# Create table
|
||||
table = Table(data)
|
||||
|
||||
# Add to story
|
||||
story.append(table)
|
||||
```
|
||||
|
||||
## Table Constructor
|
||||
|
||||
```python
|
||||
table = Table(
|
||||
data, # Required: list of lists/tuples
|
||||
colWidths=None, # List of column widths or single value
|
||||
rowHeights=None, # List of row heights or single value
|
||||
style=None, # TableStyle object
|
||||
splitByRow=1, # Split across pages by rows (not columns)
|
||||
repeatRows=0, # Number of header rows to repeat
|
||||
repeatCols=0, # Number of header columns to repeat
|
||||
rowSplitRange=None, # Tuple (start, end) of splittable rows
|
||||
spaceBefore=None, # Space before table
|
||||
spaceAfter=None, # Space after table
|
||||
cornerRadii=None, # [TL, TR, BL, BR] for rounded corners
|
||||
)
|
||||
```
|
||||
|
||||
### Column Widths
|
||||
|
||||
```python
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
# Equal widths
|
||||
table = Table(data, colWidths=2*inch)
|
||||
|
||||
# Different widths per column
|
||||
table = Table(data, colWidths=[1.5*inch, 2*inch, 1*inch])
|
||||
|
||||
# Auto-calculate widths (default)
|
||||
table = Table(data)
|
||||
|
||||
# Percentage-based (of available width)
|
||||
table = Table(data, colWidths=[None, None, None]) # Equal auto-sizing
|
||||
```
|
||||
|
||||
## Cell Content Types
|
||||
|
||||
### Text and Newlines
|
||||
|
||||
```python
|
||||
# Newlines work in cells
|
||||
data = [
|
||||
['Line 1\nLine 2', 'Single line'],
|
||||
['Another\nmulti-line\ncell', 'Text'],
|
||||
]
|
||||
```
|
||||
|
||||
### Paragraph Objects
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Paragraph
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
data = [
|
||||
[Paragraph("Formatted <b>bold</b> text", styles['Normal']),
|
||||
Paragraph("More <i>italic</i> text", styles['Normal'])],
|
||||
]
|
||||
|
||||
table = Table(data)
|
||||
```
|
||||
|
||||
### Images
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Image
|
||||
|
||||
data = [
|
||||
['Description', Image('logo.png', width=1*inch, height=1*inch)],
|
||||
['Product', Image('product.jpg', width=2*inch, height=1.5*inch)],
|
||||
]
|
||||
|
||||
table = Table(data)
|
||||
```
|
||||
|
||||
### Nested Tables
|
||||
|
||||
```python
|
||||
# Create inner table
|
||||
inner_data = [['A', 'B'], ['C', 'D']]
|
||||
inner_table = Table(inner_data)
|
||||
|
||||
# Use in outer table
|
||||
outer_data = [
|
||||
['Label', inner_table],
|
||||
['Other', 'Content'],
|
||||
]
|
||||
|
||||
outer_table = Table(outer_data)
|
||||
```
|
||||
|
||||
## TableStyle
|
||||
|
||||
Styles are applied using command lists:
|
||||
|
||||
```python
|
||||
from reportlab.platypus import TableStyle
|
||||
from reportlab.lib import colors
|
||||
|
||||
style = TableStyle([
|
||||
# Command format: ('COMMAND', (startcol, startrow), (endcol, endrow), *args)
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.black), # Grid over all cells
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.grey), # Header background
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), # Header text color
|
||||
])
|
||||
|
||||
table = Table(data)
|
||||
table.setStyle(style)
|
||||
```
|
||||
|
||||
### Cell Coordinate System
|
||||
|
||||
- Columns and rows are 0-indexed: `(col, row)`
|
||||
- Negative indices count from end: `-1` is last column/row
|
||||
- `(0, 0)` is top-left cell
|
||||
- `(-1, -1)` is bottom-right cell
|
||||
|
||||
```python
|
||||
# Examples:
|
||||
(0, 0), (2, 0) # First three cells of header row
|
||||
(0, 1), (-1, -1) # All cells except header
|
||||
(0, 0), (-1, -1) # Entire table
|
||||
```
|
||||
|
||||
## Styling Commands
|
||||
|
||||
### Text Formatting
|
||||
|
||||
```python
|
||||
style = TableStyle([
|
||||
# Font name
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
|
||||
# Font size
|
||||
('FONTSIZE', (0, 0), (-1, -1), 10),
|
||||
|
||||
# Text color
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
||||
('TEXTCOLOR', (0, 1), (-1, -1), colors.black),
|
||||
|
||||
# Combined font command
|
||||
('FONT', (0, 0), (-1, 0), 'Helvetica-Bold', 12), # name, size
|
||||
])
|
||||
```
|
||||
|
||||
### Alignment
|
||||
|
||||
```python
|
||||
style = TableStyle([
|
||||
# Horizontal alignment: LEFT, CENTER, RIGHT, DECIMAL
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('ALIGN', (0, 1), (0, -1), 'LEFT'), # First column left
|
||||
('ALIGN', (1, 1), (-1, -1), 'RIGHT'), # Other columns right
|
||||
|
||||
# Vertical alignment: TOP, MIDDLE, BOTTOM
|
||||
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
||||
('VALIGN', (0, 0), (-1, 0), 'BOTTOM'), # Header bottom-aligned
|
||||
])
|
||||
```
|
||||
|
||||
### Cell Padding
|
||||
|
||||
```python
|
||||
style = TableStyle([
|
||||
# Individual padding
|
||||
('LEFTPADDING', (0, 0), (-1, -1), 12),
|
||||
('RIGHTPADDING', (0, 0), (-1, -1), 12),
|
||||
('TOPPADDING', (0, 0), (-1, -1), 6),
|
||||
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
|
||||
|
||||
# Or set all at once by setting each
|
||||
])
|
||||
```
|
||||
|
||||
### Background Colors
|
||||
|
||||
```python
|
||||
style = TableStyle([
|
||||
# Solid background
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.blue),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.lightgrey),
|
||||
|
||||
# Alternating row colors
|
||||
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightblue]),
|
||||
|
||||
# Alternating column colors
|
||||
('COLBACKGROUNDS', (0, 0), (-1, -1), [colors.white, colors.lightgrey]),
|
||||
])
|
||||
```
|
||||
|
||||
### Gradient Backgrounds
|
||||
|
||||
```python
|
||||
from reportlab.lib.colors import Color
|
||||
|
||||
style = TableStyle([
|
||||
# Vertical gradient (top to bottom)
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.blue),
|
||||
('VERTICALGRADIENT', (0, 0), (-1, 0),
|
||||
[colors.blue, colors.lightblue]),
|
||||
|
||||
# Horizontal gradient (left to right)
|
||||
('HORIZONTALGRADIENT', (0, 1), (-1, 1),
|
||||
[colors.red, colors.yellow]),
|
||||
])
|
||||
```
|
||||
|
||||
### Lines and Borders
|
||||
|
||||
```python
|
||||
style = TableStyle([
|
||||
# Complete grid
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.black),
|
||||
|
||||
# Box/outline only
|
||||
('BOX', (0, 0), (-1, -1), 2, colors.black),
|
||||
('OUTLINE', (0, 0), (-1, -1), 2, colors.black), # Same as BOX
|
||||
|
||||
# Inner grid only
|
||||
('INNERGRID', (0, 0), (-1, -1), 0.5, colors.grey),
|
||||
|
||||
# Directional lines
|
||||
('LINEABOVE', (0, 0), (-1, 0), 2, colors.black), # Header border
|
||||
('LINEBELOW', (0, 0), (-1, 0), 1, colors.black), # Header bottom
|
||||
('LINEBEFORE', (0, 0), (0, -1), 1, colors.black), # Left border
|
||||
('LINEAFTER', (-1, 0), (-1, -1), 1, colors.black), # Right border
|
||||
|
||||
# Thickness and color
|
||||
('LINEABOVE', (0, 1), (-1, 1), 0.5, colors.grey), # Thin grey line
|
||||
])
|
||||
```
|
||||
|
||||
### Cell Spanning
|
||||
|
||||
```python
|
||||
data = [
|
||||
['Spanning Header', '', ''], # Span will merge these
|
||||
['A', 'B', 'C'],
|
||||
['D', 'E', 'F'],
|
||||
]
|
||||
|
||||
style = TableStyle([
|
||||
# Span 3 columns in first row
|
||||
('SPAN', (0, 0), (2, 0)),
|
||||
|
||||
# Center the spanning cell
|
||||
('ALIGN', (0, 0), (2, 0), 'CENTER'),
|
||||
])
|
||||
|
||||
table = Table(data)
|
||||
table.setStyle(style)
|
||||
```
|
||||
|
||||
**Important:** Cells that are spanned over must contain empty strings `''`.
|
||||
|
||||
### Advanced Spanning Examples
|
||||
|
||||
```python
|
||||
# Span multiple rows and columns
|
||||
data = [
|
||||
['A', 'B', 'B', 'C'],
|
||||
['A', 'D', 'E', 'F'],
|
||||
['A', 'G', 'H', 'I'],
|
||||
]
|
||||
|
||||
style = TableStyle([
|
||||
# Span rows in column 0
|
||||
('SPAN', (0, 0), (0, 2)), # Merge A cells vertically
|
||||
|
||||
# Span columns in row 0
|
||||
('SPAN', (1, 0), (2, 0)), # Merge B cells horizontally
|
||||
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.black),
|
||||
])
|
||||
```
|
||||
|
||||
## Special Commands
|
||||
|
||||
### Rounded Corners
|
||||
|
||||
```python
|
||||
table = Table(data, cornerRadii=[5, 5, 5, 5]) # [TL, TR, BL, BR]
|
||||
|
||||
# Or in style
|
||||
style = TableStyle([
|
||||
('ROUNDEDCORNERS', [10, 10, 0, 0]), # Rounded top corners only
|
||||
])
|
||||
```
|
||||
|
||||
### No Split
|
||||
|
||||
Prevent table from splitting at specific locations:
|
||||
|
||||
```python
|
||||
style = TableStyle([
|
||||
# Don't split between rows 0 and 2
|
||||
('NOSPLIT', (0, 0), (-1, 2)),
|
||||
])
|
||||
```
|
||||
|
||||
### Split-Specific Styling
|
||||
|
||||
Apply styles only to first or last part when table splits:
|
||||
|
||||
```python
|
||||
style = TableStyle([
|
||||
# Style for first part after split
|
||||
('LINEBELOW', (0, 'splitfirst'), (-1, 'splitfirst'), 2, colors.red),
|
||||
|
||||
# Style for last part after split
|
||||
('LINEABOVE', (0, 'splitlast'), (-1, 'splitlast'), 2, colors.blue),
|
||||
])
|
||||
```
|
||||
|
||||
## Repeating Headers
|
||||
|
||||
```python
|
||||
# Repeat first row on each page
|
||||
table = Table(data, repeatRows=1)
|
||||
|
||||
# Repeat first 2 rows
|
||||
table = Table(data, repeatRows=2)
|
||||
```
|
||||
|
||||
## Complete Examples
|
||||
|
||||
### Styled Report Table
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Table, TableStyle
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
data = [
|
||||
['Product', 'Quantity', 'Unit Price', 'Total'],
|
||||
['Widget A', '10', '$5.00', '$50.00'],
|
||||
['Widget B', '5', '$12.00', '$60.00'],
|
||||
['Widget C', '20', '$3.00', '$60.00'],
|
||||
['', '', 'Subtotal:', '$170.00'],
|
||||
]
|
||||
|
||||
table = Table(data, colWidths=[2.5*inch, 1*inch, 1*inch, 1*inch])
|
||||
|
||||
style = TableStyle([
|
||||
# Header row
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.darkblue),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 12),
|
||||
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
|
||||
# Data rows
|
||||
('BACKGROUND', (0, 1), (-1, -2), colors.beige),
|
||||
('GRID', (0, 0), (-1, -2), 0.5, colors.grey),
|
||||
('ALIGN', (1, 1), (-1, -1), 'RIGHT'),
|
||||
('ALIGN', (0, 1), (0, -1), 'LEFT'),
|
||||
|
||||
# Total row
|
||||
('BACKGROUND', (0, -1), (-1, -1), colors.lightgrey),
|
||||
('LINEABOVE', (0, -1), (-1, -1), 2, colors.black),
|
||||
('FONTNAME', (2, -1), (-1, -1), 'Helvetica-Bold'),
|
||||
])
|
||||
|
||||
table.setStyle(style)
|
||||
```
|
||||
|
||||
### Alternating Row Colors
|
||||
|
||||
```python
|
||||
data = [
|
||||
['Name', 'Age', 'City'],
|
||||
['Alice', '30', 'New York'],
|
||||
['Bob', '25', 'Boston'],
|
||||
['Charlie', '35', 'Chicago'],
|
||||
['Diana', '28', 'Denver'],
|
||||
]
|
||||
|
||||
table = Table(data, colWidths=[2*inch, 1*inch, 1.5*inch])
|
||||
|
||||
style = TableStyle([
|
||||
# Header
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.darkslategray),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
|
||||
# Alternating rows (zebra striping)
|
||||
('ROWBACKGROUNDS', (0, 1), (-1, -1),
|
||||
[colors.white, colors.lightgrey]),
|
||||
|
||||
# Borders
|
||||
('BOX', (0, 0), (-1, -1), 2, colors.black),
|
||||
('LINEBELOW', (0, 0), (-1, 0), 2, colors.black),
|
||||
|
||||
# Padding
|
||||
('LEFTPADDING', (0, 0), (-1, -1), 12),
|
||||
('RIGHTPADDING', (0, 0), (-1, -1), 12),
|
||||
('TOPPADDING', (0, 0), (-1, -1), 6),
|
||||
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
|
||||
])
|
||||
|
||||
table.setStyle(style)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Set colWidths explicitly** for consistent layout
|
||||
2. **Use repeatRows** for multi-page tables with headers
|
||||
3. **Apply padding** for better readability (especially LEFTPADDING and RIGHTPADDING)
|
||||
4. **Use ROWBACKGROUNDS** for alternating colors instead of styling each row
|
||||
5. **Put empty strings** in cells that will be spanned
|
||||
6. **Test page breaks** early with realistic data amounts
|
||||
7. **Use Paragraph objects** in cells for complex formatted text
|
||||
8. **Set VALIGN to MIDDLE** for better appearance with varying row heights
|
||||
9. **Keep tables simple** - complex nested tables are hard to maintain
|
||||
10. **Use consistent styling** - define once, apply to all tables
|
||||
394
scientific-packages/reportlab/references/text_and_fonts.md
Normal file
394
scientific-packages/reportlab/references/text_and_fonts.md
Normal file
@@ -0,0 +1,394 @@
|
||||
# Text and Fonts Reference
|
||||
|
||||
Comprehensive guide to text formatting, paragraph styles, and font handling in ReportLab.
|
||||
|
||||
## Text Encoding
|
||||
|
||||
**IMPORTANT:** All text input should be UTF-8 encoded or Python Unicode objects (since ReportLab 2.0).
|
||||
|
||||
```python
|
||||
# Correct - UTF-8 strings
|
||||
text = "Hello 世界 مرحبا"
|
||||
para = Paragraph(text, style)
|
||||
|
||||
# For legacy data, convert first
|
||||
import codecs
|
||||
decoded_text = codecs.decode(legacy_bytes, 'latin-1')
|
||||
```
|
||||
|
||||
## Paragraph Styles
|
||||
|
||||
### Creating Styles
|
||||
|
||||
```python
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
|
||||
from reportlab.lib.colors import black, blue, red
|
||||
from reportlab.lib.units import inch
|
||||
|
||||
# Get default styles
|
||||
styles = getSampleStyleSheet()
|
||||
normal = styles['Normal']
|
||||
heading = styles['Heading1']
|
||||
|
||||
# Create custom style
|
||||
custom_style = ParagraphStyle(
|
||||
'CustomStyle',
|
||||
parent=normal, # Inherit from another style
|
||||
|
||||
# Font properties
|
||||
fontName='Helvetica',
|
||||
fontSize=12,
|
||||
leading=14, # Line spacing (should be > fontSize)
|
||||
|
||||
# Indentation (in points)
|
||||
leftIndent=0,
|
||||
rightIndent=0,
|
||||
firstLineIndent=0, # Positive = indent, negative = outdent
|
||||
|
||||
# Spacing
|
||||
spaceBefore=0,
|
||||
spaceAfter=0,
|
||||
|
||||
# Alignment
|
||||
alignment=TA_LEFT, # TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
|
||||
|
||||
# Colors
|
||||
textColor=black,
|
||||
backColor=None, # Background color
|
||||
|
||||
# Borders
|
||||
borderWidth=0,
|
||||
borderColor=None,
|
||||
borderPadding=0,
|
||||
borderRadius=None,
|
||||
|
||||
# Bullets
|
||||
bulletFontName='Helvetica',
|
||||
bulletFontSize=12,
|
||||
bulletIndent=0,
|
||||
bulletText=None, # Text for bullets (e.g., '•')
|
||||
|
||||
# Advanced
|
||||
wordWrap=None, # 'CJK' for Asian languages
|
||||
allowWidows=1, # Allow widow lines
|
||||
allowOrphans=0, # Prevent orphan lines
|
||||
endDots=None, # Trailing dots for TOC entries
|
||||
splitLongWords=1,
|
||||
hyphenationLang=None, # 'en_US', etc. (requires pyphen)
|
||||
)
|
||||
|
||||
# Add to stylesheet
|
||||
styles.add(custom_style)
|
||||
```
|
||||
|
||||
### Built-in Styles
|
||||
|
||||
```python
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Common styles
|
||||
styles['Normal'] # Body text
|
||||
styles['BodyText'] # Similar to Normal
|
||||
styles['Heading1'] # Top-level heading
|
||||
styles['Heading2'] # Second-level heading
|
||||
styles['Heading3'] # Third-level heading
|
||||
styles['Title'] # Document title
|
||||
styles['Bullet'] # Bulleted list items
|
||||
styles['Definition'] # Definition text
|
||||
styles['Code'] # Code samples
|
||||
```
|
||||
|
||||
## Paragraph Formatting
|
||||
|
||||
### Basic Paragraph
|
||||
|
||||
```python
|
||||
from reportlab.platypus import Paragraph
|
||||
|
||||
para = Paragraph("This is a paragraph.", style)
|
||||
story.append(para)
|
||||
```
|
||||
|
||||
### Inline Formatting Tags
|
||||
|
||||
```python
|
||||
text = """
|
||||
<b>Bold text</b>
|
||||
<i>Italic text</i>
|
||||
<u>Underlined text</u>
|
||||
<strike>Strikethrough text</strike>
|
||||
<strong>Strong (bold) text</strong>
|
||||
"""
|
||||
|
||||
para = Paragraph(text, normal_style)
|
||||
```
|
||||
|
||||
### Font Control
|
||||
|
||||
```python
|
||||
text = """
|
||||
<font face="Courier" size="14" color="blue">
|
||||
Custom font, size, and color
|
||||
</font>
|
||||
|
||||
<font color="#FF0000">Hex color codes work too</font>
|
||||
"""
|
||||
|
||||
para = Paragraph(text, normal_style)
|
||||
```
|
||||
|
||||
### Superscripts and Subscripts
|
||||
|
||||
```python
|
||||
text = """
|
||||
H<sub>2</sub>O is water.
|
||||
E=mc<super>2</super> or E=mc<sup>2</sup>
|
||||
X<sub><i>i</i></sub> for subscripted variables
|
||||
"""
|
||||
|
||||
para = Paragraph(text, normal_style)
|
||||
```
|
||||
|
||||
### Greek Letters
|
||||
|
||||
```python
|
||||
text = """
|
||||
<greek>alpha</greek>, <greek>beta</greek>, <greek>gamma</greek>
|
||||
<greek>epsilon</greek>, <greek>pi</greek>, <greek>omega</greek>
|
||||
"""
|
||||
|
||||
para = Paragraph(text, normal_style)
|
||||
```
|
||||
|
||||
### Links
|
||||
|
||||
```python
|
||||
# External link
|
||||
text = '<link href="https://example.com" color="blue">Click here</link>'
|
||||
|
||||
# Internal link (to bookmark)
|
||||
text = '<link href="#section1" color="blue">Go to Section 1</link>'
|
||||
|
||||
# Anchor for internal links
|
||||
text = '<a name="section1"/>Section 1 Heading'
|
||||
|
||||
para = Paragraph(text, normal_style)
|
||||
```
|
||||
|
||||
### Inline Images
|
||||
|
||||
```python
|
||||
text = """
|
||||
Here is an inline image: <img src="icon.png" width="12" height="12" valign="middle"/>
|
||||
"""
|
||||
|
||||
para = Paragraph(text, normal_style)
|
||||
```
|
||||
|
||||
### Line Breaks
|
||||
|
||||
```python
|
||||
text = """
|
||||
First line<br/>
|
||||
Second line<br/>
|
||||
Third line
|
||||
"""
|
||||
|
||||
para = Paragraph(text, normal_style)
|
||||
```
|
||||
|
||||
## Font Handling
|
||||
|
||||
### Standard Fonts
|
||||
|
||||
ReportLab includes 14 standard PDF fonts (no embedding needed):
|
||||
|
||||
```python
|
||||
# Helvetica family
|
||||
'Helvetica'
|
||||
'Helvetica-Bold'
|
||||
'Helvetica-Oblique'
|
||||
'Helvetica-BoldOblique'
|
||||
|
||||
# Times family
|
||||
'Times-Roman'
|
||||
'Times-Bold'
|
||||
'Times-Italic'
|
||||
'Times-BoldItalic'
|
||||
|
||||
# Courier family
|
||||
'Courier'
|
||||
'Courier-Bold'
|
||||
'Courier-Oblique'
|
||||
'Courier-BoldOblique'
|
||||
|
||||
# Symbol and Dingbats
|
||||
'Symbol'
|
||||
'ZapfDingbats'
|
||||
```
|
||||
|
||||
### TrueType Fonts
|
||||
|
||||
```python
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
|
||||
# Register single font
|
||||
pdfmetrics.registerFont(TTFont('CustomFont', 'CustomFont.ttf'))
|
||||
|
||||
# Use in Canvas
|
||||
canvas.setFont('CustomFont', 12)
|
||||
|
||||
# Use in Paragraph style
|
||||
style = ParagraphStyle('Custom', fontName='CustomFont', fontSize=12)
|
||||
```
|
||||
|
||||
### Font Families
|
||||
|
||||
Register related fonts as a family for bold/italic support:
|
||||
|
||||
```python
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.lib.fonts import addMapping
|
||||
|
||||
# Register fonts
|
||||
pdfmetrics.registerFont(TTFont('Vera', 'Vera.ttf'))
|
||||
pdfmetrics.registerFont(TTFont('VeraBd', 'VeraBd.ttf'))
|
||||
pdfmetrics.registerFont(TTFont('VeraIt', 'VeraIt.ttf'))
|
||||
pdfmetrics.registerFont(TTFont('VeraBI', 'VeraBI.ttf'))
|
||||
|
||||
# Map family (normal, bold, italic, bold-italic)
|
||||
addMapping('Vera', 0, 0, 'Vera') # normal
|
||||
addMapping('Vera', 1, 0, 'VeraBd') # bold
|
||||
addMapping('Vera', 0, 1, 'VeraIt') # italic
|
||||
addMapping('Vera', 1, 1, 'VeraBI') # bold-italic
|
||||
|
||||
# Now <b> and <i> tags work with this family
|
||||
style = ParagraphStyle('VeraStyle', fontName='Vera', fontSize=12)
|
||||
para = Paragraph("Normal <b>Bold</b> <i>Italic</i> <b><i>Both</i></b>", style)
|
||||
```
|
||||
|
||||
### Font Search Paths
|
||||
|
||||
```python
|
||||
from reportlab.pdfbase.ttfonts import TTFSearchPath
|
||||
|
||||
# Add custom font directory
|
||||
TTFSearchPath.append('/path/to/fonts/')
|
||||
|
||||
# Now fonts in this directory can be found by name
|
||||
pdfmetrics.registerFont(TTFont('MyFont', 'MyFont.ttf'))
|
||||
```
|
||||
|
||||
### Asian Language Support
|
||||
|
||||
#### Using Adobe Language Packs (no embedding)
|
||||
|
||||
```python
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.cidfonts import UnicodeCIDFont
|
||||
|
||||
# Register CID fonts
|
||||
pdfmetrics.registerFont(UnicodeCIDFont('HeiseiMin-W3')) # Japanese
|
||||
pdfmetrics.registerFont(UnicodeCIDFont('STSong-Light')) # Chinese (Simplified)
|
||||
pdfmetrics.registerFont(UnicodeCIDFont('MSung-Light')) # Chinese (Traditional)
|
||||
pdfmetrics.registerFont(UnicodeCIDFont('HYSMyeongJo-Medium')) # Korean
|
||||
|
||||
# Use in styles
|
||||
style = ParagraphStyle('Japanese', fontName='HeiseiMin-W3', fontSize=12)
|
||||
para = Paragraph("日本語テキスト", style)
|
||||
```
|
||||
|
||||
#### Using TrueType Fonts with Asian Characters
|
||||
|
||||
```python
|
||||
# Register TrueType font with full Unicode support
|
||||
pdfmetrics.registerFont(TTFont('SimSun', 'simsun.ttc'))
|
||||
|
||||
style = ParagraphStyle('Chinese', fontName='SimSun', fontSize=12, wordWrap='CJK')
|
||||
para = Paragraph("中文文本", style)
|
||||
```
|
||||
|
||||
Note: Set `wordWrap='CJK'` for proper line breaking in Asian languages.
|
||||
|
||||
## Numbering and Sequences
|
||||
|
||||
Auto-numbering using `<seq>` tags:
|
||||
|
||||
```python
|
||||
# Simple numbering
|
||||
text = "<seq id='chapter'/> Introduction" # Outputs: 1 Introduction
|
||||
text = "<seq id='chapter'/> Methods" # Outputs: 2 Methods
|
||||
|
||||
# Reset counter
|
||||
text = "<seq id='figure' reset='yes'/>"
|
||||
|
||||
# Formatting templates
|
||||
text = "Figure <seq template='%(chapter)s-%(figure+)s' id='figure'/>"
|
||||
# Outputs: Figure 1-1, Figure 1-2, etc.
|
||||
|
||||
# Multi-level numbering
|
||||
text = "Section <seq template='%(chapter)s.%(section+)s' id='section'/>"
|
||||
```
|
||||
|
||||
## Bullets and Lists
|
||||
|
||||
### Using Bullet Style
|
||||
|
||||
```python
|
||||
bullet_style = ParagraphStyle(
|
||||
'Bullet',
|
||||
parent=normal_style,
|
||||
leftIndent=20,
|
||||
bulletIndent=10,
|
||||
bulletText='•', # Unicode bullet
|
||||
bulletFontName='Helvetica',
|
||||
)
|
||||
|
||||
story.append(Paragraph("First item", bullet_style))
|
||||
story.append(Paragraph("Second item", bullet_style))
|
||||
story.append(Paragraph("Third item", bullet_style))
|
||||
```
|
||||
|
||||
### Custom Bullet Characters
|
||||
|
||||
```python
|
||||
# Different bullet styles
|
||||
bulletText='•' # Filled circle
|
||||
bulletText='◦' # Open circle
|
||||
bulletText='▪' # Square
|
||||
bulletText='▸' # Triangle
|
||||
bulletText='→' # Arrow
|
||||
bulletText='1.' # Numbers
|
||||
bulletText='a)' # Letters
|
||||
```
|
||||
|
||||
## Text Measurement
|
||||
|
||||
```python
|
||||
from reportlab.pdfbase.pdfmetrics import stringWidth
|
||||
|
||||
# Measure string width
|
||||
width = stringWidth("Hello World", "Helvetica", 12)
|
||||
|
||||
# Check if text fits in available width
|
||||
max_width = 200
|
||||
if stringWidth(text, font_name, font_size) > max_width:
|
||||
# Text is too wide
|
||||
pass
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use UTF-8** for text input
|
||||
2. **Set leading > fontSize** for readability (typically fontSize + 2)
|
||||
3. **Register font families** for proper bold/italic support
|
||||
4. **Escape HTML** if displaying user content: use `<` for < and `>` for >
|
||||
5. **Use getSampleStyleSheet()** as a starting point, don't create all styles from scratch
|
||||
6. **Test Asian fonts** early if supporting multi-language content
|
||||
7. **Set wordWrap='CJK'** for Chinese/Japanese/Korean text
|
||||
8. **Use stringWidth()** to check if text fits before rendering
|
||||
9. **Define styles once** at document start, reuse throughout
|
||||
10. **Enable hyphenation** for justified text: `hyphenationLang='en_US'` (requires pyphen package)
|
||||
229
scientific-packages/reportlab/scripts/quick_document.py
Normal file
229
scientific-packages/reportlab/scripts/quick_document.py
Normal file
@@ -0,0 +1,229 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Quick Document Generator - Helper for creating simple ReportLab documents
|
||||
|
||||
This script provides utility functions for quickly creating common document types
|
||||
without writing boilerplate code.
|
||||
"""
|
||||
|
||||
from reportlab.lib.pagesizes import letter, A4
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.lib.units import inch
|
||||
from reportlab.lib import colors
|
||||
from reportlab.platypus import (
|
||||
SimpleDocTemplate, Paragraph, Spacer, PageBreak,
|
||||
Table, TableStyle, Image, KeepTogether
|
||||
)
|
||||
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def create_simple_document(filename, title, author="", content_blocks=None, pagesize=letter):
|
||||
"""
|
||||
Create a simple document with title and content blocks.
|
||||
|
||||
Args:
|
||||
filename: Output PDF filename
|
||||
title: Document title
|
||||
author: Document author (optional)
|
||||
content_blocks: List of dicts with 'type' and 'content' keys
|
||||
type can be: 'heading', 'paragraph', 'bullet', 'space'
|
||||
pagesize: Page size (default: letter)
|
||||
|
||||
Example content_blocks:
|
||||
[
|
||||
{'type': 'heading', 'content': 'Introduction'},
|
||||
{'type': 'paragraph', 'content': 'This is a paragraph.'},
|
||||
{'type': 'bullet', 'content': 'Bullet point item'},
|
||||
{'type': 'space', 'height': 0.2}, # height in inches
|
||||
]
|
||||
"""
|
||||
if content_blocks is None:
|
||||
content_blocks = []
|
||||
|
||||
# Create document
|
||||
doc = SimpleDocTemplate(
|
||||
filename,
|
||||
pagesize=pagesize,
|
||||
rightMargin=72,
|
||||
leftMargin=72,
|
||||
topMargin=72,
|
||||
bottomMargin=18,
|
||||
title=title,
|
||||
author=author
|
||||
)
|
||||
|
||||
# Get styles
|
||||
styles = getSampleStyleSheet()
|
||||
story = []
|
||||
|
||||
# Add title
|
||||
story.append(Paragraph(title, styles['Title']))
|
||||
story.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# Process content blocks
|
||||
for block in content_blocks:
|
||||
block_type = block.get('type', 'paragraph')
|
||||
content = block.get('content', '')
|
||||
|
||||
if block_type == 'heading':
|
||||
story.append(Paragraph(content, styles['Heading1']))
|
||||
story.append(Spacer(1, 0.1*inch))
|
||||
|
||||
elif block_type == 'heading2':
|
||||
story.append(Paragraph(content, styles['Heading2']))
|
||||
story.append(Spacer(1, 0.1*inch))
|
||||
|
||||
elif block_type == 'paragraph':
|
||||
story.append(Paragraph(content, styles['BodyText']))
|
||||
story.append(Spacer(1, 0.1*inch))
|
||||
|
||||
elif block_type == 'bullet':
|
||||
story.append(Paragraph(content, styles['Bullet']))
|
||||
|
||||
elif block_type == 'space':
|
||||
height = block.get('height', 0.2)
|
||||
story.append(Spacer(1, height*inch))
|
||||
|
||||
elif block_type == 'pagebreak':
|
||||
story.append(PageBreak())
|
||||
|
||||
# Build PDF
|
||||
doc.build(story)
|
||||
return filename
|
||||
|
||||
|
||||
def create_styled_table(data, col_widths=None, style_name='default'):
|
||||
"""
|
||||
Create a styled table with common styling presets.
|
||||
|
||||
Args:
|
||||
data: List of lists containing table data
|
||||
col_widths: List of column widths (None for auto)
|
||||
style_name: 'default', 'striped', 'minimal', 'report'
|
||||
|
||||
Returns:
|
||||
Table object ready to add to story
|
||||
"""
|
||||
table = Table(data, colWidths=col_widths)
|
||||
|
||||
if style_name == 'striped':
|
||||
style = TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.darkblue),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 12),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
('ROWBACKGROUNDS', (0, 1), (-1, -1), [colors.white, colors.lightgrey]),
|
||||
('GRID', (0, 0), (-1, -1), 0.5, colors.grey),
|
||||
])
|
||||
|
||||
elif style_name == 'minimal':
|
||||
style = TableStyle([
|
||||
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('LINEABOVE', (0, 0), (-1, 0), 2, colors.black),
|
||||
('LINEBELOW', (0, 0), (-1, 0), 1, colors.black),
|
||||
('LINEBELOW', (0, -1), (-1, -1), 2, colors.black),
|
||||
])
|
||||
|
||||
elif style_name == 'report':
|
||||
style = TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
|
||||
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 11),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.beige),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.grey),
|
||||
('LEFTPADDING', (0, 0), (-1, -1), 12),
|
||||
('RIGHTPADDING', (0, 0), (-1, -1), 12),
|
||||
])
|
||||
|
||||
else: # default
|
||||
style = TableStyle([
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
|
||||
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 12),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.white),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.black),
|
||||
])
|
||||
|
||||
table.setStyle(style)
|
||||
return table
|
||||
|
||||
|
||||
def add_header_footer(canvas, doc, header_text="", footer_text=""):
|
||||
"""
|
||||
Callback function to add headers and footers to each page.
|
||||
|
||||
Usage:
|
||||
from functools import partial
|
||||
callback = partial(add_header_footer, header_text="My Document", footer_text="Confidential")
|
||||
template = PageTemplate(id='normal', frames=[frame], onPage=callback)
|
||||
"""
|
||||
canvas.saveState()
|
||||
|
||||
# Header
|
||||
if header_text:
|
||||
canvas.setFont('Helvetica', 9)
|
||||
canvas.drawString(inch, doc.pagesize[1] - 0.5*inch, header_text)
|
||||
|
||||
# Footer
|
||||
if footer_text:
|
||||
canvas.setFont('Helvetica', 9)
|
||||
canvas.drawString(inch, 0.5*inch, footer_text)
|
||||
|
||||
# Page number
|
||||
canvas.drawRightString(doc.pagesize[0] - inch, 0.5*inch, f"Page {doc.page}")
|
||||
|
||||
canvas.restoreState()
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
# Example 1: Simple document
|
||||
content = [
|
||||
{'type': 'heading', 'content': 'Introduction'},
|
||||
{'type': 'paragraph', 'content': 'This is a sample paragraph with some text.'},
|
||||
{'type': 'space', 'height': 0.2},
|
||||
{'type': 'heading', 'content': 'Main Content'},
|
||||
{'type': 'paragraph', 'content': 'More content here with <b>bold</b> and <i>italic</i> text.'},
|
||||
{'type': 'bullet', 'content': 'First bullet point'},
|
||||
{'type': 'bullet', 'content': 'Second bullet point'},
|
||||
]
|
||||
|
||||
create_simple_document(
|
||||
"example_document.pdf",
|
||||
"Sample Document",
|
||||
author="John Doe",
|
||||
content_blocks=content
|
||||
)
|
||||
|
||||
print("Created: example_document.pdf")
|
||||
|
||||
# Example 2: Document with styled table
|
||||
doc = SimpleDocTemplate("table_example.pdf", pagesize=letter)
|
||||
story = []
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
story.append(Paragraph("Sales Report", styles['Title']))
|
||||
story.append(Spacer(1, 0.3*inch))
|
||||
|
||||
# Create table
|
||||
data = [
|
||||
['Product', 'Q1', 'Q2', 'Q3', 'Q4'],
|
||||
['Widget A', '100', '150', '130', '180'],
|
||||
['Widget B', '80', '120', '110', '160'],
|
||||
['Widget C', '90', '110', '100', '140'],
|
||||
]
|
||||
|
||||
table = create_styled_table(data, col_widths=[2*inch, 1*inch, 1*inch, 1*inch, 1*inch], style_name='striped')
|
||||
story.append(table)
|
||||
|
||||
doc.build(story)
|
||||
print("Created: table_example.pdf")
|
||||
Reference in New Issue
Block a user