Add cover page settings
This commit is contained in:
parent
4bfaa63aae
commit
aa02506d4c
@ -994,9 +994,6 @@ class GLWidget(UndoableInteractionMixin, QOpenGLWidget):
|
|||||||
if not hasattr(main_window, 'project'):
|
if not hasattr(main_window, 'project'):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Get page layout with ghosts from project
|
|
||||||
layout_with_ghosts = main_window.project.calculate_page_layout_with_ghosts()
|
|
||||||
|
|
||||||
dpi = main_window.project.working_dpi
|
dpi = main_window.project.working_dpi
|
||||||
|
|
||||||
# Use project's page_spacing_mm setting (default is 10mm = 1cm)
|
# Use project's page_spacing_mm setting (default is 10mm = 1cm)
|
||||||
@ -1011,6 +1008,22 @@ class GLWidget(UndoableInteractionMixin, QOpenGLWidget):
|
|||||||
result = []
|
result = []
|
||||||
current_y = top_margin_px # Initial top offset in pixels (not screen pixels)
|
current_y = top_margin_px # Initial top offset in pixels (not screen pixels)
|
||||||
|
|
||||||
|
# First, render cover if it exists
|
||||||
|
for page in main_window.project.pages:
|
||||||
|
if page.is_cover:
|
||||||
|
result.append(('page', page, current_y))
|
||||||
|
|
||||||
|
# Calculate cover height in pixels
|
||||||
|
page_height_mm = page.layout.size[1]
|
||||||
|
page_height_px = page_height_mm * dpi / 25.4
|
||||||
|
|
||||||
|
# Move to next position (add height + spacing)
|
||||||
|
current_y += page_height_px + spacing_px
|
||||||
|
break # Only one cover allowed
|
||||||
|
|
||||||
|
# Get page layout with ghosts from project (this excludes cover)
|
||||||
|
layout_with_ghosts = main_window.project.calculate_page_layout_with_ghosts()
|
||||||
|
|
||||||
for page_type, page_obj, logical_pos in layout_with_ghosts:
|
for page_type, page_obj, logical_pos in layout_with_ghosts:
|
||||||
if page_type == 'page':
|
if page_type == 'page':
|
||||||
# Regular page (single or double spread)
|
# Regular page (single or double spread)
|
||||||
|
|||||||
@ -62,9 +62,10 @@ class PageOperationsMixin:
|
|||||||
|
|
||||||
page_combo = QComboBox()
|
page_combo = QComboBox()
|
||||||
for i, page in enumerate(self.project.pages):
|
for i, page in enumerate(self.project.pages):
|
||||||
page_label = f"Page {page.page_number}"
|
# Use display name helper
|
||||||
if page.is_double_spread:
|
page_label = self.project.get_page_display_name(page)
|
||||||
page_label += f" (Double Spread: {page.page_number}-{page.page_number + 1})"
|
if page.is_double_spread and not page.is_cover:
|
||||||
|
page_label += f" (Double Spread)"
|
||||||
if page.manually_sized:
|
if page.manually_sized:
|
||||||
page_label += " *"
|
page_label += " *"
|
||||||
page_combo.addItem(page_label, i)
|
page_combo.addItem(page_label, i)
|
||||||
@ -78,6 +79,48 @@ class PageOperationsMixin:
|
|||||||
page_select_group.setLayout(page_select_layout)
|
page_select_group.setLayout(page_select_layout)
|
||||||
layout.addWidget(page_select_group)
|
layout.addWidget(page_select_group)
|
||||||
|
|
||||||
|
# Cover settings group (only show if first page is selected)
|
||||||
|
cover_group = QGroupBox("Cover Settings")
|
||||||
|
cover_layout = QVBoxLayout()
|
||||||
|
|
||||||
|
# Cover checkbox
|
||||||
|
cover_checkbox = QCheckBox("Designate as Cover")
|
||||||
|
cover_checkbox.setToolTip("Mark this page as the book cover with wrap-around front/spine/back")
|
||||||
|
cover_layout.addWidget(cover_checkbox)
|
||||||
|
|
||||||
|
# Paper thickness
|
||||||
|
thickness_layout = QHBoxLayout()
|
||||||
|
thickness_layout.addWidget(QLabel("Paper Thickness:"))
|
||||||
|
thickness_spinbox = QDoubleSpinBox()
|
||||||
|
thickness_spinbox.setRange(0.05, 1.0)
|
||||||
|
thickness_spinbox.setSingleStep(0.05)
|
||||||
|
thickness_spinbox.setValue(self.project.paper_thickness_mm)
|
||||||
|
thickness_spinbox.setSuffix(" mm")
|
||||||
|
thickness_spinbox.setToolTip("Thickness of paper for spine calculation")
|
||||||
|
thickness_layout.addWidget(thickness_spinbox)
|
||||||
|
cover_layout.addLayout(thickness_layout)
|
||||||
|
|
||||||
|
# Bleed margin
|
||||||
|
bleed_layout = QHBoxLayout()
|
||||||
|
bleed_layout.addWidget(QLabel("Bleed Margin:"))
|
||||||
|
bleed_spinbox = QDoubleSpinBox()
|
||||||
|
bleed_spinbox.setRange(0, 10)
|
||||||
|
bleed_spinbox.setSingleStep(0.5)
|
||||||
|
bleed_spinbox.setValue(self.project.cover_bleed_mm)
|
||||||
|
bleed_spinbox.setSuffix(" mm")
|
||||||
|
bleed_spinbox.setToolTip("Extra margin around cover for printing bleed")
|
||||||
|
bleed_layout.addWidget(bleed_spinbox)
|
||||||
|
cover_layout.addLayout(bleed_layout)
|
||||||
|
|
||||||
|
# Calculated spine width display
|
||||||
|
spine_info_label = QLabel()
|
||||||
|
spine_info_label.setStyleSheet("font-size: 9pt; color: #0066cc; padding: 5px;")
|
||||||
|
spine_info_label.setWordWrap(True)
|
||||||
|
cover_layout.addWidget(spine_info_label)
|
||||||
|
|
||||||
|
cover_group.setLayout(cover_layout)
|
||||||
|
layout.addWidget(cover_group)
|
||||||
|
|
||||||
# Page size group
|
# Page size group
|
||||||
size_group = QGroupBox("Page Size")
|
size_group = QGroupBox("Page Size")
|
||||||
size_layout = QVBoxLayout()
|
size_layout = QVBoxLayout()
|
||||||
@ -136,14 +179,62 @@ class PageOperationsMixin:
|
|||||||
# Function to update displayed values when page selection changes
|
# Function to update displayed values when page selection changes
|
||||||
def on_page_changed(index):
|
def on_page_changed(index):
|
||||||
selected_page = self.project.pages[index]
|
selected_page = self.project.pages[index]
|
||||||
# Get base width (accounting for double spreads)
|
|
||||||
if selected_page.is_double_spread:
|
# Show/hide cover settings based on page selection
|
||||||
|
is_first_page = (index == 0)
|
||||||
|
cover_group.setVisible(is_first_page)
|
||||||
|
|
||||||
|
# Update cover checkbox
|
||||||
|
if is_first_page:
|
||||||
|
cover_checkbox.setChecked(selected_page.is_cover)
|
||||||
|
update_spine_info()
|
||||||
|
|
||||||
|
# Get base width (accounting for double spreads and covers)
|
||||||
|
if selected_page.is_cover:
|
||||||
|
# For covers, show the full calculated width
|
||||||
|
display_width = selected_page.layout.size[0]
|
||||||
|
elif selected_page.is_double_spread:
|
||||||
display_width = selected_page.layout.base_width if hasattr(selected_page.layout, 'base_width') else selected_page.layout.size[0] / 2
|
display_width = selected_page.layout.base_width if hasattr(selected_page.layout, 'base_width') else selected_page.layout.size[0] / 2
|
||||||
else:
|
else:
|
||||||
display_width = selected_page.layout.size[0]
|
display_width = selected_page.layout.size[0]
|
||||||
|
|
||||||
width_spinbox.setValue(display_width)
|
width_spinbox.setValue(display_width)
|
||||||
height_spinbox.setValue(selected_page.layout.size[1])
|
height_spinbox.setValue(selected_page.layout.size[1])
|
||||||
|
|
||||||
|
# Disable size editing for covers (auto-calculated)
|
||||||
|
if selected_page.is_cover:
|
||||||
|
width_spinbox.setEnabled(False)
|
||||||
|
height_spinbox.setEnabled(False)
|
||||||
|
set_default_checkbox.setEnabled(False)
|
||||||
|
else:
|
||||||
|
width_spinbox.setEnabled(True)
|
||||||
|
height_spinbox.setEnabled(True)
|
||||||
|
set_default_checkbox.setEnabled(True)
|
||||||
|
|
||||||
|
def update_spine_info():
|
||||||
|
"""Update the spine information display"""
|
||||||
|
if cover_checkbox.isChecked():
|
||||||
|
# Calculate spine width with current settings
|
||||||
|
content_pages = sum(p.get_page_count() for p in self.project.pages if not p.is_cover)
|
||||||
|
import math
|
||||||
|
sheets = math.ceil(content_pages / 4)
|
||||||
|
spine_width = sheets * thickness_spinbox.value() * 2
|
||||||
|
|
||||||
|
page_width = self.project.page_size_mm[0]
|
||||||
|
total_width = (page_width * 2) + spine_width + (bleed_spinbox.value() * 2)
|
||||||
|
|
||||||
|
spine_info_label.setText(
|
||||||
|
f"Cover Layout: Front ({page_width:.0f}mm) + Spine ({spine_width:.2f}mm) + "
|
||||||
|
f"Back ({page_width:.0f}mm) + Bleed ({bleed_spinbox.value():.1f}mm × 2)\n"
|
||||||
|
f"Total Width: {total_width:.1f}mm | Content Pages: {content_pages} | Sheets: {sheets}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
spine_info_label.setText("")
|
||||||
|
|
||||||
|
# Connect signals
|
||||||
|
cover_checkbox.stateChanged.connect(lambda: update_spine_info())
|
||||||
|
thickness_spinbox.valueChanged.connect(lambda: update_spine_info())
|
||||||
|
bleed_spinbox.valueChanged.connect(lambda: update_spine_info())
|
||||||
|
|
||||||
# Connect page selection change
|
# Connect page selection change
|
||||||
page_combo.currentIndexChanged.connect(on_page_changed)
|
page_combo.currentIndexChanged.connect(on_page_changed)
|
||||||
@ -172,33 +263,57 @@ class PageOperationsMixin:
|
|||||||
selected_index = page_combo.currentData()
|
selected_index = page_combo.currentData()
|
||||||
selected_page = self.project.pages[selected_index]
|
selected_page = self.project.pages[selected_index]
|
||||||
|
|
||||||
|
# Update project cover settings
|
||||||
|
self.project.paper_thickness_mm = thickness_spinbox.value()
|
||||||
|
self.project.cover_bleed_mm = bleed_spinbox.value()
|
||||||
|
|
||||||
|
# Handle cover designation (only for first page)
|
||||||
|
if selected_index == 0:
|
||||||
|
was_cover = selected_page.is_cover
|
||||||
|
is_cover = cover_checkbox.isChecked()
|
||||||
|
|
||||||
|
if was_cover != is_cover:
|
||||||
|
selected_page.is_cover = is_cover
|
||||||
|
self.project.has_cover = is_cover
|
||||||
|
|
||||||
|
if is_cover:
|
||||||
|
# Calculate and set cover dimensions
|
||||||
|
self.project.update_cover_dimensions()
|
||||||
|
print(f"Page 1 designated as cover")
|
||||||
|
else:
|
||||||
|
# Restore normal page size
|
||||||
|
selected_page.layout.size = self.project.page_size_mm
|
||||||
|
print(f"Cover removed from page 1")
|
||||||
|
|
||||||
# Get new values
|
# Get new values
|
||||||
width_mm = width_spinbox.value()
|
width_mm = width_spinbox.value()
|
||||||
height_mm = height_spinbox.value()
|
height_mm = height_spinbox.value()
|
||||||
|
|
||||||
# Check if size actually changed
|
# Don't allow manual size changes for covers
|
||||||
# For double spreads, compare with base width
|
if not selected_page.is_cover:
|
||||||
if selected_page.is_double_spread:
|
# Check if size actually changed
|
||||||
old_base_width = selected_page.layout.base_width if hasattr(selected_page.layout, 'base_width') else selected_page.layout.size[0] / 2
|
# For double spreads, compare with base width
|
||||||
old_height = selected_page.layout.size[1]
|
if selected_page.is_double_spread:
|
||||||
size_changed = (old_base_width != width_mm or old_height != height_mm)
|
old_base_width = selected_page.layout.base_width if hasattr(selected_page.layout, 'base_width') else selected_page.layout.size[0] / 2
|
||||||
|
old_height = selected_page.layout.size[1]
|
||||||
if size_changed:
|
size_changed = (old_base_width != width_mm or old_height != height_mm)
|
||||||
# Update double spread
|
|
||||||
selected_page.layout.base_width = width_mm
|
if size_changed:
|
||||||
selected_page.layout.size = (width_mm * 2, height_mm)
|
# Update double spread
|
||||||
selected_page.manually_sized = True
|
selected_page.layout.base_width = width_mm
|
||||||
print(f"Page {selected_page.page_number} (double spread) updated to {width_mm}×{height_mm} mm per page")
|
selected_page.layout.size = (width_mm * 2, height_mm)
|
||||||
else:
|
selected_page.manually_sized = True
|
||||||
old_size = selected_page.layout.size
|
print(f"{self.project.get_page_display_name(selected_page)} (double spread) updated to {width_mm}×{height_mm} mm per page")
|
||||||
size_changed = (old_size != (width_mm, height_mm))
|
else:
|
||||||
|
old_size = selected_page.layout.size
|
||||||
if size_changed:
|
size_changed = (old_size != (width_mm, height_mm))
|
||||||
# Update single page
|
|
||||||
selected_page.layout.size = (width_mm, height_mm)
|
if size_changed:
|
||||||
selected_page.layout.base_width = width_mm
|
# Update single page
|
||||||
selected_page.manually_sized = True
|
selected_page.layout.size = (width_mm, height_mm)
|
||||||
print(f"Page {selected_page.page_number} updated to {width_mm}×{height_mm} mm")
|
selected_page.layout.base_width = width_mm
|
||||||
|
selected_page.manually_sized = True
|
||||||
|
print(f"{self.project.get_page_display_name(selected_page)} updated to {width_mm}×{height_mm} mm")
|
||||||
|
|
||||||
# Update DPI settings
|
# Update DPI settings
|
||||||
self.project.working_dpi = working_dpi_spinbox.value()
|
self.project.working_dpi = working_dpi_spinbox.value()
|
||||||
@ -212,9 +327,13 @@ class PageOperationsMixin:
|
|||||||
self.update_view()
|
self.update_view()
|
||||||
|
|
||||||
# Build status message
|
# Build status message
|
||||||
status_msg = f"Page {selected_page.page_number} size: {width_mm}×{height_mm} mm"
|
page_name = self.project.get_page_display_name(selected_page)
|
||||||
if set_default_checkbox.isChecked():
|
if selected_page.is_cover:
|
||||||
status_msg += " (set as default)"
|
status_msg = f"{page_name} updated"
|
||||||
|
else:
|
||||||
|
status_msg = f"{page_name} size: {width_mm}×{height_mm} mm"
|
||||||
|
if set_default_checkbox.isChecked():
|
||||||
|
status_msg += " (set as default)"
|
||||||
self.show_status(status_msg, 2000)
|
self.show_status(status_msg, 2000)
|
||||||
|
|
||||||
@ribbon_action(
|
@ribbon_action(
|
||||||
|
|||||||
@ -48,8 +48,11 @@ class PDFExporter:
|
|||||||
self.current_pdf_page = 1
|
self.current_pdf_page = 1
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Calculate total pages for progress
|
# Calculate total pages for progress (cover counts as 1)
|
||||||
total_pages = sum(2 if page.is_double_spread else 1 for page in self.project.pages)
|
total_pages = sum(
|
||||||
|
1 if page.is_cover else (2 if page.is_double_spread else 1)
|
||||||
|
for page in self.project.pages
|
||||||
|
)
|
||||||
|
|
||||||
# Get page dimensions from project (in mm)
|
# Get page dimensions from project (in mm)
|
||||||
page_width_mm, page_height_mm = self.project.page_size_mm
|
page_width_mm, page_height_mm = self.project.page_size_mm
|
||||||
@ -64,11 +67,18 @@ class PDFExporter:
|
|||||||
# Process each page
|
# Process each page
|
||||||
pages_processed = 0
|
pages_processed = 0
|
||||||
for page in self.project.pages:
|
for page in self.project.pages:
|
||||||
|
# Get display name for progress
|
||||||
|
page_name = self.project.get_page_display_name(page)
|
||||||
|
|
||||||
if progress_callback:
|
if progress_callback:
|
||||||
progress_callback(pages_processed, total_pages,
|
progress_callback(pages_processed, total_pages,
|
||||||
f"Exporting page {page.page_number}...")
|
f"Exporting {page_name}...")
|
||||||
|
|
||||||
if page.is_double_spread:
|
if page.is_cover:
|
||||||
|
# Export cover as single page with wrap-around design
|
||||||
|
self._export_cover(c, page, page_width_pt, page_height_pt)
|
||||||
|
pages_processed += 1
|
||||||
|
elif page.is_double_spread:
|
||||||
# Ensure spread starts on even page (left page of facing pair)
|
# Ensure spread starts on even page (left page of facing pair)
|
||||||
if self.current_pdf_page % 2 == 1:
|
if self.current_pdf_page % 2 == 1:
|
||||||
# Insert blank page
|
# Insert blank page
|
||||||
@ -98,6 +108,70 @@ class PDFExporter:
|
|||||||
self.warnings.append(f"Export failed: {str(e)}")
|
self.warnings.append(f"Export failed: {str(e)}")
|
||||||
return False, self.warnings
|
return False, self.warnings
|
||||||
|
|
||||||
|
def _export_cover(self, c: canvas.Canvas, page, page_width_pt: float,
|
||||||
|
page_height_pt: float):
|
||||||
|
"""
|
||||||
|
Export a cover page to PDF.
|
||||||
|
Cover has different dimensions (wrap-around: front + spine + back + bleed).
|
||||||
|
"""
|
||||||
|
# Get cover dimensions (already calculated in page.layout.size)
|
||||||
|
cover_width_mm, cover_height_mm = page.layout.size
|
||||||
|
|
||||||
|
# Convert to PDF points
|
||||||
|
cover_width_pt = cover_width_mm * self.MM_TO_POINTS
|
||||||
|
cover_height_pt = cover_height_mm * self.MM_TO_POINTS
|
||||||
|
|
||||||
|
# Create a new page with cover dimensions
|
||||||
|
c.setPageSize((cover_width_pt, cover_height_pt))
|
||||||
|
|
||||||
|
# Render all elements on the cover
|
||||||
|
for element in sorted(page.layout.elements, key=lambda x: x.z_index):
|
||||||
|
self._render_element(c, element, 0, cover_width_pt, cover_height_pt, "Cover")
|
||||||
|
|
||||||
|
# Draw guide lines for front/spine/back zones
|
||||||
|
self._draw_cover_guides(c, cover_width_pt, cover_height_pt)
|
||||||
|
|
||||||
|
c.showPage() # Finish cover page
|
||||||
|
self.current_pdf_page += 1
|
||||||
|
|
||||||
|
# Reset page size for content pages
|
||||||
|
c.setPageSize((page_width_pt, page_height_pt))
|
||||||
|
|
||||||
|
def _draw_cover_guides(self, c: canvas.Canvas, cover_width_pt: float, cover_height_pt: float):
|
||||||
|
"""Draw guide lines for cover zones (front/spine/back)"""
|
||||||
|
from reportlab.lib.colors import lightgrey
|
||||||
|
|
||||||
|
# Calculate zone boundaries
|
||||||
|
bleed_pt = self.project.cover_bleed_mm * self.MM_TO_POINTS
|
||||||
|
page_width_pt = self.project.page_size_mm[0] * self.MM_TO_POINTS
|
||||||
|
spine_width_pt = self.project.calculate_spine_width() * self.MM_TO_POINTS
|
||||||
|
|
||||||
|
# Zone boundaries (from left to right)
|
||||||
|
# Bleed | Back | Spine | Front | Bleed
|
||||||
|
back_start = bleed_pt
|
||||||
|
spine_start = bleed_pt + page_width_pt
|
||||||
|
front_start = bleed_pt + page_width_pt + spine_width_pt
|
||||||
|
front_end = bleed_pt + page_width_pt + spine_width_pt + page_width_pt
|
||||||
|
|
||||||
|
# Draw dashed lines at zone boundaries
|
||||||
|
c.saveState()
|
||||||
|
c.setStrokeColor(lightgrey)
|
||||||
|
c.setDash(3, 3)
|
||||||
|
c.setLineWidth(0.5)
|
||||||
|
|
||||||
|
# Back/Spine boundary
|
||||||
|
c.line(spine_start, 0, spine_start, cover_height_pt)
|
||||||
|
|
||||||
|
# Spine/Front boundary
|
||||||
|
c.line(front_start, 0, front_start, cover_height_pt)
|
||||||
|
|
||||||
|
# Bleed boundaries (outer edges)
|
||||||
|
if bleed_pt > 0:
|
||||||
|
c.line(back_start, 0, back_start, cover_height_pt)
|
||||||
|
c.line(front_end, 0, front_end, cover_height_pt)
|
||||||
|
|
||||||
|
c.restoreState()
|
||||||
|
|
||||||
def _export_single_page(self, c: canvas.Canvas, page, page_width_pt: float,
|
def _export_single_page(self, c: canvas.Canvas, page, page_width_pt: float,
|
||||||
page_height_pt: float):
|
page_height_pt: float):
|
||||||
"""Export a single page to PDF"""
|
"""Export a single page to PDF"""
|
||||||
|
|||||||
@ -3,6 +3,7 @@ Project and page management for pyPhotoAlbum
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import math
|
||||||
from typing import List, Dict, Any, Optional, Tuple
|
from typing import List, Dict, Any, Optional, Tuple
|
||||||
from pyPhotoAlbum.page_layout import PageLayout
|
from pyPhotoAlbum.page_layout import PageLayout
|
||||||
from pyPhotoAlbum.commands import CommandHistory
|
from pyPhotoAlbum.commands import CommandHistory
|
||||||
@ -109,6 +110,12 @@ class Project:
|
|||||||
self.export_dpi = 300 # Default export DPI
|
self.export_dpi = 300 # Default export DPI
|
||||||
self.page_spacing_mm = 10.0 # Default spacing between pages (1cm)
|
self.page_spacing_mm = 10.0 # Default spacing between pages (1cm)
|
||||||
|
|
||||||
|
# Cover configuration
|
||||||
|
self.has_cover = False # Whether project has a cover
|
||||||
|
self.paper_thickness_mm = 0.2 # Paper thickness for spine calculation (default 0.2mm)
|
||||||
|
self.cover_bleed_mm = 0.0 # Bleed margin for cover (default 0mm)
|
||||||
|
self.binding_type = "saddle_stitch" # Binding type for spine calculation
|
||||||
|
|
||||||
# Embedded templates - templates that travel with the project
|
# Embedded templates - templates that travel with the project
|
||||||
self.embedded_templates: Dict[str, Dict[str, Any]] = {}
|
self.embedded_templates: Dict[str, Dict[str, Any]] = {}
|
||||||
|
|
||||||
@ -122,14 +129,113 @@ class Project:
|
|||||||
def add_page(self, page: Page):
|
def add_page(self, page: Page):
|
||||||
"""Add a page to the project"""
|
"""Add a page to the project"""
|
||||||
self.pages.append(page)
|
self.pages.append(page)
|
||||||
|
# Update cover dimensions if we have a cover
|
||||||
|
if self.has_cover and self.pages:
|
||||||
|
self.update_cover_dimensions()
|
||||||
|
|
||||||
def remove_page(self, page: Page):
|
def remove_page(self, page: Page):
|
||||||
"""Remove a page from the project"""
|
"""Remove a page from the project"""
|
||||||
self.pages.remove(page)
|
self.pages.remove(page)
|
||||||
|
# Update cover dimensions if we have a cover
|
||||||
|
if self.has_cover and self.pages:
|
||||||
|
self.update_cover_dimensions()
|
||||||
|
|
||||||
|
def calculate_spine_width(self) -> float:
|
||||||
|
"""
|
||||||
|
Calculate spine width based on page count and paper thickness.
|
||||||
|
|
||||||
|
For saddle stitch binding:
|
||||||
|
- Each sheet = 4 pages (2 pages per side when folded)
|
||||||
|
- Spine width = (Number of sheets × Paper thickness × 2)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Spine width in mm
|
||||||
|
"""
|
||||||
|
if not self.has_cover:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
# Count content pages (excluding cover)
|
||||||
|
content_page_count = sum(
|
||||||
|
page.get_page_count()
|
||||||
|
for page in self.pages
|
||||||
|
if not page.is_cover
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.binding_type == "saddle_stitch":
|
||||||
|
# Calculate number of sheets (each sheet = 4 pages)
|
||||||
|
sheets = math.ceil(content_page_count / 4)
|
||||||
|
# Spine width = sheets × paper thickness × 2 (folded)
|
||||||
|
spine_width = sheets * self.paper_thickness_mm * 2
|
||||||
|
return spine_width
|
||||||
|
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def update_cover_dimensions(self):
|
||||||
|
"""
|
||||||
|
Update cover page dimensions based on current page count and settings.
|
||||||
|
Calculates: Front width + Spine width + Back width + Bleed margins
|
||||||
|
"""
|
||||||
|
if not self.has_cover or not self.pages:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Find cover page (should be first page)
|
||||||
|
cover_page = None
|
||||||
|
for page in self.pages:
|
||||||
|
if page.is_cover:
|
||||||
|
cover_page = page
|
||||||
|
break
|
||||||
|
|
||||||
|
if not cover_page:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get standard page dimensions
|
||||||
|
page_width_mm, page_height_mm = self.page_size_mm
|
||||||
|
|
||||||
|
# Calculate spine width
|
||||||
|
spine_width = self.calculate_spine_width()
|
||||||
|
|
||||||
|
# Calculate cover dimensions
|
||||||
|
# Cover = Front + Spine + Back + Bleed on all sides
|
||||||
|
cover_width = (page_width_mm * 2) + spine_width + (self.cover_bleed_mm * 2)
|
||||||
|
cover_height = page_height_mm + (self.cover_bleed_mm * 2)
|
||||||
|
|
||||||
|
# Update cover page layout
|
||||||
|
cover_page.layout.size = (cover_width, cover_height)
|
||||||
|
cover_page.layout.base_width = page_width_mm # Store base width for reference
|
||||||
|
cover_page.manually_sized = True # Mark as manually sized
|
||||||
|
|
||||||
|
print(f"Cover dimensions updated: {cover_width:.1f} × {cover_height:.1f} mm "
|
||||||
|
f"(Front: {page_width_mm}, Spine: {spine_width:.2f}, Back: {page_width_mm}, "
|
||||||
|
f"Bleed: {self.cover_bleed_mm})")
|
||||||
|
|
||||||
|
def get_page_display_name(self, page: Page) -> str:
|
||||||
|
"""
|
||||||
|
Get display name for a page.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: The page to get the display name for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Display name like "Cover", "Page 1", "Pages 1-2", etc.
|
||||||
|
"""
|
||||||
|
if page.is_cover:
|
||||||
|
return "Cover"
|
||||||
|
|
||||||
|
# Calculate adjusted page number (excluding cover from count)
|
||||||
|
adjusted_num = page.page_number
|
||||||
|
if self.has_cover:
|
||||||
|
# Subtract 1 to account for cover
|
||||||
|
adjusted_num = page.page_number - 1
|
||||||
|
|
||||||
|
if page.is_double_spread:
|
||||||
|
return f"Pages {adjusted_num}-{adjusted_num + 1}"
|
||||||
|
else:
|
||||||
|
return f"Page {adjusted_num}"
|
||||||
|
|
||||||
def calculate_page_layout_with_ghosts(self) -> List[Tuple[str, Any, int]]:
|
def calculate_page_layout_with_ghosts(self) -> List[Tuple[str, Any, int]]:
|
||||||
"""
|
"""
|
||||||
Calculate page layout including ghost pages for alignment.
|
Calculate page layout including ghost pages for alignment.
|
||||||
|
Excludes cover from spread calculations.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of tuples (page_type, page_or_ghost, logical_position)
|
List of tuples (page_type, page_or_ghost, logical_position)
|
||||||
@ -143,6 +249,10 @@ class Project:
|
|||||||
current_position = 1 # Start at position 1 (right page)
|
current_position = 1 # Start at position 1 (right page)
|
||||||
|
|
||||||
for page in self.pages:
|
for page in self.pages:
|
||||||
|
# Skip cover in spread calculations
|
||||||
|
if page.is_cover:
|
||||||
|
# Cover is rendered separately, doesn't participate in spreads
|
||||||
|
continue
|
||||||
# Check if we need a ghost page for alignment
|
# Check if we need a ghost page for alignment
|
||||||
# Ghost pages are needed when a single page would appear on the left
|
# Ghost pages are needed when a single page would appear on the left
|
||||||
# but should be on the right (odd positions)
|
# but should be on the right (odd positions)
|
||||||
@ -198,6 +308,10 @@ class Project:
|
|||||||
"working_dpi": self.working_dpi,
|
"working_dpi": self.working_dpi,
|
||||||
"export_dpi": self.export_dpi,
|
"export_dpi": self.export_dpi,
|
||||||
"page_spacing_mm": self.page_spacing_mm,
|
"page_spacing_mm": self.page_spacing_mm,
|
||||||
|
"has_cover": self.has_cover,
|
||||||
|
"paper_thickness_mm": self.paper_thickness_mm,
|
||||||
|
"cover_bleed_mm": self.cover_bleed_mm,
|
||||||
|
"binding_type": self.binding_type,
|
||||||
"embedded_templates": self.embedded_templates,
|
"embedded_templates": self.embedded_templates,
|
||||||
"pages": [page.serialize() for page in self.pages],
|
"pages": [page.serialize() for page in self.pages],
|
||||||
"history": self.history.serialize(),
|
"history": self.history.serialize(),
|
||||||
@ -215,6 +329,10 @@ class Project:
|
|||||||
self.working_dpi = data.get("working_dpi", 300)
|
self.working_dpi = data.get("working_dpi", 300)
|
||||||
self.export_dpi = data.get("export_dpi", 300)
|
self.export_dpi = data.get("export_dpi", 300)
|
||||||
self.page_spacing_mm = data.get("page_spacing_mm", 10.0)
|
self.page_spacing_mm = data.get("page_spacing_mm", 10.0)
|
||||||
|
self.has_cover = data.get("has_cover", False)
|
||||||
|
self.paper_thickness_mm = data.get("paper_thickness_mm", 0.2)
|
||||||
|
self.cover_bleed_mm = data.get("cover_bleed_mm", 0.0)
|
||||||
|
self.binding_type = data.get("binding_type", "saddle_stitch")
|
||||||
|
|
||||||
# Deserialize embedded templates
|
# Deserialize embedded templates
|
||||||
self.embedded_templates = data.get("embedded_templates", {})
|
self.embedded_templates = data.get("embedded_templates", {})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user