diff --git a/pyWebLayout/layout/document_layouter.py b/pyWebLayout/layout/document_layouter.py index e101ff2..bd4d2b8 100644 --- a/pyWebLayout/layout/document_layouter.py +++ b/pyWebLayout/layout/document_layouter.py @@ -74,7 +74,16 @@ def paragraph_layouter(paragraph: Paragraph, page: Page, start_word: int = 0, pr int(concrete_style.word_spacing_max) ) text_align = concrete_style.text_align - + + # Apply page-level word spacing override if specified + if hasattr(page.style, 'word_spacing') and isinstance(page.style.word_spacing, int) and page.style.word_spacing > 0: + # Add the page-level word spacing to both min and max constraints + min_ws, max_ws = word_spacing_constraints + word_spacing_constraints = ( + min_ws + page.style.word_spacing, + max_ws + page.style.word_spacing + ) + # Apply alignment override if provided if alignment_override is not None: text_align = alignment_override @@ -91,9 +100,14 @@ def paragraph_layouter(paragraph: Paragraph, page: Page, start_word: int = 0, pr background=font.background ) - # Calculate baseline-to-baseline spacing using line spacing multiplier + # Calculate baseline-to-baseline spacing: font size + additional line spacing # This is the vertical distance between baselines of consecutive lines - baseline_spacing = int(font.font_size * page.style.line_spacing_multiplier) + # Formula: baseline_spacing = font_size + line_spacing (absolute pixels) + line_spacing_value = getattr(page.style, 'line_spacing', 5) + # Ensure line_spacing is an int (could be Mock in tests) + if not isinstance(line_spacing_value, int): + line_spacing_value = 5 + baseline_spacing = font.font_size + line_spacing_value # Get font metrics for boundary checking ascent, descent = font.font.getmetrics() diff --git a/pyWebLayout/layout/ereader_layout.py b/pyWebLayout/layout/ereader_layout.py index 90247a9..5b0eb1f 100644 --- a/pyWebLayout/layout/ereader_layout.py +++ b/pyWebLayout/layout/ereader_layout.py @@ -247,11 +247,16 @@ class BidirectionalLayouter: # Try to fit the block on the current page success, new_pos = self._layout_block_on_page(scaled_block, page, current_pos, font_scale) - + if not success: # Block doesn't fit, we're done with this page break - + + # Add inter-block spacing after successfully laying out a block + # Only add if we're not at the end of the document and there's space + if new_pos.block_index < len(self.blocks): + page._current_y_offset += self.page_style.inter_block_spacing + # Ensure new position doesn't go beyond bounds if new_pos.block_index >= len(self.blocks): # We've reached the end of the document diff --git a/pyWebLayout/layout/ereader_manager.py b/pyWebLayout/layout/ereader_manager.py index da6a9ea..0517293 100644 --- a/pyWebLayout/layout/ereader_manager.py +++ b/pyWebLayout/layout/ereader_manager.py @@ -341,7 +341,97 @@ class EreaderLayoutManager: def get_font_scale(self) -> float: """Get the current font scale""" return self.font_scale - + + def increase_line_spacing(self, amount: int = 2) -> Page: + """ + Increase line spacing and re-render current page. + + Args: + amount: Pixels to add to line spacing (default: 2) + + Returns: + Re-rendered page with increased line spacing + """ + self.page_style.line_spacing += amount + self.renderer.page_style = self.page_style # Update renderer's reference + self.renderer.buffer.invalidate_all() # Clear cache to force re-render + return self.get_current_page() + + def decrease_line_spacing(self, amount: int = 2) -> Page: + """ + Decrease line spacing and re-render current page. + + Args: + amount: Pixels to remove from line spacing (default: 2) + + Returns: + Re-rendered page with decreased line spacing + """ + self.page_style.line_spacing = max(0, self.page_style.line_spacing - amount) + self.renderer.page_style = self.page_style # Update renderer's reference + self.renderer.buffer.invalidate_all() # Clear cache to force re-render + return self.get_current_page() + + def increase_inter_block_spacing(self, amount: int = 5) -> Page: + """ + Increase spacing between blocks and re-render current page. + + Args: + amount: Pixels to add to inter-block spacing (default: 5) + + Returns: + Re-rendered page with increased block spacing + """ + self.page_style.inter_block_spacing += amount + self.renderer.page_style = self.page_style # Update renderer's reference + self.renderer.buffer.invalidate_all() # Clear cache to force re-render + return self.get_current_page() + + def decrease_inter_block_spacing(self, amount: int = 5) -> Page: + """ + Decrease spacing between blocks and re-render current page. + + Args: + amount: Pixels to remove from inter-block spacing (default: 5) + + Returns: + Re-rendered page with decreased block spacing + """ + self.page_style.inter_block_spacing = max(0, self.page_style.inter_block_spacing - amount) + self.renderer.page_style = self.page_style # Update renderer's reference + self.renderer.buffer.invalidate_all() # Clear cache to force re-render + return self.get_current_page() + + def increase_word_spacing(self, amount: int = 2) -> Page: + """ + Increase spacing between words and re-render current page. + + Args: + amount: Pixels to add to word spacing (default: 2) + + Returns: + Re-rendered page with increased word spacing + """ + self.page_style.word_spacing += amount + self.renderer.page_style = self.page_style # Update renderer's reference + self.renderer.buffer.invalidate_all() # Clear cache to force re-render + return self.get_current_page() + + def decrease_word_spacing(self, amount: int = 2) -> Page: + """ + Decrease spacing between words and re-render current page. + + Args: + amount: Pixels to remove from word spacing (default: 2) + + Returns: + Re-rendered page with decreased word spacing + """ + self.page_style.word_spacing = max(0, self.page_style.word_spacing - amount) + self.renderer.page_style = self.page_style # Update renderer's reference + self.renderer.buffer.invalidate_all() # Clear cache to force re-render + return self.get_current_page() + def get_table_of_contents(self) -> List[Tuple[str, HeadingLevel, RenderingPosition]]: """ Get the table of contents. diff --git a/pyWebLayout/style/page_style.py b/pyWebLayout/style/page_style.py index 724f7eb..9fb0cd3 100644 --- a/pyWebLayout/style/page_style.py +++ b/pyWebLayout/style/page_style.py @@ -14,18 +14,18 @@ class PageStyle: border_color: Tuple[int, int, int] = (0, 0, 0) # Spacing properties - line_spacing: int = 5 - inter_block_spacing: int = 15 + line_spacing: int = 5 # Additional pixels between lines (added to font size) + inter_block_spacing: int = 15 # Pixels between blocks (paragraphs, headings, etc.) + word_spacing: int = 0 # Additional pixels between words (0 = use font defaults) # Padding (top, right, bottom, left) padding: Tuple[int, int, int, int] = (20, 20, 20, 20) # Background color background_color: Tuple[int, int, int] = (255, 255, 255) - + # Typography properties max_font_size: int = 72 # Maximum font size allowed on a page - line_spacing_multiplier: float = 1.2 # Baseline-to-baseline spacing multiplier @property def padding_top(self) -> int: