Brings ~55 mod-exclusive files to the upstream-based mod/master-resync branch: Activities (migrated to new ActivityManager pattern): - Clock/Time: SetTimeActivity, SetTimezoneOffsetActivity, NtpSyncActivity - Dictionary: DictionaryDefinitionActivity, DictionarySuggestionsActivity, DictionaryWordSelectActivity, LookedUpWordsActivity - Bookmark: EpubReaderBookmarkSelectionActivity - Book management: BookManageMenuActivity, EndOfBookMenuActivity - OPDS: OpdsServerListActivity, OpdsSettingsActivity - Utility: DirectoryPickerActivity, NumericStepperActivity Utilities (unchanged): - BookManager, BookSettings, BookmarkStore, BootNtpSync - Dictionary, LookupHistory, TimeSync, OpdsServerStore Libraries: PlaceholderCover, TableData, ChapterXPathIndexer Scripts: inject_mod_version, generate_book_icon, preview_placeholder_cover Docs: KOReader sync XPath mapping Migration changes: - ActivityWithSubactivity -> Activity base class - Callback constructors -> finish()/setResult() pattern - enterNewActivity() -> startActivityForResult() - Activity::RenderLock&& -> RenderLock&& These files won't compile yet - they reference mod settings and I18n strings that will be added in subsequent phases. Made-with: Cursor
180 lines
6.9 KiB
Python
180 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate a preview of the placeholder cover layout at full cover size (480x800).
|
|
This mirrors the C++ PlaceholderCoverGenerator layout logic for visual verification.
|
|
"""
|
|
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
import sys
|
|
import os
|
|
|
|
# Reuse the book icon generator
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
from generate_book_icon import generate_book_icon
|
|
|
|
|
|
def create_preview(width=480, height=800, title="The Great Gatsby", author="F. Scott Fitzgerald"):
|
|
img = Image.new("1", (width, height), 1) # White
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
# Proportional layout constants
|
|
edge_padding = max(3, width // 48) # ~10px at 480w
|
|
border_width = max(2, width // 96) # ~5px at 480w
|
|
inner_padding = max(4, width // 32) # ~15px at 480w
|
|
|
|
title_scale = 2 if height >= 600 else 1
|
|
author_scale = 2 if height >= 600 else 1 # Author also larger on full covers
|
|
icon_scale = 2 if height >= 600 else (1 if height >= 350 else 0)
|
|
|
|
# Draw border inset from edge
|
|
bx = edge_padding
|
|
by = edge_padding
|
|
bw = width - 2 * edge_padding
|
|
bh = height - 2 * edge_padding
|
|
for i in range(border_width):
|
|
draw.rectangle([bx + i, by + i, bx + bw - 1 - i, by + bh - 1 - i], outline=0)
|
|
|
|
# Content area
|
|
content_x = edge_padding + border_width + inner_padding
|
|
content_y = edge_padding + border_width + inner_padding
|
|
content_w = width - 2 * content_x
|
|
content_h = height - 2 * content_y
|
|
|
|
# Zones
|
|
title_zone_h = content_h * 2 // 3
|
|
author_zone_h = content_h - title_zone_h
|
|
author_zone_y = content_y + title_zone_h
|
|
|
|
# Separator
|
|
sep_w = content_w // 3
|
|
sep_x = content_x + (content_w - sep_w) // 2
|
|
draw.line([sep_x, author_zone_y, sep_x + sep_w, author_zone_y], fill=0)
|
|
|
|
# Use a basic font for the preview (won't match exact Ubuntu metrics, but shows layout)
|
|
try:
|
|
title_font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 12 * title_scale)
|
|
author_font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", 10 * author_scale)
|
|
except (OSError, IOError):
|
|
title_font = ImageFont.load_default()
|
|
author_font = ImageFont.load_default()
|
|
|
|
# Icon dimensions (needed for title text wrapping)
|
|
icon_w_px = 48 * icon_scale if icon_scale > 0 else 0
|
|
icon_h_px = 48 * icon_scale if icon_scale > 0 else 0
|
|
icon_gap = max(8, width // 40) if icon_scale > 0 else 0
|
|
title_text_w = content_w - icon_w_px - icon_gap # Title wraps in narrower area beside icon
|
|
|
|
# Wrap title (within the narrower area to the right of the icon)
|
|
title_lines = []
|
|
words = title.split()
|
|
current_line = ""
|
|
for word in words:
|
|
test = f"{current_line} {word}".strip()
|
|
bbox = draw.textbbox((0, 0), test, font=title_font)
|
|
if bbox[2] - bbox[0] <= title_text_w:
|
|
current_line = test
|
|
else:
|
|
if current_line:
|
|
title_lines.append(current_line)
|
|
current_line = word
|
|
if current_line:
|
|
title_lines.append(current_line)
|
|
title_lines = title_lines[:5]
|
|
|
|
# Line spacing: 75% of advanceY (tighter so 2-3 lines fit within icon height)
|
|
title_line_h = 29 * title_scale * 3 // 4 # Based on C++ ubuntu_12_bold advanceY
|
|
|
|
# Measure actual single-line height from the PIL font for accurate centering
|
|
sample_bbox = draw.textbbox((0, 0), "Ag", font=title_font) # Tall + descender chars
|
|
single_line_visual_h = sample_bbox[3] - sample_bbox[1]
|
|
|
|
# Visual height: line spacing between lines + actual height of last line's glyphs
|
|
num_title_lines = len(title_lines)
|
|
title_visual_h = (num_title_lines - 1) * title_line_h + single_line_visual_h if num_title_lines > 0 else 0
|
|
title_block_h = max(icon_h_px, title_visual_h)
|
|
|
|
title_start_y = content_y + (title_zone_h - title_block_h) // 2
|
|
if title_start_y < content_y:
|
|
title_start_y = content_y
|
|
|
|
# If title fits within icon height, center it vertically against the icon.
|
|
# Otherwise top-align so extra lines overflow below.
|
|
icon_y = title_start_y
|
|
if icon_h_px > 0 and title_visual_h <= icon_h_px:
|
|
title_text_y = title_start_y + (icon_h_px - title_visual_h) // 2
|
|
else:
|
|
title_text_y = title_start_y
|
|
|
|
# Horizontal centering: measure widest title line, center icon+gap+text block
|
|
max_title_line_w = 0
|
|
for line in title_lines:
|
|
bbox = draw.textbbox((0, 0), line, font=title_font)
|
|
w = bbox[2] - bbox[0]
|
|
if w > max_title_line_w:
|
|
max_title_line_w = w
|
|
title_block_w = icon_w_px + icon_gap + max_title_line_w
|
|
title_block_x = content_x + (content_w - title_block_w) // 2
|
|
|
|
# Draw icon
|
|
if icon_scale > 0:
|
|
icon_img = generate_book_icon(48)
|
|
scaled_icon = icon_img.resize((icon_w_px, icon_h_px), Image.NEAREST)
|
|
for iy in range(scaled_icon.height):
|
|
for ix in range(scaled_icon.width):
|
|
if not scaled_icon.getpixel((ix, iy)):
|
|
img.putpixel((title_block_x + ix, icon_y + iy), 0)
|
|
|
|
# Draw title (to the right of the icon)
|
|
title_text_x = title_block_x + icon_w_px + icon_gap
|
|
current_y = title_text_y
|
|
for line in title_lines:
|
|
draw.text((title_text_x, current_y), line, fill=0, font=title_font)
|
|
current_y += title_line_h
|
|
|
|
# Wrap author
|
|
author_lines = []
|
|
words = author.split()
|
|
current_line = ""
|
|
for word in words:
|
|
test = f"{current_line} {word}".strip()
|
|
bbox = draw.textbbox((0, 0), test, font=author_font)
|
|
if bbox[2] - bbox[0] <= content_w:
|
|
current_line = test
|
|
else:
|
|
if current_line:
|
|
author_lines.append(current_line)
|
|
current_line = word
|
|
if current_line:
|
|
author_lines.append(current_line)
|
|
author_lines = author_lines[:3]
|
|
|
|
# Draw author centered in bottom 1/3
|
|
author_line_h = 24 * author_scale # Ubuntu 10 regular advanceY ~24
|
|
author_block_h = len(author_lines) * author_line_h
|
|
author_start_y = author_zone_y + (author_zone_h - author_block_h) // 2
|
|
|
|
for line in author_lines:
|
|
bbox = draw.textbbox((0, 0), line, font=author_font)
|
|
line_w = bbox[2] - bbox[0]
|
|
line_x = content_x + (content_w - line_w) // 2
|
|
draw.text((line_x, author_start_y), line, fill=0, font=author_font)
|
|
author_start_y += author_line_h
|
|
|
|
return img
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Full cover
|
|
img = create_preview(480, 800, "A Really Long Book Title That Should Wrap", "Jane Doe")
|
|
img.save("mod/preview_cover_480x800.png")
|
|
print("Saved mod/preview_cover_480x800.png", file=sys.stderr)
|
|
|
|
# Medium thumbnail
|
|
img2 = create_preview(240, 400, "A Really Long Book Title That Should Wrap", "Jane Doe")
|
|
img2.save("mod/preview_thumb_240x400.png")
|
|
print("Saved mod/preview_thumb_240x400.png", file=sys.stderr)
|
|
|
|
# Small thumbnail
|
|
img3 = create_preview(136, 226, "A Really Long Book Title", "Jane Doe")
|
|
img3.save("mod/preview_thumb_136x226.png")
|
|
print("Saved mod/preview_thumb_136x226.png", file=sys.stderr)
|