first commit
Lint / lint (push) Failing after 2m46s
Tests / test (3.11) (push) Has been cancelled
Tests / test (3.9) (push) Has been cancelled
Tests / test (3.10) (push) Has been cancelled

This commit is contained in:
2025-10-21 22:02:49 +02:00
commit 46585228fd
50 changed files with 12567 additions and 0 deletions
+90
View File
@@ -0,0 +1,90 @@
# pyPhotoAlbum Examples
This directory contains working examples demonstrating various features of pyPhotoAlbum.
## Available Examples
### basic_usage.py
Demonstrates the fundamentals of creating a photo album project:
- Creating a project
- Adding pages with images
- Working with text boxes
- Saving and loading projects
- Asset management
Run:
```bash
python basic_usage.py
```
### template_example.py
Shows how to work with the template system:
- Creating custom templates
- Applying templates to pages
- Using built-in templates
- Template scaling modes
Run:
```bash
python template_example.py
```
### generate_screenshots.py
Script to generate documentation screenshots programmatically:
- Creates example projects
- Captures screenshots for documentation
- Demonstrates various layouts
Run:
```bash
python generate_screenshots.py
```
This creates screenshots in `examples/screenshots/` directory.
## Requirements
Make sure you have pyPhotoAlbum installed:
```bash
# From the project root
pip install -e .
```
## Sample Images
The examples use placeholder images. To use your own:
1. Create an `images/` directory in the examples folder
2. Add your sample images
3. Update the image paths in the example scripts
## Output
Examples will create:
- `examples/output/` - Generated project files (.ppz)
- `examples/screenshots/` - Documentation screenshots
- `examples/pdfs/` - Exported PDFs
These directories are created automatically when you run the examples.
## Notes
- The examples use realistic page sizes (A4, square formats)
- DPI settings match typical print requirements (300 DPI)
- All examples include error handling and cleanup
- Examples demonstrate both programmatic and template-based workflows
## Regenerating Documentation Screenshots
To update screenshots for the documentation:
1. Run `python generate_screenshots.py`
2. Review generated images in `screenshots/`
3. Copy needed screenshots to documentation
The script generates:
- UI screenshots
- Layout examples
- Template demonstrations
- Feature showcases
+426
View File
@@ -0,0 +1,426 @@
#!/usr/bin/env python3
"""
Basic Usage Example for pyPhotoAlbum
This example demonstrates:
- Creating a new project
- Adding pages with images and text
- Working with the asset manager
- Saving and loading projects
- Basic element manipulation
Based on unit test examples from the pyPhotoAlbum test suite.
"""
import os
import sys
import tempfile
from pathlib import Path
# Add parent directory to path to import pyPhotoAlbum
sys.path.insert(0, str(Path(__file__).parent.parent))
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.models import ImageData, TextBoxData, PlaceholderData
from pyPhotoAlbum.project_serializer import save_to_zip, load_from_zip, get_project_info
from pyPhotoAlbum.pdf_exporter import PDFExporter
def create_sample_image(path: str, color: str = 'blue', size: tuple = (400, 300)):
"""Create a sample image for testing"""
try:
from PIL import Image, ImageDraw, ImageFont
img = Image.new('RGB', size, color=color)
draw = ImageDraw.Draw(img)
# Draw a border
border_width = 10
draw.rectangle(
[(border_width, border_width), (size[0]-border_width, size[1]-border_width)],
outline='white',
width=5
)
# Add some text
text = f"{color.upper()}\n{size[0]}x{size[1]}"
try:
# Try to use a nice font
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 36)
except:
# Fallback to default
font = ImageFont.load_default()
# Calculate text position (center)
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = (size[0] - text_width) // 2
y = (size[1] - text_height) // 2
draw.text((x, y), text, fill='white', font=font)
img.save(path)
print(f"Created sample image: {path}")
return True
except Exception as e:
print(f"Could not create sample image: {e}")
return False
def example_1_create_basic_project():
"""
Example 1: Create a basic project with one page and an image
This demonstrates the fundamental workflow based on test_project.py
"""
print("\n" + "="*60)
print("Example 1: Creating a Basic Project")
print("="*60)
# Create output directory
output_dir = Path(__file__).parent / "output"
output_dir.mkdir(exist_ok=True)
# Create a temporary directory for the project
temp_dir = tempfile.mkdtemp(prefix="photo_album_")
project_folder = os.path.join(temp_dir, "my_album")
print(f"\nProject folder: {project_folder}")
# Create a new project (from test_project.py)
project = Project(name="My First Album", folder_path=project_folder)
project.page_size_mm = (210, 297) # A4 size
project.working_dpi = 300
project.export_dpi = 300
print(f"Created project: {project.name}")
print(f"Page size: {project.page_size_mm[0]}mm x {project.page_size_mm[1]}mm")
print(f"DPI: {project.working_dpi}")
# Create a sample image
image_path = output_dir / "sample_photo.jpg"
create_sample_image(str(image_path), color='blue', size=(800, 600))
# Import the image into the project (from test_project_serialization.py)
imported_path = project.asset_manager.import_asset(str(image_path))
print(f"\nImported asset: {imported_path}")
# Create a page layout (from test_project.py)
layout = PageLayout(width=210, height=297)
# Add an image element (from test_models.py)
image = ImageData(
image_path=imported_path,
x=10.0,
y=10.0,
width=190.0,
height=140.0,
rotation=0,
z_index=0
)
layout.add_element(image)
print(f"Added image at position ({image.position[0]}, {image.position[1]})")
print(f"Image size: {image.size[0]}mm x {image.size[1]}mm")
# Add a text box (from test_models.py)
textbox = TextBoxData(
text_content="My First Photo Album",
font_settings={"family": "Arial", "size": 24, "color": (0, 0, 0)},
alignment="center",
x=10.0,
y=160.0,
width=190.0,
height=30.0
)
layout.add_element(textbox)
print(f"Added text box: '{textbox.text_content}'")
# Create a page and add it to the project
page = Page(layout=layout, page_number=1)
project.add_page(page)
print(f"\nAdded page {page.page_number} to project")
print(f"Total pages: {len(project.pages)}")
print(f"Total elements on page: {len(page.layout.elements)}")
# Save the project (from test_project_serialization.py)
output_path = output_dir / "basic_project.ppz"
print(f"\nSaving project to: {output_path}")
success, error = save_to_zip(project, str(output_path))
if success:
print("Project saved successfully!")
# Get project info without loading (from test_project_serialization.py)
info = get_project_info(str(output_path))
if info:
print(f"\nProject Info:")
print(f" Name: {info['name']}")
print(f" Pages: {info['page_count']}")
print(f" Version: {info['version']}")
print(f" Working DPI: {info['working_dpi']}")
else:
print(f"Error saving project: {error}")
return str(output_path)
def example_2_load_and_modify_project(project_path: str):
"""
Example 2: Load an existing project and add more pages
Based on test_project_serialization.py
"""
print("\n" + "="*60)
print("Example 2: Loading and Modifying a Project")
print("="*60)
# Load the project (from test_project_serialization.py)
print(f"\nLoading project from: {project_path}")
loaded_project, error = load_from_zip(project_path)
if not loaded_project:
print(f"Error loading project: {error}")
return
print(f"Loaded project: {loaded_project.name}")
print(f"Pages: {len(loaded_project.pages)}")
# Create output directory
output_dir = Path(__file__).parent / "output"
# Create more sample images
for i in range(2, 4):
image_path = output_dir / f"sample_photo_{i}.jpg"
colors = ['red', 'green']
create_sample_image(str(image_path), color=colors[i-2], size=(600, 800))
# Import the image
imported_path = loaded_project.asset_manager.import_asset(str(image_path))
# Create a new page with the image (from test_project.py)
layout = PageLayout(width=210, height=297)
image = ImageData(
image_path=imported_path,
x=20.0 + i*5,
y=20.0 + i*5,
width=170.0,
height=230.0
)
layout.add_element(image)
# Add caption
caption = TextBoxData(
text_content=f"Page {i}",
font_settings={"family": "Arial", "size": 18, "color": (0, 0, 0)},
alignment="center",
x=20.0 + i*5,
y=260.0,
width=170.0,
height=25.0
)
layout.add_element(caption)
page = Page(layout=layout, page_number=i)
loaded_project.add_page(page)
print(f"Added page {i} with {len(page.layout.elements)} elements")
# Save the modified project
modified_path = output_dir / "modified_project.ppz"
print(f"\nSaving modified project to: {modified_path}")
success, error = save_to_zip(loaded_project, str(modified_path))
if success:
print("Modified project saved successfully!")
print(f"Total pages: {len(loaded_project.pages)}")
else:
print(f"Error saving: {error}")
return str(modified_path)
def example_3_serialization_roundtrip():
"""
Example 3: Demonstrate serialization/deserialization
Based on test_models.py serialization tests
"""
print("\n" + "="*60)
print("Example 3: Serialization Round-Trip")
print("="*60)
# Create an image element (from test_models.py)
print("\n1. Creating ImageData element...")
original_image = ImageData(
image_path="test.jpg",
x=50.0,
y=60.0,
width=300.0,
height=200.0,
rotation=15.0,
z_index=2,
crop_info=(0.1, 0.1, 0.9, 0.9)
)
print(f" Position: {original_image.position}")
print(f" Size: {original_image.size}")
print(f" Rotation: {original_image.rotation}°")
print(f" Z-index: {original_image.z_index}")
# Serialize (from test_models.py)
print("\n2. Serializing to dictionary...")
data = original_image.serialize()
print(f" Serialized data keys: {list(data.keys())}")
print(f" Type: {data['type']}")
# Deserialize (from test_models.py)
print("\n3. Deserializing from dictionary...")
restored_image = ImageData()
restored_image.deserialize(data)
print(f" Position: {restored_image.position}")
print(f" Size: {restored_image.size}")
print(f" Rotation: {restored_image.rotation}°")
# Verify round-trip
print("\n4. Verifying round-trip...")
assert restored_image.position == original_image.position
assert restored_image.size == original_image.size
assert restored_image.rotation == original_image.rotation
assert restored_image.z_index == original_image.z_index
assert restored_image.crop_info == original_image.crop_info
print(" Round-trip successful!")
# Do the same for TextBoxData (from test_models.py)
print("\n5. Testing TextBoxData serialization...")
font_settings = {"family": "Georgia", "size": 20, "color": (255, 255, 0)}
original_text = TextBoxData(
text_content="Round Trip Test",
font_settings=font_settings,
alignment="center",
x=85.0,
y=95.0,
width=320.0,
height=120.0,
rotation=25.0,
z_index=9
)
data = original_text.serialize()
restored_text = TextBoxData()
restored_text.deserialize(data)
assert restored_text.text_content == original_text.text_content
assert restored_text.alignment == original_text.alignment
assert restored_text.position == original_text.position
print(" TextBoxData round-trip successful!")
def example_4_export_to_pdf():
"""
Example 4: Export a project to PDF
Based on pdf_exporter.py usage
"""
print("\n" + "="*60)
print("Example 4: Export to PDF")
print("="*60)
# Create a simple project
output_dir = Path(__file__).parent / "output"
output_dir.mkdir(exist_ok=True)
temp_dir = tempfile.mkdtemp(prefix="photo_album_pdf_")
project_folder = os.path.join(temp_dir, "pdf_export")
project = Project(name="PDF Export Example", folder_path=project_folder)
project.page_size_mm = (140, 140) # Square format
project.working_dpi = 300
project.export_dpi = 300
print(f"\nCreating project with {project.page_size_mm[0]}x{project.page_size_mm[1]}mm pages")
# Create multiple pages with different colored images
colors = ['red', 'green', 'blue', 'yellow']
for i, color in enumerate(colors, 1):
# Create sample image
image_path = output_dir / f"pdf_sample_{color}.jpg"
create_sample_image(str(image_path), color=color, size=(600, 600))
# Import and add to page
imported_path = project.asset_manager.import_asset(str(image_path))
layout = PageLayout(width=140, height=140)
image = ImageData(
image_path=imported_path,
x=10.0,
y=10.0,
width=120.0,
height=120.0
)
layout.add_element(image)
page = Page(layout=layout, page_number=i)
project.add_page(page)
print(f"Created {len(project.pages)} pages")
# Export to PDF
pdf_path = output_dir / "example_album.pdf"
print(f"\nExporting to PDF: {pdf_path}")
exporter = PDFExporter(project, export_dpi=300)
def progress_callback(current, total):
print(f" Exporting page {current}/{total}...")
success, errors = exporter.export(
output_path=str(pdf_path),
progress_callback=progress_callback
)
if success:
print(f"\nPDF exported successfully!")
print(f"File size: {os.path.getsize(pdf_path) / 1024:.1f} KB")
else:
print(f"\nErrors during export:")
for error in errors:
print(f" - {error}")
def main():
"""Run all examples"""
print("\n" + "="*60)
print("pyPhotoAlbum - Basic Usage Examples")
print("="*60)
print("\nThese examples demonstrate core functionality using")
print("code patterns from the pyPhotoAlbum unit tests.\n")
try:
# Example 1: Create a basic project
project_path = example_1_create_basic_project()
# Example 2: Load and modify
if project_path and os.path.exists(project_path):
example_2_load_and_modify_project(project_path)
# Example 3: Serialization
example_3_serialization_roundtrip()
# Example 4: PDF export
example_4_export_to_pdf()
print("\n" + "="*60)
print("All examples completed successfully!")
print("="*60)
print(f"\nOutput files are in: {Path(__file__).parent / 'output'}")
except Exception as e:
print(f"\nError running examples: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()
+427
View File
@@ -0,0 +1,427 @@
#!/usr/bin/env python3
"""
Generate Documentation Screenshots
This script creates visual examples for documentation purposes:
- Sample layouts
- Template demonstrations
- Feature showcases
- UI mockups
Note: This creates programmatic representations rather than actual screenshots.
For real screenshots, run the application and use a screen capture tool.
"""
import os
import sys
from pathlib import Path
# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.models import ImageData, TextBoxData, PlaceholderData
from pyPhotoAlbum.project_serializer import save_to_zip
import tempfile
def create_visual_mockup(path: str, title: str, description: str, elements: list):
"""
Create a visual mockup image showing a layout
Args:
path: Output path for the image
title: Title for the mockup
description: Description text
elements: List of (type, x, y, w, h, label) tuples
"""
try:
from PIL import Image, ImageDraw, ImageFont
# Create canvas (A4 aspect ratio)
width, height = 800, 1132
img = Image.new('RGB', (width, height), color='white')
draw = ImageDraw.Draw(img)
# Try to load a nice font
try:
title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 32)
desc_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 18)
label_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 14)
except:
title_font = ImageFont.load_default()
desc_font = ImageFont.load_default()
label_font = ImageFont.load_default()
# Draw title
draw.text((20, 20), title, fill='black', font=title_font)
# Draw description
draw.text((20, 65), description, fill='gray', font=desc_font)
# Draw page boundary
page_margin = 50
page_top = 120
page_bottom = height - 50
page_width = width - 2 * page_margin
page_height = page_bottom - page_top
# Light gray page background
draw.rectangle(
[(page_margin, page_top), (width - page_margin, page_bottom)],
fill='#f5f5f5',
outline='#cccccc',
width=2
)
# Draw elements
colors = {
'image': '#e3f2fd', # Light blue
'text': '#fff9c4', # Light yellow
'placeholder': '#e8f5e9' # Light green
}
for elem_type, x, y, w, h, label in elements:
# Convert from mm to pixels (rough approximation)
px = page_margin + (x / 210) * page_width
py = page_top + (y / 297) * page_height
pw = (w / 210) * page_width
ph = (h / 297) * page_height
# Draw element
color = colors.get(elem_type, '#eeeeee')
draw.rectangle(
[(px, py), (px + pw, py + ph)],
fill=color,
outline='#666666',
width=2
)
# Draw label
if label:
label_bbox = draw.textbbox((0, 0), label, font=label_font)
label_w = label_bbox[2] - label_bbox[0]
label_h = label_bbox[3] - label_bbox[1]
label_x = px + (pw - label_w) / 2
label_y = py + (ph - label_h) / 2
draw.text((label_x, label_y), label, fill='#333333', font=label_font)
# Save
img.save(path)
print(f" Created: {path}")
return True
except Exception as e:
print(f" Error creating mockup: {e}")
return False
def generate_layout_examples():
"""Generate example layout screenshots"""
print("\n" + "="*60)
print("Generating Layout Examples")
print("="*60)
output_dir = Path(__file__).parent / "screenshots"
output_dir.mkdir(exist_ok=True)
# Example 1: Single large image
print("\n1. Single Large Image Layout")
create_visual_mockup(
str(output_dir / "layout_single_large.png"),
"Single Large Image Layout",
"One large image with caption",
[
('image', 10, 10, 190, 250, 'Large Image'),
('text', 10, 270, 190, 20, 'Caption Text')
]
)
# Example 2: Grid 2x2
print("\n2. Grid 2x2 Layout")
create_visual_mockup(
str(output_dir / "layout_grid_2x2.png"),
"Grid 2x2 Layout",
"Four images in a 2x2 grid",
[
('image', 10, 10, 95, 95, 'Image 1'),
('image', 105, 10, 95, 95, 'Image 2'),
('image', 10, 110, 95, 95, 'Image 3'),
('image', 105, 110, 95, 95, 'Image 4')
]
)
# Example 3: Mixed layout
print("\n3. Mixed Layout")
create_visual_mockup(
str(output_dir / "layout_mixed.png"),
"Mixed Layout",
"Combination of images and text boxes",
[
('image', 10, 10, 190, 120, 'Header Image'),
('text', 10, 140, 190, 25, 'Title'),
('image', 10, 175, 90, 90, 'Image 1'),
('image', 110, 175, 90, 90, 'Image 2'),
('text', 10, 270, 190, 15, 'Footer')
]
)
# Example 4: Template with placeholders
print("\n4. Template with Placeholders")
create_visual_mockup(
str(output_dir / "layout_template.png"),
"Template Layout",
"Page template with placeholder blocks",
[
('placeholder', 10, 10, 190, 140, 'Drop Image Here'),
('placeholder', 10, 160, 90, 90, 'Image'),
('placeholder', 110, 160, 90, 90, 'Image'),
('text', 10, 260, 190, 25, 'Text Box')
]
)
def generate_feature_examples():
"""Generate feature demonstration screenshots"""
print("\n" + "="*60)
print("Generating Feature Examples")
print("="*60)
output_dir = Path(__file__).parent / "screenshots"
output_dir.mkdir(exist_ok=True)
# Alignment demonstration
print("\n1. Alignment Features")
create_visual_mockup(
str(output_dir / "feature_alignment.png"),
"Alignment Tools",
"Align left, center, right, and distribute",
[
('image', 10, 10, 50, 50, 'Img 1'),
('image', 70, 10, 50, 50, 'Img 2'),
('image', 130, 10, 50, 50, 'Img 3'),
('text', 10, 80, 170, 15, 'Aligned and distributed horizontally')
]
)
# Sizing demonstration
print("\n2. Sizing Features")
create_visual_mockup(
str(output_dir / "feature_sizing.png"),
"Sizing Tools",
"Make same size, width, or height",
[
('image', 10, 10, 80, 80, 'Same Size'),
('image', 100, 10, 80, 80, 'Same Size'),
('image', 10, 100, 80, 60, 'Same Width'),
('image', 100, 100, 80, 80, 'Same Width')
]
)
# Z-order demonstration
print("\n3. Z-Order (Layering)")
create_visual_mockup(
str(output_dir / "feature_zorder.png"),
"Z-Order / Layering",
"Elements can be ordered in layers",
[
('image', 30, 30, 100, 100, 'Background'),
('image', 60, 60, 100, 100, 'Middle'),
('image', 90, 90, 100, 100, 'Foreground')
]
)
def generate_workflow_example():
"""Generate a complete workflow example"""
print("\n" + "="*60)
print("Generating Workflow Example")
print("="*60)
output_dir = Path(__file__).parent / "screenshots"
output_dir.mkdir(exist_ok=True)
# Step 1: Empty page
print("\n1. Start with empty page")
create_visual_mockup(
str(output_dir / "workflow_01_empty.png"),
"Step 1: Empty Page",
"Start with a blank page",
[]
)
# Step 2: Apply template
print("\n2. Apply template")
create_visual_mockup(
str(output_dir / "workflow_02_template.png"),
"Step 2: Apply Template",
"Add placeholder blocks using a template",
[
('placeholder', 10, 10, 90, 90, 'Drop Here'),
('placeholder', 110, 10, 90, 90, 'Drop Here'),
('placeholder', 10, 110, 90, 90, 'Drop Here'),
('placeholder', 110, 110, 90, 90, 'Drop Here')
]
)
# Step 3: Add images
print("\n3. Add images")
create_visual_mockup(
str(output_dir / "workflow_03_images.png"),
"Step 3: Add Images",
"Drag and drop images onto placeholders",
[
('image', 10, 10, 90, 90, 'Photo 1'),
('image', 110, 10, 90, 90, 'Photo 2'),
('image', 10, 110, 90, 90, 'Photo 3'),
('placeholder', 110, 110, 90, 90, 'Drop Here')
]
)
# Step 4: Final layout
print("\n4. Final layout")
create_visual_mockup(
str(output_dir / "workflow_04_final.png"),
"Step 4: Final Layout",
"Complete page with all images and text",
[
('image', 10, 10, 90, 90, 'Photo 1'),
('image', 110, 10, 90, 90, 'Photo 2'),
('image', 10, 110, 90, 90, 'Photo 3'),
('image', 110, 110, 90, 90, 'Photo 4'),
('text', 10, 210, 190, 20, 'My Photo Album - Summer 2024')
]
)
def generate_template_examples():
"""Generate template system examples"""
print("\n" + "="*60)
print("Generating Template Examples")
print("="*60)
output_dir = Path(__file__).parent / "screenshots"
output_dir.mkdir(exist_ok=True)
# Grid template
print("\n1. Grid_2x2 Template")
create_visual_mockup(
str(output_dir / "template_grid_2x2.png"),
"Grid_2x2 Template",
"Built-in 2x2 grid template",
[
('placeholder', 10, 10, 95, 133, ''),
('placeholder', 105, 10, 95, 133, ''),
('placeholder', 10, 143, 95, 134, ''),
('placeholder', 105, 143, 95, 134, '')
]
)
# Single large template
print("\n2. Single_Large Template")
create_visual_mockup(
str(output_dir / "template_single_large.png"),
"Single_Large Template",
"Built-in single large image template",
[
('placeholder', 10, 10, 190, 240, 'Large Image'),
('text', 10, 260, 190, 25, 'Title')
]
)
def generate_project_structure_diagram():
"""Generate project structure visualization"""
print("\n" + "="*60)
print("Generating Project Structure Diagram")
print("="*60)
output_dir = Path(__file__).parent / "screenshots"
output_dir.mkdir(exist_ok=True)
try:
from PIL import Image, ImageDraw, ImageFont
width, height = 800, 600
img = Image.new('RGB', (width, height), color='white')
draw = ImageDraw.Draw(img)
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 16)
title_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 20)
except:
font = ImageFont.load_default()
title_font = ImageFont.load_default()
# Title
draw.text((20, 20), "pyPhotoAlbum Project Structure", fill='black', font=title_font)
# Draw structure
structure = [
(50, "Project", 60),
(100, "├─ Page 1", 100),
(150, "│ ├─ PageLayout", 140),
(200, "│ │ ├─ ImageData", 180),
(250, "│ │ ├─ TextBoxData", 220),
(300, "│ │ └─ PlaceholderData", 260),
(100, "├─ Page 2", 320),
(100, "└─ AssetManager", 360),
(150, " └─ assets/", 400),
]
for x, text, y in structure:
draw.text((x, y), text, fill='#333333', font=font)
# Draw connecting lines (simplified)
draw.line([(60, 90), (60, 380)], fill='#666666', width=2)
draw.line([(110, 130), (110, 280)], fill='#666666', width=2)
path = output_dir / "project_structure.png"
img.save(str(path))
print(f" Created: {path}")
except Exception as e:
print(f" Error creating diagram: {e}")
def main():
"""Generate all documentation screenshots"""
print("\n" + "="*60)
print("pyPhotoAlbum - Generate Documentation Screenshots")
print("="*60)
print("\nThis script creates visual examples for documentation.")
print("For actual application screenshots, run the app and")
print("use a screen capture tool.\n")
try:
# Create output directory
output_dir = Path(__file__).parent / "screenshots"
output_dir.mkdir(exist_ok=True)
print(f"Output directory: {output_dir}")
# Generate examples
generate_layout_examples()
generate_feature_examples()
generate_workflow_example()
generate_template_examples()
generate_project_structure_diagram()
print("\n" + "="*60)
print("Screenshot generation complete!")
print("="*60)
print(f"\nGenerated files are in: {output_dir}")
print("\nThese mockups can be used in documentation to illustrate:")
print(" - Page layouts")
print(" - Template system")
print(" - Feature demonstrations")
print(" - Workflow examples")
except Exception as e:
print(f"\nError generating screenshots: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()
+437
View File
@@ -0,0 +1,437 @@
#!/usr/bin/env python3
"""
Template System Example for pyPhotoAlbum
This example demonstrates:
- Creating templates from existing pages
- Using built-in templates
- Applying templates to new pages
- Template scaling modes
- Saving and loading custom templates
Based on template_manager.py and TEMPLATES_README.md
"""
import os
import sys
import tempfile
from pathlib import Path
# Add parent directory to path to import pyPhotoAlbum
sys.path.insert(0, str(Path(__file__).parent.parent))
from pyPhotoAlbum.project import Project, Page
from pyPhotoAlbum.page_layout import PageLayout
from pyPhotoAlbum.models import ImageData, TextBoxData, PlaceholderData
from pyPhotoAlbum.template_manager import TemplateManager, Template
from pyPhotoAlbum.project_serializer import save_to_zip
def create_sample_image(path: str, color: str = 'blue', size: tuple = (400, 300)):
"""Create a sample image for testing"""
try:
from PIL import Image, ImageDraw, ImageFont
img = Image.new('RGB', size, color=color)
draw = ImageDraw.Draw(img)
# Draw a border
border_width = 10
draw.rectangle(
[(border_width, border_width), (size[0]-border_width, size[1]-border_width)],
outline='white',
width=5
)
# Add text
text = f"{color.upper()}"
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 48)
except:
font = ImageFont.load_default()
bbox = draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
x = (size[0] - text_width) // 2
y = (size[1] - text_height) // 2
draw.text((x, y), text, fill='white', font=font)
img.save(path)
print(f" Created: {path}")
return True
except Exception as e:
print(f" Could not create image: {e}")
return False
def example_1_list_templates():
"""
Example 1: List available templates
From template_manager.py
"""
print("\n" + "="*60)
print("Example 1: Listing Available Templates")
print("="*60)
manager = TemplateManager()
print("\nAvailable templates:")
templates = manager.list_templates()
for i, template_name in enumerate(templates, 1):
print(f" {i}. {template_name}")
# Load and display info about each template
print("\nTemplate Details:")
for template_name in templates[:2]: # Show first 2 templates
try:
template = manager.load_template(template_name)
print(f"\n {template.name}:")
print(f" Description: {template.description}")
print(f" Page Size: {template.page_size_mm[0]}x{template.page_size_mm[1]} mm")
print(f" Elements: {len(template.elements)}")
except Exception as e:
print(f" Error loading: {e}")
def example_2_create_page_from_template():
"""
Example 2: Create a new page from a built-in template
From template_manager.py - create_page_from_template method
"""
print("\n" + "="*60)
print("Example 2: Creating Page from Template")
print("="*60)
manager = TemplateManager()
output_dir = Path(__file__).parent / "output"
output_dir.mkdir(exist_ok=True)
# Create a project
temp_dir = tempfile.mkdtemp(prefix="template_example_")
project_folder = os.path.join(temp_dir, "template_project")
project = Project(name="Template Example", folder_path=project_folder)
project.page_size_mm = (210, 297) # A4
project.working_dpi = 300
print(f"\nCreated project: {project.name}")
print(f"Page size: {project.page_size_mm[0]}x{project.page_size_mm[1]} mm")
# Create a page from Grid_2x2 template
print("\nCreating page from 'Grid_2x2' template...")
page = manager.create_page_from_template(
template_name="Grid_2x2",
target_page_size=project.page_size_mm,
page_number=1
)
print(f" Created page with {len(page.layout.elements)} placeholder elements")
# Show details of placeholders
for i, element in enumerate(page.layout.elements, 1):
if isinstance(element, PlaceholderData):
print(f" Placeholder {i}: position={element.position}, size={element.size}")
project.add_page(page)
# Now replace placeholders with actual images
print("\nReplacing placeholders with images...")
colors = ['red', 'green', 'blue', 'yellow']
for i, element in enumerate(page.layout.elements):
if isinstance(element, PlaceholderData) and i < len(colors):
# Create sample image
image_path = output_dir / f"grid_image_{colors[i]}.jpg"
create_sample_image(str(image_path), color=colors[i], size=(600, 600))
# Import to project
imported_path = project.asset_manager.import_asset(str(image_path))
# Replace placeholder with image
image = ImageData(
image_path=imported_path,
x=element.position[0],
y=element.position[1],
width=element.size[0],
height=element.size[1],
z_index=element.z_index
)
# Remove placeholder and add image
page.layout.remove_element(element)
page.layout.add_element(image)
print(f" Replaced placeholder {i+1} with {colors[i]} image")
# Save project
project_path = output_dir / "template_project.ppz"
print(f"\nSaving project to: {project_path}")
success, error = save_to_zip(project, str(project_path))
if success:
print(" Project saved successfully!")
else:
print(f" Error: {error}")
def example_3_create_custom_template():
"""
Example 3: Create a custom template from a page
From template_manager.py - create_template_from_page method
"""
print("\n" + "="*60)
print("Example 3: Creating Custom Template")
print("="*60)
manager = TemplateManager()
output_dir = Path(__file__).parent / "output"
output_dir.mkdir(exist_ok=True)
# Create a page with a custom layout
print("\nDesigning custom page layout...")
layout = PageLayout(width=210, height=297)
# Large image at top
top_image = ImageData(
image_path="dummy.jpg", # Will be converted to placeholder
x=10.0,
y=10.0,
width=190.0,
height=140.0,
z_index=0
)
layout.add_element(top_image)
print(" Added large top image")
# Two smaller images at bottom
bottom_left = ImageData(
image_path="dummy.jpg",
x=10.0,
y=160.0,
width=90.0,
height=90.0,
z_index=0
)
layout.add_element(bottom_left)
bottom_right = ImageData(
image_path="dummy.jpg",
x=110.0,
y=160.0,
width=90.0,
height=90.0,
z_index=0
)
layout.add_element(bottom_right)
print(" Added two smaller bottom images")
# Title text box
title = TextBoxData(
text_content="Title",
font_settings={"family": "Arial", "size": 20, "color": (0, 0, 0)},
alignment="center",
x=10.0,
y=260.0,
width=190.0,
height=30.0,
z_index=1
)
layout.add_element(title)
print(" Added title text box")
# Create page
page = Page(layout=layout, page_number=1)
# Create template from page
print("\nCreating template from page...")
template = manager.create_template_from_page(
page=page,
name="Custom_Large_Plus_Two",
description="One large image at top, two smaller at bottom, with title"
)
print(f" Template created: {template.name}")
print(f" Description: {template.description}")
print(f" Elements: {len(template.elements)}")
# Save template
print("\nSaving custom template...")
try:
manager.save_template(template)
print(f" Template saved successfully!")
print(f" Location: {manager._get_templates_directory()}")
except Exception as e:
print(f" Error saving template: {e}")
# Verify it's in the list
templates = manager.list_templates()
if template.name in templates:
print(f" Verified: Template appears in list")
def example_4_template_scaling_modes():
"""
Example 4: Demonstrate different template scaling modes
From template_manager.py - scale_template_elements method
"""
print("\n" + "="*60)
print("Example 4: Template Scaling Modes")
print("="*60)
manager = TemplateManager()
# Create a simple template
print("\nCreating test template (100x100mm square)...")
template = Template(name="Test Square", page_size_mm=(100, 100))
# Add a centered square element
element = PlaceholderData(
x=25.0, y=25.0,
width=50.0, height=50.0
)
template.add_element(element)
print(f" Original element: position={element.position}, size={element.size}")
# Test different target sizes
target_sizes = [
(200, 200, "Same aspect ratio (2x)"),
(200, 100, "Wider (2x wide, 1x tall)"),
(100, 200, "Taller (1x wide, 2x tall)")
]
scaling_modes = ["proportional", "stretch", "center"]
for target_w, target_h, desc in target_sizes:
print(f"\nTarget size: {target_w}x{target_h}mm ({desc})")
for mode in scaling_modes:
scaled = manager.scale_template_elements(
template.elements.copy(),
source_size=template.page_size_mm,
target_size=(target_w, target_h),
scaling=mode
)
if scaled:
scaled_element = scaled[0]
print(f" {mode:12s}: position={scaled_element.position}, size={scaled_element.size}")
def example_5_apply_template_modes():
"""
Example 5: Apply template to existing page with different modes
From template_manager.py - apply_template_to_page method
"""
print("\n" + "="*60)
print("Example 5: Applying Template to Existing Page")
print("="*60)
manager = TemplateManager()
output_dir = Path(__file__).parent / "output"
output_dir.mkdir(exist_ok=True)
# Create a page with some existing content
print("\nCreating page with existing images...")
temp_dir = tempfile.mkdtemp(prefix="template_apply_")
project_folder = os.path.join(temp_dir, "apply_project")
project = Project(name="Apply Template Test", folder_path=project_folder)
project.page_size_mm = (210, 297)
layout = PageLayout(width=210, height=297)
# Add some images
colors = ['red', 'green', 'blue']
for i, color in enumerate(colors):
image_path = output_dir / f"apply_{color}.jpg"
create_sample_image(str(image_path), color=color, size=(600, 400))
imported_path = project.asset_manager.import_asset(str(image_path))
image = ImageData(
image_path=imported_path,
x=10.0 + i*20,
y=10.0 + i*30,
width=100.0,
height=100.0
)
layout.add_element(image)
page = Page(layout=layout, page_number=1)
project.add_page(page)
print(f" Created page with {len(page.layout.elements)} images")
# Apply Grid_2x2 template with "reflow" mode
print("\nApplying Grid_2x2 template with 'reflow' mode...")
print(" This will reposition existing images to fit template slots")
try:
template = manager.load_template("Grid_2x2")
manager.apply_template_to_page(
template=template,
target_page=page,
mode="reflow", # Keep existing images, reposition them
scaling="proportional"
)
print(f" Template applied! Page now has {len(page.layout.elements)} elements")
# Show new positions
for i, element in enumerate(page.layout.elements, 1):
print(f" Element {i}: position={element.position}, size={element.size}")
except Exception as e:
print(f" Error applying template: {e}")
# Save result
project_path = output_dir / "template_applied.ppz"
print(f"\nSaving result to: {project_path}")
success, error = save_to_zip(project, str(project_path))
if success:
print(" Saved successfully!")
def main():
"""Run all template examples"""
print("\n" + "="*60)
print("pyPhotoAlbum - Template System Examples")
print("="*60)
print("\nDemonstrating the template system using code from")
print("template_manager.py and TEMPLATES_README.md\n")
try:
# Example 1: List templates
example_1_list_templates()
# Example 2: Create page from template
example_2_create_page_from_template()
# Example 3: Create custom template
example_3_create_custom_template()
# Example 4: Scaling modes
example_4_template_scaling_modes()
# Example 5: Apply template
example_5_apply_template_modes()
print("\n" + "="*60)
print("All template examples completed!")
print("="*60)
print(f"\nOutput files are in: {Path(__file__).parent / 'output'}")
except Exception as e:
print(f"\nError running examples: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()