This commit is contained in:
parent
b18a780a33
commit
2a087f0c9d
656
README.md
656
README.md
@ -1,14 +1,10 @@
|
|||||||
# pyPhotoAlbum
|
# pyPhotoAlbum
|
||||||
|
|
||||||
| Badge | Description |
|

|
||||||
|-------|-------------|
|

|
||||||
|  | **Test Coverage** - Percentage of code covered by unit tests |
|

|
||||||
|  | **Documentation Coverage** - Percentage of code with docstrings |
|
|
||||||
|  | **License** - Project licensing information |
|
|
||||||
|
|
||||||
> 📋 **Note**: Badges show results from the commit referenced in the URLs. Red "error" badges indicate build failures for that specific step.
|
A desktop application for designing and creating professional photo albums with an intuitive drag-and-drop interface and high-quality PDF export.
|
||||||
|
|
||||||
A Python-based desktop application for designing photo albums with an intuitive interface and professional PDF export capabilities.
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@ -39,19 +35,8 @@ cd pyPhotoAlbum
|
|||||||
./install.sh
|
./install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
**For GNOME users:** See [GNOME_QUICKSTART.md](GNOME_QUICKSTART.md) for desktop integration.
|
|
||||||
|
|
||||||
**For detailed instructions:** See [INSTALLATION.md](INSTALLATION.md)
|
**For detailed instructions:** See [INSTALLATION.md](INSTALLATION.md)
|
||||||
|
|
||||||
### Requirements
|
|
||||||
|
|
||||||
- Python 3.9 or higher
|
|
||||||
- PyQt6
|
|
||||||
- PyOpenGL
|
|
||||||
- Pillow
|
|
||||||
- ReportLab
|
|
||||||
- lxml
|
|
||||||
|
|
||||||
### Distribution Packages
|
### Distribution Packages
|
||||||
|
|
||||||
**Fedora (RPM):**
|
**Fedora (RPM):**
|
||||||
@ -67,25 +52,12 @@ makepkg -si
|
|||||||
|
|
||||||
See [INSTALLATION.md](INSTALLATION.md) for complete instructions.
|
See [INSTALLATION.md](INSTALLATION.md) for complete instructions.
|
||||||
|
|
||||||
### Install for Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone repository
|
|
||||||
git clone https://gitea.tourolle.paris/dtourolle/pyPhotoAlbum.git
|
|
||||||
cd pyPhotoAlbum
|
|
||||||
|
|
||||||
# Create virtual environment
|
|
||||||
python -m venv venv
|
|
||||||
source venv/bin/activate
|
|
||||||
|
|
||||||
# Install with development dependencies
|
|
||||||
pip install -e ".[dev]"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Running the Application
|
### Running the Application
|
||||||
|
|
||||||
|
After installation, launch pyPhotoAlbum:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pyphotoalbum
|
pyphotoalbum
|
||||||
```
|
```
|
||||||
@ -96,600 +68,44 @@ Or run directly from source:
|
|||||||
python pyPhotoAlbum/main.py
|
python pyPhotoAlbum/main.py
|
||||||
```
|
```
|
||||||
|
|
||||||
### Basic Usage Example
|
### Basic Workflow
|
||||||
|
|
||||||
```python
|
1. **Create a New Project** - Choose your page size (A4, Letter, etc.) and DPI
|
||||||
from pyPhotoAlbum.project import Project, Page
|
2. **Add Pages** - Start with blank pages or use templates
|
||||||
from pyPhotoAlbum.page_layout import PageLayout
|
3. **Add Images** - Drag and drop images from your file browser onto pages
|
||||||
from pyPhotoAlbum.models import ImageData
|
4. **Arrange & Edit** - Move, resize, rotate, and crop images to your liking
|
||||||
|
5. **Save Your Work** - Projects are saved as .ppz files (ZIP archives)
|
||||||
|
6. **Export to PDF** - Generate high-quality PDFs ready for printing
|
||||||
|
|
||||||
# Create a new project
|
## Using Templates
|
||||||
project = Project(name="My Photo Album")
|
|
||||||
project.page_size_mm = (210, 297) # A4 size
|
|
||||||
project.working_dpi = 300
|
|
||||||
|
|
||||||
# Create a page with an image
|
pyPhotoAlbum includes a template system to help you quickly create consistent layouts:
|
||||||
layout = PageLayout(width=210, height=297)
|
|
||||||
image = ImageData(
|
|
||||||
image_path="photos/vacation.jpg",
|
|
||||||
x=10.0,
|
|
||||||
y=10.0,
|
|
||||||
width=190.0,
|
|
||||||
height=140.0
|
|
||||||
)
|
|
||||||
layout.add_element(image)
|
|
||||||
|
|
||||||
# Add page to project
|
- **Built-in Templates**: Grid layouts, single large image, and more
|
||||||
page = Page(layout=layout, page_number=1)
|
- **Custom Templates**: Save your favorite layouts as templates
|
||||||
project.add_page(page)
|
- **Flexible Application**: Apply templates to new or existing pages
|
||||||
|
|
||||||
# Save project
|
## Architecture Highlights
|
||||||
from pyPhotoAlbum.project_serializer import save_to_zip
|
|
||||||
success, error = save_to_zip(project, "my_album.ppz")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Architecture
|
pyPhotoAlbum is built with clean, maintainable design patterns:
|
||||||
|
|
||||||
### GLWidget Mixin Architecture
|
### Mixin-Based Composition
|
||||||
|
|
||||||
The main OpenGL widget uses a **mixin-based architecture** for maintainability and testability. The monolithic 1,368-line `gl_widget.py` has been refactored into 9 focused mixins averaging 89 lines each:
|
The main OpenGL widget is composed of **12 specialized mixins** instead of one monolithic class:
|
||||||
|
- Each mixin handles a single responsibility (viewport, rendering, selection, etc.)
|
||||||
|
- Average ~90 lines per mixin for maintainability
|
||||||
|
- Easy to test in isolation with comprehensive unit tests
|
||||||
|
- Clean separation of concerns throughout the codebase
|
||||||
|
|
||||||
```python
|
### Declarative UI with Decorators
|
||||||
class GLWidget(
|
|
||||||
ViewportMixin, # Zoom & pan state
|
|
||||||
RenderingMixin, # OpenGL rendering
|
|
||||||
AssetDropMixin, # Drag-and-drop
|
|
||||||
PageNavigationMixin, # Page detection
|
|
||||||
ImagePanMixin, # Image cropping
|
|
||||||
ElementManipulationMixin, # Resize & rotate
|
|
||||||
ElementSelectionMixin, # Hit detection
|
|
||||||
MouseInteractionMixin, # Event routing
|
|
||||||
UndoableInteractionMixin, # Undo/redo
|
|
||||||
QOpenGLWidget # Qt base class
|
|
||||||
):
|
|
||||||
"""Clean orchestration with minimal boilerplate"""
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits:**
|
The ribbon interface is **auto-generated from decorator metadata**:
|
||||||
- Each mixin has a single, clear responsibility
|
- `@ribbon_action` - Automatically creates ribbon buttons from method metadata
|
||||||
- 89 comprehensive unit tests with 69-97% coverage per mixin
|
- `@undoable_operation` - Automatically captures state for undo/redo
|
||||||
- Easy to test in isolation with mock dependencies
|
- `@dialog_action` - Separates dialog presentation from business logic
|
||||||
- Clear separation of concerns
|
- No manual UI wiring required - just add decorators to your methods
|
||||||
- Maintainable codebase (average 89 lines per mixin)
|
|
||||||
|
|
||||||
See [REFACTORING_COMPLETE.md](REFACTORING_COMPLETE.md) for details on the refactoring process.
|
This approach keeps UI concerns separate from business logic and makes the codebase easier to maintain and extend.
|
||||||
|
|
||||||
### Core Components
|
|
||||||
|
|
||||||
#### Models (`models.py`)
|
|
||||||
|
|
||||||
Base classes for layout elements:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Image element with crop support
|
|
||||||
image = ImageData(
|
|
||||||
image_path="photo.jpg",
|
|
||||||
x=10, y=20,
|
|
||||||
width=200, height=150,
|
|
||||||
rotation=0,
|
|
||||||
z_index=0,
|
|
||||||
crop_info=(0, 0, 1, 1) # (x_min, y_min, x_max, y_max)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Text box element
|
|
||||||
textbox = TextBoxData(
|
|
||||||
text_content="My Caption",
|
|
||||||
font_settings={"family": "Arial", "size": 14, "color": (0, 0, 0)},
|
|
||||||
alignment="center",
|
|
||||||
x=10, y=180,
|
|
||||||
width=200, height=30
|
|
||||||
)
|
|
||||||
|
|
||||||
# Placeholder for templates
|
|
||||||
placeholder = PlaceholderData(
|
|
||||||
placeholder_type="image",
|
|
||||||
x=10, y=10,
|
|
||||||
width=100, height=100
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Project Structure (`project.py`)
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Project contains multiple pages
|
|
||||||
project = Project(name="Album", folder_path="/path/to/project")
|
|
||||||
|
|
||||||
# Each page has a layout with elements
|
|
||||||
page = Page(layout=PageLayout(), page_number=1)
|
|
||||||
page.layout.add_element(image)
|
|
||||||
|
|
||||||
project.add_page(page)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Asset Management (`asset_manager.py`)
|
|
||||||
|
|
||||||
Automatic asset handling with reference counting:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Import an image into the project
|
|
||||||
asset_path = project.asset_manager.import_asset("photo.jpg")
|
|
||||||
# Returns: "assets/photo_001.jpg" (relative path)
|
|
||||||
|
|
||||||
# Assets are automatically copied to project folder
|
|
||||||
# Reference counting tracks usage across pages
|
|
||||||
# Cleanup happens automatically when elements are deleted
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Command System (`commands.py`)
|
|
||||||
|
|
||||||
Undo/redo support for all operations:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Commands are automatically created for operations
|
|
||||||
from pyPhotoAlbum.commands import AddElementCommand, MoveElementCommand
|
|
||||||
|
|
||||||
# Add element (undoable)
|
|
||||||
cmd = AddElementCommand(page.layout, image, project.asset_manager)
|
|
||||||
project.history.execute(cmd)
|
|
||||||
|
|
||||||
# Move element (undoable)
|
|
||||||
cmd = MoveElementCommand(image, old_pos=(10, 10), new_pos=(20, 20))
|
|
||||||
project.history.execute(cmd)
|
|
||||||
|
|
||||||
# Undo/redo
|
|
||||||
project.history.undo()
|
|
||||||
project.history.redo()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Layout System
|
|
||||||
|
|
||||||
#### PageLayout (`page_layout.py`)
|
|
||||||
|
|
||||||
Manages elements on a page:
|
|
||||||
|
|
||||||
```python
|
|
||||||
layout = PageLayout(width=210, height=297) # A4 in mm
|
|
||||||
|
|
||||||
# Add multiple elements
|
|
||||||
layout.add_element(image1)
|
|
||||||
layout.add_element(image2)
|
|
||||||
layout.add_element(textbox)
|
|
||||||
|
|
||||||
# Elements are rendered in z_index order
|
|
||||||
# Serialize/deserialize for saving
|
|
||||||
data = layout.serialize()
|
|
||||||
layout2 = PageLayout()
|
|
||||||
layout2.deserialize(data)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Alignment Tools (`alignment.py`)
|
|
||||||
|
|
||||||
Precise element positioning:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from pyPhotoAlbum.alignment import AlignmentManager
|
|
||||||
|
|
||||||
# Align multiple elements to the left
|
|
||||||
changes = AlignmentManager.align_left(selected_elements)
|
|
||||||
for element, new_position in changes:
|
|
||||||
element.position = new_position
|
|
||||||
|
|
||||||
# Distribute elements evenly
|
|
||||||
changes = AlignmentManager.distribute_horizontally(selected_elements)
|
|
||||||
|
|
||||||
# Make elements the same size
|
|
||||||
changes = AlignmentManager.make_same_size(selected_elements)
|
|
||||||
for element, new_position, new_size in changes:
|
|
||||||
element.position = new_position
|
|
||||||
element.size = new_size
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template System
|
|
||||||
|
|
||||||
#### Creating Templates
|
|
||||||
|
|
||||||
```python
|
|
||||||
from pyPhotoAlbum.template_manager import TemplateManager, Template
|
|
||||||
|
|
||||||
manager = TemplateManager()
|
|
||||||
|
|
||||||
# Create template from existing page
|
|
||||||
template = manager.create_template_from_page(
|
|
||||||
page=current_page,
|
|
||||||
name="My Grid Layout",
|
|
||||||
description="2x2 photo grid"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Save template
|
|
||||||
manager.save_template(template)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Using Templates
|
|
||||||
|
|
||||||
```python
|
|
||||||
# List available templates
|
|
||||||
templates = manager.list_templates()
|
|
||||||
# Returns: ["Grid_2x2", "Single_Large", "My Grid Layout", ...]
|
|
||||||
|
|
||||||
# Create new page from template
|
|
||||||
new_page = manager.create_page_from_template(
|
|
||||||
template_name="Grid_2x2",
|
|
||||||
target_page_size=(210, 297),
|
|
||||||
page_number=5
|
|
||||||
)
|
|
||||||
|
|
||||||
# Apply template to existing page
|
|
||||||
manager.apply_template_to_page(
|
|
||||||
template=template,
|
|
||||||
target_page=existing_page,
|
|
||||||
mode="replace", # or "reflow"
|
|
||||||
scaling="proportional" # or "stretch" or "center"
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Project Serialization
|
|
||||||
|
|
||||||
#### Save/Load Projects
|
|
||||||
|
|
||||||
Projects are saved as ZIP archives (.ppz) containing:
|
|
||||||
- `project.json` - Project metadata and structure
|
|
||||||
- `assets/` - All referenced images
|
|
||||||
|
|
||||||
```python
|
|
||||||
from pyPhotoAlbum.project_serializer import save_to_zip, load_from_zip
|
|
||||||
|
|
||||||
# Save project
|
|
||||||
success, error = save_to_zip(project, "album.ppz")
|
|
||||||
if not success:
|
|
||||||
print(f"Error saving: {error}")
|
|
||||||
|
|
||||||
# Load project
|
|
||||||
try:
|
|
||||||
loaded_project = load_from_zip("album.ppz")
|
|
||||||
print(f"Loaded: {loaded_project.name}")
|
|
||||||
except Exception as error:
|
|
||||||
print(f"Error loading: {error}")
|
|
||||||
|
|
||||||
# Get project info without loading
|
|
||||||
from pyPhotoAlbum.project_serializer import get_project_info
|
|
||||||
info = get_project_info("album.ppz")
|
|
||||||
print(f"Name: {info['name']}, Pages: {info['page_count']}")
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Project Structure
|
|
||||||
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"name": "My Album",
|
|
||||||
"serialization_version": "1.0",
|
|
||||||
"page_size_mm": [210, 297],
|
|
||||||
"working_dpi": 300,
|
|
||||||
"export_dpi": 300,
|
|
||||||
"pages": [
|
|
||||||
{
|
|
||||||
"page_number": 1,
|
|
||||||
"is_double_spread": false,
|
|
||||||
"layout": {
|
|
||||||
"width": 210,
|
|
||||||
"height": 297,
|
|
||||||
"elements": [...]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### PDF Export
|
|
||||||
|
|
||||||
```python
|
|
||||||
from pyPhotoAlbum.pdf_exporter import PDFExporter
|
|
||||||
|
|
||||||
# Create exporter
|
|
||||||
exporter = PDFExporter(project, export_dpi=300)
|
|
||||||
|
|
||||||
# Export with progress callback
|
|
||||||
def progress_callback(current, total):
|
|
||||||
print(f"Exporting page {current}/{total}")
|
|
||||||
|
|
||||||
success, errors = exporter.export(
|
|
||||||
output_path="album.pdf",
|
|
||||||
progress_callback=progress_callback
|
|
||||||
)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
print("PDF exported successfully")
|
|
||||||
else:
|
|
||||||
print(f"Errors: {errors}")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
### Run Tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run all tests
|
|
||||||
pytest
|
|
||||||
|
|
||||||
# Run with coverage
|
|
||||||
pytest --cov=pyPhotoAlbum --cov-report=html
|
|
||||||
|
|
||||||
# Run specific test file
|
|
||||||
pytest tests/test_models.py
|
|
||||||
|
|
||||||
# Run with verbose output
|
|
||||||
pytest -v
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
tests/
|
|
||||||
├── __init__.py
|
|
||||||
├── conftest.py # Shared fixtures
|
|
||||||
├── test_models.py # Model serialization tests
|
|
||||||
├── test_project.py # Project and page tests
|
|
||||||
├── test_project_serialization.py # Save/load tests
|
|
||||||
├── test_page_renderer.py # Rendering tests
|
|
||||||
├── test_pdf_export.py # PDF export tests
|
|
||||||
├── test_gl_widget_fixtures.py # Shared GL widget test fixtures
|
|
||||||
├── test_viewport_mixin.py # Viewport mixin tests
|
|
||||||
├── test_element_selection_mixin.py # Selection mixin tests
|
|
||||||
├── test_element_manipulation_mixin.py # Manipulation mixin tests
|
|
||||||
├── test_image_pan_mixin.py # Image pan mixin tests
|
|
||||||
├── test_page_navigation_mixin.py # Page navigation mixin tests
|
|
||||||
└── test_asset_drop_mixin.py # Asset drop mixin tests
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example Test Cases
|
|
||||||
|
|
||||||
From `tests/test_models.py`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def test_image_serialization():
|
|
||||||
"""Test ImageData serialization"""
|
|
||||||
img = ImageData(
|
|
||||||
image_path="test.jpg",
|
|
||||||
x=15.0, y=25.0,
|
|
||||||
width=180.0, height=120.0,
|
|
||||||
rotation=30.0,
|
|
||||||
z_index=3
|
|
||||||
)
|
|
||||||
|
|
||||||
# Serialize
|
|
||||||
data = img.serialize()
|
|
||||||
assert data["type"] == "image"
|
|
||||||
assert data["position"] == (15.0, 25.0)
|
|
||||||
|
|
||||||
# Deserialize
|
|
||||||
img2 = ImageData()
|
|
||||||
img2.deserialize(data)
|
|
||||||
assert img2.position == img.position
|
|
||||||
```
|
|
||||||
|
|
||||||
From `tests/test_project_serialization.py`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
def test_save_and_load_project(tmp_path):
|
|
||||||
"""Test complete save/load cycle"""
|
|
||||||
# Create project with pages
|
|
||||||
project = Project(name="Test")
|
|
||||||
page = Page(layout=PageLayout(), page_number=1)
|
|
||||||
project.add_page(page)
|
|
||||||
|
|
||||||
# Save
|
|
||||||
zip_path = tmp_path / "project.ppz"
|
|
||||||
success, error = save_to_zip(project, str(zip_path))
|
|
||||||
assert success is True
|
|
||||||
|
|
||||||
# Load
|
|
||||||
loaded = load_from_zip(str(zip_path))
|
|
||||||
assert loaded.name == "Test"
|
|
||||||
assert len(loaded.pages) == 1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
pyPhotoAlbum/
|
|
||||||
├── __init__.py
|
|
||||||
├── main.py # Application entry point
|
|
||||||
├── models.py # Data models (ImageData, TextBoxData, etc.)
|
|
||||||
├── project.py # Project and Page classes
|
|
||||||
├── page_layout.py # Page layout management
|
|
||||||
├── page_renderer.py # OpenGL rendering
|
|
||||||
├── gl_widget.py # Main OpenGL widget (mixin orchestration)
|
|
||||||
├── project_serializer.py # Save/load functionality
|
|
||||||
├── asset_manager.py # Asset handling
|
|
||||||
├── commands.py # Undo/redo system
|
|
||||||
├── template_manager.py # Template system
|
|
||||||
├── pdf_exporter.py # PDF export
|
|
||||||
├── alignment.py # Alignment tools
|
|
||||||
├── snapping.py # Snapping system
|
|
||||||
├── decorators.py # UI decorators
|
|
||||||
├── ribbon_widget.py # Ribbon interface
|
|
||||||
├── ribbon_builder.py # Ribbon configuration
|
|
||||||
├── mixins/ # Mixin architecture
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── base.py # Base mixin class
|
|
||||||
│ ├── viewport.py # Zoom and pan management
|
|
||||||
│ ├── rendering.py # OpenGL rendering pipeline
|
|
||||||
│ ├── asset_drop.py # Drag-and-drop functionality
|
|
||||||
│ ├── page_navigation.py # Page detection and ghost pages
|
|
||||||
│ ├── image_pan.py # Image cropping within frames
|
|
||||||
│ ├── element_manipulation.py # Resize and rotate
|
|
||||||
│ ├── element_selection.py # Hit detection and selection
|
|
||||||
│ ├── mouse_interaction.py # Mouse event coordination
|
|
||||||
│ ├── interaction_undo.py # Undo/redo integration
|
|
||||||
│ └── operations/ # Operation mixins
|
|
||||||
│ ├── element_ops.py
|
|
||||||
│ ├── page_ops.py
|
|
||||||
│ ├── file_ops.py
|
|
||||||
│ ├── view_ops.py
|
|
||||||
│ ├── edit_ops.py
|
|
||||||
│ ├── template_ops.py
|
|
||||||
│ ├── alignment_ops.py
|
|
||||||
│ ├── distribution_ops.py
|
|
||||||
│ └── size_ops.py
|
|
||||||
└── templates/ # Built-in templates
|
|
||||||
├── Grid_2x2.json
|
|
||||||
└── Single_Large.json
|
|
||||||
|
|
||||||
tests/ # Unit tests (312 tests, 29% coverage)
|
|
||||||
examples/ # Usage examples
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
### Code Style
|
|
||||||
|
|
||||||
The project uses:
|
|
||||||
- **Black** for code formatting (line length: 120)
|
|
||||||
- **Flake8** for linting
|
|
||||||
- **MyPy** for type checking
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Format code
|
|
||||||
black pyPhotoAlbum tests
|
|
||||||
|
|
||||||
# Run linter
|
|
||||||
flake8 pyPhotoAlbum tests
|
|
||||||
|
|
||||||
# Type checking
|
|
||||||
mypy pyPhotoAlbum
|
|
||||||
```
|
|
||||||
|
|
||||||
### Continuous Integration
|
|
||||||
|
|
||||||
GitHub Actions / Gitea Actions workflows:
|
|
||||||
- Run tests on Python 3.9, 3.10, 3.11
|
|
||||||
- Check code quality with linters
|
|
||||||
- Generate coverage reports
|
|
||||||
|
|
||||||
### Contributing
|
|
||||||
|
|
||||||
1. Fork the repository
|
|
||||||
2. Create a feature branch
|
|
||||||
3. Write tests for new features
|
|
||||||
4. Ensure all tests pass
|
|
||||||
5. Submit a pull request
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
See the `examples/` directory for complete working examples:
|
|
||||||
|
|
||||||
- `basic_usage.py` - Creating projects and adding images
|
|
||||||
- `template_example.py` - Working with templates
|
|
||||||
- `generate_screenshots.py` - Creating documentation screenshots
|
|
||||||
|
|
||||||
Run examples:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd examples
|
|
||||||
python basic_usage.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Reference
|
|
||||||
|
|
||||||
### Key Classes
|
|
||||||
|
|
||||||
#### BaseLayoutElement (Abstract)
|
|
||||||
Base class for all layout elements.
|
|
||||||
|
|
||||||
**Methods:**
|
|
||||||
- `render()` - Render element using OpenGL
|
|
||||||
- `serialize() -> Dict` - Convert to dictionary
|
|
||||||
- `deserialize(data: Dict)` - Load from dictionary
|
|
||||||
|
|
||||||
**Attributes:**
|
|
||||||
- `position: Tuple[float, float]` - (x, y) in mm
|
|
||||||
- `size: Tuple[float, float]` - (width, height) in mm
|
|
||||||
- `rotation: float` - Rotation angle in degrees
|
|
||||||
- `z_index: int` - Layer order
|
|
||||||
|
|
||||||
#### ImageData
|
|
||||||
Image element with crop support.
|
|
||||||
|
|
||||||
**Constructor:**
|
|
||||||
```python
|
|
||||||
ImageData(
|
|
||||||
image_path: str = "",
|
|
||||||
crop_info: Tuple = (0, 0, 1, 1),
|
|
||||||
x: float = 0,
|
|
||||||
y: float = 0,
|
|
||||||
width: float = 100,
|
|
||||||
height: float = 100,
|
|
||||||
rotation: float = 0,
|
|
||||||
z_index: int = 0
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### TextBoxData
|
|
||||||
Text element with formatting.
|
|
||||||
|
|
||||||
**Constructor:**
|
|
||||||
```python
|
|
||||||
TextBoxData(
|
|
||||||
text_content: str = "",
|
|
||||||
font_settings: Dict = None,
|
|
||||||
alignment: str = "left",
|
|
||||||
x: float = 0,
|
|
||||||
y: float = 0,
|
|
||||||
width: float = 100,
|
|
||||||
height: float = 100
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Font Settings:**
|
|
||||||
```python
|
|
||||||
{
|
|
||||||
"family": "Arial",
|
|
||||||
"size": 12,
|
|
||||||
"color": (0, 0, 0) # RGB tuple
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Project
|
|
||||||
Main project container.
|
|
||||||
|
|
||||||
**Methods:**
|
|
||||||
- `add_page(page: Page)` - Add page to project
|
|
||||||
- `remove_page(page: Page)` - Remove page
|
|
||||||
- `serialize() -> Dict` - Save to dictionary
|
|
||||||
- `deserialize(data: Dict)` - Load from dictionary
|
|
||||||
|
|
||||||
**Attributes:**
|
|
||||||
- `name: str` - Project name
|
|
||||||
- `pages: List[Page]` - List of pages
|
|
||||||
- `page_size_mm: Tuple[float, float]` - Page dimensions
|
|
||||||
- `working_dpi: int` - Display DPI
|
|
||||||
- `export_dpi: int` - Export DPI
|
|
||||||
- `asset_manager: AssetManager` - Asset handler
|
|
||||||
- `history: CommandHistory` - Undo/redo history
|
|
||||||
|
|
||||||
#### Page
|
|
||||||
Single page in project.
|
|
||||||
|
|
||||||
**Constructor:**
|
|
||||||
```python
|
|
||||||
Page(
|
|
||||||
layout: PageLayout = None,
|
|
||||||
page_number: int = 1,
|
|
||||||
is_double_spread: bool = False
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### PageLayout
|
|
||||||
Element container for a page.
|
|
||||||
|
|
||||||
**Methods:**
|
|
||||||
- `add_element(element: BaseLayoutElement)` - Add element
|
|
||||||
- `remove_element(element: BaseLayoutElement)` - Remove element
|
|
||||||
- `render(dpi: int)` - Render all elements
|
|
||||||
|
|
||||||
**Attributes:**
|
|
||||||
- `elements: List[BaseLayoutElement]` - Page elements
|
|
||||||
- `width: float` - Page width in mm
|
|
||||||
- `height: float` - Page height in mm
|
|
||||||
|
|
||||||
## Keyboard Shortcuts
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
@ -708,12 +124,6 @@ Element container for a page.
|
|||||||
|
|
||||||
This project is licensed under the MIT License.
|
This project is licensed under the MIT License.
|
||||||
|
|
||||||
## Links
|
|
||||||
|
|
||||||
- Documentation: [Link to docs]
|
|
||||||
- Issue Tracker: [Link to issues]
|
|
||||||
- Changelog: [Link to changelog]
|
|
||||||
|
|
||||||
## Acknowledgments
|
## Acknowledgments
|
||||||
|
|
||||||
Built with:
|
Built with:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user