This commit is contained in:
parent
c4b4b0a298
commit
131d39080e
210
HTML_GENERATION.md
Normal file
210
HTML_GENERATION.md
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
# HTML Generation for dreader
|
||||||
|
|
||||||
|
This document describes how to use the HTML generation features in dreader to create UI for e-reader applications.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The dreader library now includes HTML generation capabilities that allow you to create complete user interfaces programmatically. This is designed to work with a Hardware Abstraction Layer (HAL) that handles the actual display rendering and input processing.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ dreader Library │
|
||||||
|
│ ├─ EbookReader (book rendering) │
|
||||||
|
│ ├─ html_generator (UI generation) │
|
||||||
|
│ └─ book_utils (scanning/metadata) │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
↓ HTML strings
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ HAL (Hardware Abstraction Layer) │
|
||||||
|
│ - Receives HTML strings │
|
||||||
|
│ - Renders to display │
|
||||||
|
│ - Captures touch/button input │
|
||||||
|
│ - Calls back to dreader │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Page and Overlay Concept
|
||||||
|
|
||||||
|
The UI uses a **page/overlay** architecture:
|
||||||
|
|
||||||
|
- **Page (background)**: The main book content rendered as an image
|
||||||
|
- **Overlay (foreground)**: UI elements like settings, table of contents, bookmarks, etc.
|
||||||
|
|
||||||
|
## Available Modules
|
||||||
|
|
||||||
|
### 1. html_generator
|
||||||
|
|
||||||
|
Functions for generating HTML strings:
|
||||||
|
|
||||||
|
- `generate_library_html(books)` - Grid view of all books with covers
|
||||||
|
- `generate_reader_html(title, author, page_data)` - Book reading view
|
||||||
|
- `generate_settings_overlay()` - Settings panel
|
||||||
|
- `generate_toc_overlay(chapters)` - Table of contents
|
||||||
|
- `generate_bookmarks_overlay(bookmarks)` - Bookmarks list
|
||||||
|
|
||||||
|
### 2. book_utils
|
||||||
|
|
||||||
|
Utilities for managing books:
|
||||||
|
|
||||||
|
- `scan_book_directory(path)` - Scan directory for EPUB files
|
||||||
|
- `extract_book_metadata(epub_path)` - Get title, author, cover
|
||||||
|
- `get_chapter_list(reader)` - Format chapters for TOC
|
||||||
|
- `get_bookmark_list(reader)` - Format bookmarks
|
||||||
|
- `page_image_to_base64(image)` - Convert page image to base64
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
from pathlib import Path
|
||||||
|
from dreader import create_ebook_reader
|
||||||
|
from dreader.html_generator import (
|
||||||
|
generate_library_html,
|
||||||
|
generate_reader_html,
|
||||||
|
generate_toc_overlay
|
||||||
|
)
|
||||||
|
from dreader.book_utils import (
|
||||||
|
scan_book_directory,
|
||||||
|
get_chapter_list,
|
||||||
|
page_image_to_base64
|
||||||
|
)
|
||||||
|
|
||||||
|
# 1. Show library view
|
||||||
|
books_dir = Path('books')
|
||||||
|
books = scan_book_directory(books_dir)
|
||||||
|
library_html = generate_library_html(books)
|
||||||
|
# Pass library_html to HAL for rendering
|
||||||
|
|
||||||
|
# 2. User selects a book
|
||||||
|
selected_book = books[0]
|
||||||
|
reader = create_ebook_reader(page_size=(800, 1000))
|
||||||
|
reader.load_epub(selected_book['path'])
|
||||||
|
|
||||||
|
# 3. Show reader view
|
||||||
|
page_image = reader.get_current_page()
|
||||||
|
page_base64 = page_image_to_base64(page_image)
|
||||||
|
reader_html = generate_reader_html(
|
||||||
|
book_title=reader.book_title,
|
||||||
|
book_author=reader.book_author,
|
||||||
|
page_image_data=page_base64
|
||||||
|
)
|
||||||
|
# Pass reader_html to HAL for rendering
|
||||||
|
|
||||||
|
# 4. User presses "Contents" button - show TOC overlay
|
||||||
|
chapters = get_chapter_list(reader)
|
||||||
|
toc_html = generate_toc_overlay(chapters)
|
||||||
|
# Pass toc_html to HAL for rendering on top of page
|
||||||
|
```
|
||||||
|
|
||||||
|
## HTML Structure
|
||||||
|
|
||||||
|
### Library View
|
||||||
|
|
||||||
|
The library uses an HTML table for grid layout:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<table class="library-grid">
|
||||||
|
<tr>
|
||||||
|
<td class="book-item">
|
||||||
|
<table>
|
||||||
|
<tr><td class="cover-cell"><img src="..."></td></tr>
|
||||||
|
<tr><td class="title-cell">Book Title</td></tr>
|
||||||
|
<tr><td class="author-cell">Author Name</td></tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<!-- More books... -->
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reader View
|
||||||
|
|
||||||
|
The reader view has three sections:
|
||||||
|
|
||||||
|
- Header: Book info + buttons (Library, Contents, Settings)
|
||||||
|
- Page container: Centered book page image
|
||||||
|
- Footer: Navigation buttons (Previous, Next)
|
||||||
|
|
||||||
|
### Overlays
|
||||||
|
|
||||||
|
Overlays use:
|
||||||
|
|
||||||
|
- Semi-transparent background (`rgba(0, 0, 0, 0.7)`)
|
||||||
|
- Centered white panel
|
||||||
|
- Close button
|
||||||
|
- Table-based layout for content
|
||||||
|
|
||||||
|
## Button/Link Interaction
|
||||||
|
|
||||||
|
All interactive elements have:
|
||||||
|
|
||||||
|
- `id` attributes for buttons (e.g., `id="btn-next"`)
|
||||||
|
- `data-*` attributes for dynamic content (e.g., `data-chapter-index="5"`)
|
||||||
|
- CSS classes for styling (e.g., `class="nav-button"`)
|
||||||
|
|
||||||
|
Your HAL should:
|
||||||
|
|
||||||
|
1. Parse the HTML to identify interactive elements
|
||||||
|
2. Map touch/click coordinates to elements
|
||||||
|
3. Call appropriate dreader methods
|
||||||
|
4. Regenerate and render updated HTML
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
Run the included demo to see all features:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source venv/bin/activate
|
||||||
|
python examples/html_generation_demo.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This will generate example HTML files in the `output/` directory that you can open in a browser to preview.
|
||||||
|
|
||||||
|
## Integration with HAL
|
||||||
|
|
||||||
|
Your HAL should implement:
|
||||||
|
|
||||||
|
1. **HTML Rendering**: Parse and display HTML strings
|
||||||
|
2. **Touch Input**: Map touch coordinates to HTML elements
|
||||||
|
3. **State Management**: Maintain reader state between interactions
|
||||||
|
4. **Re-rendering**: Update display when state changes
|
||||||
|
|
||||||
|
Example HAL flow:
|
||||||
|
|
||||||
|
```
|
||||||
|
User touches screen
|
||||||
|
↓
|
||||||
|
HAL identifies touched element (e.g., "btn-next")
|
||||||
|
↓
|
||||||
|
HAL calls reader.next_page()
|
||||||
|
↓
|
||||||
|
HAL regenerates reader_html with new page
|
||||||
|
↓
|
||||||
|
HAL renders updated HTML
|
||||||
|
```
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
|
||||||
|
All HTML includes inline CSS for complete styling. The design is:
|
||||||
|
|
||||||
|
- Clean, minimal interface
|
||||||
|
- Dark theme for reader (reduces eye strain)
|
||||||
|
- Large touch targets for buttons
|
||||||
|
- Responsive layout using tables (widely supported)
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
To customize the UI:
|
||||||
|
|
||||||
|
1. Edit functions in `dreader/html_generator.py`
|
||||||
|
2. Modify CSS in the `<style>` blocks
|
||||||
|
3. Change layout structure in the HTML templates
|
||||||
|
4. Adjust colors, fonts, spacing as needed
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `dreader/html_generator.py` - HTML generation functions
|
||||||
|
- `dreader/book_utils.py` - Book scanning and utilities
|
||||||
|
- `examples/html_generation_demo.py` - Complete demonstration
|
||||||
|
- `output/` - Generated HTML examples (after running demo)
|
||||||
@ -6,6 +6,8 @@ with all essential features for building ereader applications.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dreader.application import EbookReader, create_ebook_reader
|
from dreader.application import EbookReader, create_ebook_reader
|
||||||
|
from dreader import html_generator
|
||||||
|
from dreader import book_utils
|
||||||
|
|
||||||
__version__ = "0.1.0"
|
__version__ = "0.1.0"
|
||||||
__all__ = ["EbookReader", "create_ebook_reader"]
|
__all__ = ["EbookReader", "create_ebook_reader", "html_generator", "book_utils"]
|
||||||
|
|||||||
179
dreader/book_utils.py
Normal file
179
dreader/book_utils.py
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
"""
|
||||||
|
Utilities for managing book library, scanning EPUBs, and extracting metadata.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Optional
|
||||||
|
from dreader import create_ebook_reader
|
||||||
|
import base64
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
|
def scan_book_directory(directory: Path) -> List[Dict[str, str]]:
|
||||||
|
"""
|
||||||
|
Scan a directory for EPUB files and extract metadata.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: Path to directory containing EPUB files
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of book dictionaries with metadata
|
||||||
|
"""
|
||||||
|
books = []
|
||||||
|
epub_files = list(directory.glob('*.epub'))
|
||||||
|
|
||||||
|
for epub_path in epub_files:
|
||||||
|
metadata = extract_book_metadata(epub_path)
|
||||||
|
if metadata:
|
||||||
|
books.append(metadata)
|
||||||
|
|
||||||
|
return sorted(books, key=lambda b: b['title'].lower())
|
||||||
|
|
||||||
|
|
||||||
|
def extract_book_metadata(epub_path: Path, include_cover: bool = True) -> Optional[Dict]:
|
||||||
|
"""
|
||||||
|
Extract metadata from an EPUB file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
epub_path: Path to EPUB file
|
||||||
|
include_cover: Whether to extract and include cover image as base64
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dictionary with book metadata or None if extraction fails
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Create temporary reader to extract metadata
|
||||||
|
reader = create_ebook_reader(page_size=(400, 600))
|
||||||
|
reader.load_epub(str(epub_path))
|
||||||
|
|
||||||
|
metadata = {
|
||||||
|
'filename': epub_path.name,
|
||||||
|
'path': str(epub_path),
|
||||||
|
'title': reader.book_title or epub_path.stem,
|
||||||
|
'author': reader.book_author or 'Unknown Author',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extract cover image if requested
|
||||||
|
if include_cover:
|
||||||
|
cover_data = extract_cover_as_base64(reader)
|
||||||
|
metadata['cover_data'] = cover_data
|
||||||
|
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error extracting metadata from {epub_path}: {e}")
|
||||||
|
return {
|
||||||
|
'filename': epub_path.name,
|
||||||
|
'path': str(epub_path),
|
||||||
|
'title': epub_path.stem,
|
||||||
|
'author': 'Unknown',
|
||||||
|
'cover_data': None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def extract_cover_as_base64(reader, max_width: int = 300, max_height: int = 450) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Extract cover image from reader and return as base64 encoded string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reader: EbookReader instance with loaded book
|
||||||
|
max_width: Maximum width for cover image
|
||||||
|
max_height: Maximum height for cover image
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Base64 encoded PNG image string or None
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get first page as cover
|
||||||
|
cover_image = reader.get_current_page()
|
||||||
|
|
||||||
|
# Resize if needed
|
||||||
|
if cover_image.width > max_width or cover_image.height > max_height:
|
||||||
|
cover_image.thumbnail((max_width, max_height))
|
||||||
|
|
||||||
|
# Convert to base64
|
||||||
|
buffer = BytesIO()
|
||||||
|
cover_image.save(buffer, format='PNG')
|
||||||
|
img_bytes = buffer.getvalue()
|
||||||
|
img_base64 = base64.b64encode(img_bytes).decode('utf-8')
|
||||||
|
|
||||||
|
return img_base64
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error extracting cover image: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_chapter_list(reader) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Get formatted chapter list from reader.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reader: EbookReader instance with loaded book
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of chapter dictionaries with index and title
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
chapters = reader.get_chapters()
|
||||||
|
result = []
|
||||||
|
for i, chapter in enumerate(chapters):
|
||||||
|
# Handle different chapter formats
|
||||||
|
if isinstance(chapter, str):
|
||||||
|
title = chapter
|
||||||
|
elif isinstance(chapter, dict):
|
||||||
|
title = chapter.get('title', f'Chapter {i+1}')
|
||||||
|
elif isinstance(chapter, tuple) and len(chapter) >= 2:
|
||||||
|
# Tuple format: (title, ...)
|
||||||
|
title = chapter[0] if chapter[0] else f'Chapter {i+1}'
|
||||||
|
else:
|
||||||
|
title = f'Chapter {i+1}'
|
||||||
|
|
||||||
|
result.append({
|
||||||
|
'index': i,
|
||||||
|
'title': title
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting chapters: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_bookmark_list(reader) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Get formatted bookmark list from reader.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reader: EbookReader instance with loaded book
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of bookmark dictionaries
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
bookmarks = reader.list_saved_positions()
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'name': bookmark,
|
||||||
|
'position': '' # Could be enhanced to show chapter/page info
|
||||||
|
}
|
||||||
|
for bookmark in bookmarks
|
||||||
|
]
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting bookmarks: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def page_image_to_base64(page_image) -> str:
|
||||||
|
"""
|
||||||
|
Convert PIL Image to base64 encoded string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page_image: PIL Image object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Base64 encoded PNG string
|
||||||
|
"""
|
||||||
|
buffer = BytesIO()
|
||||||
|
page_image.save(buffer, format='PNG')
|
||||||
|
img_bytes = buffer.getvalue()
|
||||||
|
return base64.b64encode(img_bytes).decode('utf-8')
|
||||||
676
dreader/html_generator.py
Normal file
676
dreader/html_generator.py
Normal file
@ -0,0 +1,676 @@
|
|||||||
|
"""
|
||||||
|
HTML generation functions for dreader UI.
|
||||||
|
|
||||||
|
Generates HTML strings programmatically for library view, reader view,
|
||||||
|
and various overlays (settings, TOC, etc.) that can be passed to a HAL
|
||||||
|
for rendering.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List, Dict, Optional
|
||||||
|
from dreader import create_ebook_reader
|
||||||
|
import base64
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
|
def generate_library_html(books: List[Dict[str, str]]) -> str:
|
||||||
|
"""
|
||||||
|
Generate HTML for the library view showing all books in a grid.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
books: List of book dictionaries with keys:
|
||||||
|
- title: Book title
|
||||||
|
- author: Book author
|
||||||
|
- filename: EPUB filename
|
||||||
|
- cover_data: Optional base64 encoded cover image
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Complete HTML string for library view
|
||||||
|
"""
|
||||||
|
books_html = []
|
||||||
|
for book in books:
|
||||||
|
cover_img = ''
|
||||||
|
if book.get('cover_data'):
|
||||||
|
cover_img = f'<img src="data:image/png;base64,{book["cover_data"]}" alt="Cover">'
|
||||||
|
else:
|
||||||
|
# Placeholder if no cover
|
||||||
|
cover_img = f'<div class="no-cover">{book["title"][:1]}</div>'
|
||||||
|
|
||||||
|
books_html.append(f'''
|
||||||
|
<td class="book-item" data-filename="{book['filename']}">
|
||||||
|
<table style="width: 100%;">
|
||||||
|
<tr>
|
||||||
|
<td class="cover-cell">
|
||||||
|
{cover_img}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="title-cell">{book['title']}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="author-cell">{book['author']}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
''')
|
||||||
|
|
||||||
|
# Arrange books in rows of 3
|
||||||
|
rows = []
|
||||||
|
for i in range(0, len(books_html), 3):
|
||||||
|
row_books = books_html[i:i+3]
|
||||||
|
# Pad with empty cells if needed
|
||||||
|
while len(row_books) < 3:
|
||||||
|
row_books.append('<td class="book-item empty"></td>')
|
||||||
|
rows.append(f'<tr>{"".join(row_books)}</tr>')
|
||||||
|
|
||||||
|
html = f'''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Library</title>
|
||||||
|
<style>
|
||||||
|
* {{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}}
|
||||||
|
body {{
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 20px;
|
||||||
|
}}
|
||||||
|
.header {{
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}}
|
||||||
|
.library-grid {{
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 20px;
|
||||||
|
}}
|
||||||
|
.book-item {{
|
||||||
|
background-color: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align: top;
|
||||||
|
width: 33%;
|
||||||
|
}}
|
||||||
|
.book-item:hover {{
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
||||||
|
}}
|
||||||
|
.book-item.empty {{
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: default;
|
||||||
|
}}
|
||||||
|
.cover-cell {{
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}}
|
||||||
|
.cover-cell img {{
|
||||||
|
max-width: 200px;
|
||||||
|
max-height: 300px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}}
|
||||||
|
.no-cover {{
|
||||||
|
width: 200px;
|
||||||
|
height: 300px;
|
||||||
|
background-color: #ddd;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 72px;
|
||||||
|
color: #999;
|
||||||
|
margin: 0 auto;
|
||||||
|
}}
|
||||||
|
.title-cell {{
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 5px;
|
||||||
|
}}
|
||||||
|
.author-cell {{
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 5px;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>My Library</h1>
|
||||||
|
<p>{len(books)} books</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="library-grid">
|
||||||
|
{"".join(rows)}
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
def generate_reader_html(book_title: str, book_author: str, page_image_data: str) -> str:
|
||||||
|
"""
|
||||||
|
Generate HTML for the reader view with page display.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
book_title: Title of current book
|
||||||
|
book_author: Author of current book
|
||||||
|
page_image_data: Base64 encoded page image
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Complete HTML string for reader view (page layer only)
|
||||||
|
"""
|
||||||
|
html = f'''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{book_title}</title>
|
||||||
|
<style>
|
||||||
|
* {{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}}
|
||||||
|
body {{
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #2c2c2c;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
}}
|
||||||
|
.header {{
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}}
|
||||||
|
.book-info {{
|
||||||
|
flex: 1;
|
||||||
|
}}
|
||||||
|
.book-title {{
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 16px;
|
||||||
|
}}
|
||||||
|
.book-author {{
|
||||||
|
font-size: 12px;
|
||||||
|
color: #aaa;
|
||||||
|
}}
|
||||||
|
.header-buttons {{
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}}
|
||||||
|
.header-button {{
|
||||||
|
background-color: #444;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}}
|
||||||
|
.header-button:hover {{
|
||||||
|
background-color: #555;
|
||||||
|
}}
|
||||||
|
.page-container {{
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
}}
|
||||||
|
.page-image {{
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
||||||
|
}}
|
||||||
|
.footer {{
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}}
|
||||||
|
.nav-button {{
|
||||||
|
background-color: #444;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
}}
|
||||||
|
.nav-button:hover {{
|
||||||
|
background-color: #555;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<div class="book-info">
|
||||||
|
<div class="book-title">{book_title}</div>
|
||||||
|
<div class="book-author">{book_author}</div>
|
||||||
|
</div>
|
||||||
|
<div class="header-buttons">
|
||||||
|
<button class="header-button" id="btn-library">Library</button>
|
||||||
|
<button class="header-button" id="btn-toc">Contents</button>
|
||||||
|
<button class="header-button" id="btn-settings">Settings</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-container">
|
||||||
|
<img src="data:image/png;base64,{page_image_data}" alt="Page" class="page-image">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<button class="nav-button" id="btn-prev">← Previous</button>
|
||||||
|
<div id="page-info"></div>
|
||||||
|
<button class="nav-button" id="btn-next">Next →</button>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
def generate_settings_overlay() -> str:
|
||||||
|
"""
|
||||||
|
Generate HTML for the settings overlay.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HTML string for settings overlay
|
||||||
|
"""
|
||||||
|
html = '''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Settings</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
.overlay-panel {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
|
||||||
|
padding: 20px;
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
.overlay-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #ddd;
|
||||||
|
}
|
||||||
|
.overlay-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.close-button {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.close-button:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
}
|
||||||
|
.settings-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.settings-table td {
|
||||||
|
padding: 10px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
.setting-label {
|
||||||
|
font-weight: bold;
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
.setting-control {
|
||||||
|
width: 60%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.control-button {
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.control-button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="overlay-panel">
|
||||||
|
<div class="overlay-header">
|
||||||
|
<span class="overlay-title">Settings</span>
|
||||||
|
<button class="close-button" id="btn-close">Close</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="settings-table">
|
||||||
|
<tr>
|
||||||
|
<td class="setting-label">Font Size</td>
|
||||||
|
<td class="setting-control">
|
||||||
|
<button class="control-button" id="btn-font-decrease">A-</button>
|
||||||
|
<button class="control-button" id="btn-font-increase">A+</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="setting-label">Line Spacing</td>
|
||||||
|
<td class="setting-control">
|
||||||
|
<button class="control-button" id="btn-spacing-decrease">-</button>
|
||||||
|
<button class="control-button" id="btn-spacing-increase">+</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="setting-label">Brightness</td>
|
||||||
|
<td class="setting-control">
|
||||||
|
<button class="control-button" id="btn-brightness-decrease">-</button>
|
||||||
|
<button class="control-button" id="btn-brightness-increase">+</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="setting-label">WiFi</td>
|
||||||
|
<td class="setting-control">
|
||||||
|
<button class="control-button" id="btn-wifi">Configure</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
def generate_toc_overlay(chapters: List[Dict]) -> str:
|
||||||
|
"""
|
||||||
|
Generate HTML for the table of contents overlay.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chapters: List of chapter dictionaries with keys:
|
||||||
|
- index: Chapter index
|
||||||
|
- title: Chapter title
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HTML string for TOC overlay
|
||||||
|
"""
|
||||||
|
chapter_rows = []
|
||||||
|
for chapter in chapters:
|
||||||
|
chapter_rows.append(f'''
|
||||||
|
<tr class="chapter-row" data-chapter-index="{chapter['index']}">
|
||||||
|
<td class="chapter-cell">{chapter['title']}</td>
|
||||||
|
</tr>
|
||||||
|
''')
|
||||||
|
|
||||||
|
html = f'''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Table of Contents</title>
|
||||||
|
<style>
|
||||||
|
* {{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}}
|
||||||
|
body {{
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
}}
|
||||||
|
.overlay-panel {{
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
|
||||||
|
padding: 20px;
|
||||||
|
min-width: 500px;
|
||||||
|
max-height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}}
|
||||||
|
.overlay-header {{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #ddd;
|
||||||
|
}}
|
||||||
|
.overlay-title {{
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}}
|
||||||
|
.close-button {{
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}}
|
||||||
|
.close-button:hover {{
|
||||||
|
background-color: #c82333;
|
||||||
|
}}
|
||||||
|
.chapters-container {{
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
}}
|
||||||
|
.chapters-table {{
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}}
|
||||||
|
.chapter-row {{
|
||||||
|
cursor: pointer;
|
||||||
|
}}
|
||||||
|
.chapter-row:hover {{
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}}
|
||||||
|
.chapter-cell {{
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="overlay-panel">
|
||||||
|
<div class="overlay-header">
|
||||||
|
<span class="overlay-title">Table of Contents</span>
|
||||||
|
<button class="close-button" id="btn-close">Close</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chapters-container">
|
||||||
|
<table class="chapters-table">
|
||||||
|
{"".join(chapter_rows)}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
def generate_bookmarks_overlay(bookmarks: List[Dict]) -> str:
|
||||||
|
"""
|
||||||
|
Generate HTML for the bookmarks overlay.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bookmarks: List of bookmark dictionaries with keys:
|
||||||
|
- name: Bookmark name
|
||||||
|
- position: Position info
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
HTML string for bookmarks overlay
|
||||||
|
"""
|
||||||
|
bookmark_rows = []
|
||||||
|
for bookmark in bookmarks:
|
||||||
|
bookmark_rows.append(f'''
|
||||||
|
<tr class="bookmark-row" data-bookmark-name="{bookmark['name']}">
|
||||||
|
<td class="bookmark-cell">
|
||||||
|
<div class="bookmark-name">{bookmark['name']}</div>
|
||||||
|
<div class="bookmark-position">{bookmark.get('position', '')}</div>
|
||||||
|
</td>
|
||||||
|
<td class="bookmark-actions">
|
||||||
|
<button class="action-button delete-button" data-bookmark="{bookmark['name']}">Delete</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
''')
|
||||||
|
|
||||||
|
html = f'''
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Bookmarks</title>
|
||||||
|
<style>
|
||||||
|
* {{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}}
|
||||||
|
body {{
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100vh;
|
||||||
|
}}
|
||||||
|
.overlay-panel {{
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
|
||||||
|
padding: 20px;
|
||||||
|
min-width: 500px;
|
||||||
|
max-height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}}
|
||||||
|
.overlay-header {{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #ddd;
|
||||||
|
}}
|
||||||
|
.overlay-title {{
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}}
|
||||||
|
.close-button {{
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}}
|
||||||
|
.close-button:hover {{
|
||||||
|
background-color: #c82333;
|
||||||
|
}}
|
||||||
|
.bookmarks-container {{
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1;
|
||||||
|
}}
|
||||||
|
.bookmarks-table {{
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}}
|
||||||
|
.bookmark-row {{
|
||||||
|
cursor: pointer;
|
||||||
|
}}
|
||||||
|
.bookmark-row:hover {{
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}}
|
||||||
|
.bookmark-cell {{
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}}
|
||||||
|
.bookmark-name {{
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}}
|
||||||
|
.bookmark-position {{
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
}}
|
||||||
|
.bookmark-actions {{
|
||||||
|
padding: 12px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
text-align: right;
|
||||||
|
width: 100px;
|
||||||
|
}}
|
||||||
|
.action-button {{
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}}
|
||||||
|
.action-button:hover {{
|
||||||
|
background-color: #c82333;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="overlay-panel">
|
||||||
|
<div class="overlay-header">
|
||||||
|
<span class="overlay-title">Bookmarks</span>
|
||||||
|
<button class="close-button" id="btn-close">Close</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bookmarks-container">
|
||||||
|
<table class="bookmarks-table">
|
||||||
|
{"".join(bookmark_rows)}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
'''
|
||||||
|
return html
|
||||||
216
examples/html_generation_demo.py
Normal file
216
examples/html_generation_demo.py
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
"""
|
||||||
|
Demonstration of HTML generation for dreader UI.
|
||||||
|
|
||||||
|
This example shows how to:
|
||||||
|
1. Scan a book directory
|
||||||
|
2. Generate library view HTML
|
||||||
|
3. Load a book and generate reader view HTML
|
||||||
|
4. Generate overlay HTML for settings, TOC, and bookmarks
|
||||||
|
|
||||||
|
The generated HTML strings can be passed to a HAL (Hardware Abstraction Layer)
|
||||||
|
for rendering on the target device.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from dreader import create_ebook_reader
|
||||||
|
from dreader.html_generator import (
|
||||||
|
generate_library_html,
|
||||||
|
generate_reader_html,
|
||||||
|
generate_settings_overlay,
|
||||||
|
generate_toc_overlay,
|
||||||
|
generate_bookmarks_overlay
|
||||||
|
)
|
||||||
|
from dreader.book_utils import (
|
||||||
|
scan_book_directory,
|
||||||
|
get_chapter_list,
|
||||||
|
get_bookmark_list,
|
||||||
|
page_image_to_base64
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def demo_library_view():
|
||||||
|
"""Generate and save library view HTML."""
|
||||||
|
print("Generating library view...")
|
||||||
|
|
||||||
|
# Scan books directory
|
||||||
|
books_dir = Path('tests/data')
|
||||||
|
books = scan_book_directory(books_dir)
|
||||||
|
|
||||||
|
print(f"Found {len(books)} books:")
|
||||||
|
for book in books:
|
||||||
|
print(f" - {book['title']} by {book['author']}")
|
||||||
|
|
||||||
|
# Generate HTML
|
||||||
|
library_html = generate_library_html(books)
|
||||||
|
|
||||||
|
# Save to file for inspection
|
||||||
|
output_path = Path('output/library.html')
|
||||||
|
output_path.parent.mkdir(exist_ok=True)
|
||||||
|
output_path.write_text(library_html)
|
||||||
|
|
||||||
|
print(f"Library HTML saved to: {output_path}")
|
||||||
|
print(f"HTML length: {len(library_html)} characters\n")
|
||||||
|
|
||||||
|
|
||||||
|
def demo_reader_view():
|
||||||
|
"""Generate and save reader view HTML."""
|
||||||
|
print("Generating reader view...")
|
||||||
|
|
||||||
|
# Load a test book
|
||||||
|
book_path = Path('tests/data/test.epub')
|
||||||
|
if not book_path.exists():
|
||||||
|
print(f"Test book not found at {book_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
reader = create_ebook_reader(page_size=(800, 1000))
|
||||||
|
reader.load_epub(str(book_path))
|
||||||
|
|
||||||
|
# Get current page
|
||||||
|
page_image = reader.get_current_page()
|
||||||
|
page_base64 = page_image_to_base64(page_image)
|
||||||
|
|
||||||
|
# Generate HTML
|
||||||
|
reader_html = generate_reader_html(
|
||||||
|
book_title=reader.book_title or "Unknown Title",
|
||||||
|
book_author=reader.book_author or "Unknown Author",
|
||||||
|
page_image_data=page_base64
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save to file
|
||||||
|
output_path = Path('output/reader.html')
|
||||||
|
output_path.write_text(reader_html)
|
||||||
|
|
||||||
|
print(f"Reader HTML saved to: {output_path}")
|
||||||
|
print(f"HTML length: {len(reader_html)} characters")
|
||||||
|
print(f"Book: {reader.book_title} by {reader.book_author}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def demo_overlays():
|
||||||
|
"""Generate and save overlay HTML."""
|
||||||
|
print("Generating overlay views...")
|
||||||
|
|
||||||
|
# Load a test book
|
||||||
|
book_path = Path('tests/data/test.epub')
|
||||||
|
if not book_path.exists():
|
||||||
|
print(f"Test book not found at {book_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
reader = create_ebook_reader(page_size=(800, 1000))
|
||||||
|
reader.load_epub(str(book_path))
|
||||||
|
|
||||||
|
# 1. Settings overlay
|
||||||
|
settings_html = generate_settings_overlay()
|
||||||
|
settings_path = Path('output/overlay_settings.html')
|
||||||
|
settings_path.write_text(settings_html)
|
||||||
|
print(f"Settings overlay saved to: {settings_path}")
|
||||||
|
|
||||||
|
# 2. TOC overlay
|
||||||
|
chapters = get_chapter_list(reader)
|
||||||
|
print(f"Found {len(chapters)} chapters")
|
||||||
|
toc_html = generate_toc_overlay(chapters)
|
||||||
|
toc_path = Path('output/overlay_toc.html')
|
||||||
|
toc_path.write_text(toc_html)
|
||||||
|
print(f"TOC overlay saved to: {toc_path}")
|
||||||
|
|
||||||
|
# 3. Bookmarks overlay (create some test bookmarks first)
|
||||||
|
reader.save_position('Chapter 1 Start')
|
||||||
|
reader.next_page()
|
||||||
|
reader.next_page()
|
||||||
|
reader.save_position('Page 3')
|
||||||
|
|
||||||
|
bookmarks = get_bookmark_list(reader)
|
||||||
|
print(f"Found {len(bookmarks)} bookmarks")
|
||||||
|
bookmarks_html = generate_bookmarks_overlay(bookmarks)
|
||||||
|
bookmarks_path = Path('output/overlay_bookmarks.html')
|
||||||
|
bookmarks_path.write_text(bookmarks_html)
|
||||||
|
print(f"Bookmarks overlay saved to: {bookmarks_path}\n")
|
||||||
|
|
||||||
|
|
||||||
|
def demo_hal_usage():
|
||||||
|
"""
|
||||||
|
Demonstrate how a HAL would use these functions.
|
||||||
|
|
||||||
|
This simulates how your Hardware Abstraction Layer would
|
||||||
|
interact with the HTML generation functions.
|
||||||
|
"""
|
||||||
|
print("=" * 60)
|
||||||
|
print("HAL Integration Example")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Step 1: Initialize with books directory
|
||||||
|
books_dir = Path('tests/data')
|
||||||
|
|
||||||
|
# Step 2: Show library view
|
||||||
|
books = scan_book_directory(books_dir)
|
||||||
|
library_html = generate_library_html(books)
|
||||||
|
print(f"\n[HAL] Rendering library with {len(books)} books")
|
||||||
|
print(f"[HAL] HTML size: {len(library_html)} bytes")
|
||||||
|
|
||||||
|
# Step 3: User selects a book (simulated)
|
||||||
|
if books:
|
||||||
|
selected_book = books[0]
|
||||||
|
print(f"\n[HAL] User selected: {selected_book['title']}")
|
||||||
|
|
||||||
|
# Step 4: Load book and show reader view
|
||||||
|
reader = create_ebook_reader(page_size=(800, 1000))
|
||||||
|
reader.load_epub(selected_book['path'])
|
||||||
|
|
||||||
|
page_image = reader.get_current_page()
|
||||||
|
page_base64 = page_image_to_base64(page_image)
|
||||||
|
|
||||||
|
reader_html = generate_reader_html(
|
||||||
|
book_title=reader.book_title,
|
||||||
|
book_author=reader.book_author,
|
||||||
|
page_image_data=page_base64
|
||||||
|
)
|
||||||
|
print(f"[HAL] Rendering reader view")
|
||||||
|
print(f"[HAL] HTML size: {len(reader_html)} bytes")
|
||||||
|
|
||||||
|
# Step 5: User presses "Settings" button (simulated)
|
||||||
|
print(f"\n[HAL] User pressed 'Settings' button")
|
||||||
|
settings_html = generate_settings_overlay()
|
||||||
|
print(f"[HAL] Rendering settings overlay on top of page")
|
||||||
|
print(f"[HAL] HTML size: {len(settings_html)} bytes")
|
||||||
|
|
||||||
|
# Step 6: User presses "Contents" button (simulated)
|
||||||
|
print(f"\n[HAL] User pressed 'Contents' button")
|
||||||
|
chapters = get_chapter_list(reader)
|
||||||
|
toc_html = generate_toc_overlay(chapters)
|
||||||
|
print(f"[HAL] Rendering TOC overlay with {len(chapters)} chapters")
|
||||||
|
print(f"[HAL] HTML size: {len(toc_html)} bytes")
|
||||||
|
|
||||||
|
# Step 7: User navigates pages (simulated)
|
||||||
|
print(f"\n[HAL] User pressed 'Next' button")
|
||||||
|
reader.next_page()
|
||||||
|
page_image = reader.get_current_page()
|
||||||
|
page_base64 = page_image_to_base64(page_image)
|
||||||
|
reader_html = generate_reader_html(
|
||||||
|
book_title=reader.book_title,
|
||||||
|
book_author=reader.book_author,
|
||||||
|
page_image_data=page_base64
|
||||||
|
)
|
||||||
|
print(f"[HAL] Rendering next page")
|
||||||
|
print(f"[HAL] HTML size: {len(reader_html)} bytes")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print("dreader HTML Generation Demo")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Create output directory
|
||||||
|
Path('output').mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Run demos
|
||||||
|
demo_library_view()
|
||||||
|
demo_reader_view()
|
||||||
|
demo_overlays()
|
||||||
|
demo_hal_usage()
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
print("Demo complete!")
|
||||||
|
print("=" * 60)
|
||||||
|
print("\nGenerated HTML files have been saved to the 'output/' directory.")
|
||||||
|
print("Open them in a browser to see how they look.")
|
||||||
|
print("\nIn a real application, these HTML strings would be passed")
|
||||||
|
print("to your HAL for rendering on the target display device.")
|
||||||
BIN
tests/data/cover 1.png
Normal file
BIN
tests/data/cover 1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
BIN
tests/data/cover 2.png
Normal file
BIN
tests/data/cover 2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
BIN
tests/data/cover 3.png
Normal file
BIN
tests/data/cover 3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
BIN
tests/data/cover 4.png
Normal file
BIN
tests/data/cover 4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 121 KiB |
@ -308,8 +308,8 @@ class TestEbookReaderHighlighting(unittest.TestCase):
|
|||||||
self.assertIsNotNone(result_img)
|
self.assertIsNotNone(result_img)
|
||||||
self.assertIsInstance(result_img, PILImage.Image)
|
self.assertIsInstance(result_img, PILImage.Image)
|
||||||
self.assertEqual(result_img.size, test_img.size)
|
self.assertEqual(result_img.size, test_img.size)
|
||||||
# Result should be RGBA for transparency
|
# Result should preserve the input mode
|
||||||
self.assertEqual(result_img.mode, 'RGBA')
|
self.assertEqual(result_img.mode, 'RGB')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user