This commit is contained in:
parent
9ba35d2fa8
commit
37505d3dcc
@ -53,7 +53,15 @@ def paragraph_layouter(paragraph: Paragraph, page: Page, start_word: int = 0, pr
|
|||||||
text_align = Alignment.LEFT # Default alignment
|
text_align = Alignment.LEFT # Default alignment
|
||||||
else:
|
else:
|
||||||
# paragraph.style is an AbstractStyle, resolve it
|
# paragraph.style is an AbstractStyle, resolve it
|
||||||
rendering_context = RenderingContext(base_font_size=paragraph.style.font_size)
|
# Ensure font_size is an int (it could be a FontSize enum)
|
||||||
|
from pyWebLayout.style.abstract_style import FontSize
|
||||||
|
if isinstance(paragraph.style.font_size, FontSize):
|
||||||
|
# Use a default base font size, the resolver will handle the semantic size
|
||||||
|
base_font_size = 16
|
||||||
|
else:
|
||||||
|
base_font_size = int(paragraph.style.font_size)
|
||||||
|
|
||||||
|
rendering_context = RenderingContext(base_font_size=base_font_size)
|
||||||
style_resolver = StyleResolver(rendering_context)
|
style_resolver = StyleResolver(rendering_context)
|
||||||
style_registry = ConcreteStyleRegistry(style_resolver)
|
style_registry = ConcreteStyleRegistry(style_resolver)
|
||||||
concrete_style = style_registry.get_concrete_style(paragraph.style)
|
concrete_style = style_registry.get_concrete_style(paragraph.style)
|
||||||
|
|||||||
@ -159,6 +159,8 @@ class StyleResolver:
|
|||||||
# Resolve each property
|
# Resolve each property
|
||||||
font_path = self._resolve_font_path(abstract_style.font_family)
|
font_path = self._resolve_font_path(abstract_style.font_family)
|
||||||
font_size = self._resolve_font_size(abstract_style.font_size)
|
font_size = self._resolve_font_size(abstract_style.font_size)
|
||||||
|
# Ensure font_size is always an int before using in arithmetic
|
||||||
|
font_size = int(font_size)
|
||||||
color = self._resolve_color(abstract_style.color)
|
color = self._resolve_color(abstract_style.color)
|
||||||
background_color = self._resolve_background_color(abstract_style.background_color)
|
background_color = self._resolve_background_color(abstract_style.background_color)
|
||||||
line_height = self._resolve_line_height(abstract_style.line_height)
|
line_height = self._resolve_line_height(abstract_style.line_height)
|
||||||
@ -166,7 +168,7 @@ class StyleResolver:
|
|||||||
word_spacing = self._resolve_word_spacing(abstract_style.word_spacing, font_size)
|
word_spacing = self._resolve_word_spacing(abstract_style.word_spacing, font_size)
|
||||||
word_spacing_min = self._resolve_word_spacing(abstract_style.word_spacing_min, font_size)
|
word_spacing_min = self._resolve_word_spacing(abstract_style.word_spacing_min, font_size)
|
||||||
word_spacing_max = self._resolve_word_spacing(abstract_style.word_spacing_max, font_size)
|
word_spacing_max = self._resolve_word_spacing(abstract_style.word_spacing_max, font_size)
|
||||||
min_hyphenation_width = max(font_size * 4, 32) # At least 32 pixels
|
min_hyphenation_width = max(int(font_size) * 4, 32) # At least 32 pixels
|
||||||
|
|
||||||
# Apply default logic for word spacing constraints
|
# Apply default logic for word spacing constraints
|
||||||
if word_spacing_min == 0.0 and word_spacing_max == 0.0:
|
if word_spacing_min == 0.0 and word_spacing_max == 0.0:
|
||||||
@ -223,13 +225,21 @@ class StyleResolver:
|
|||||||
|
|
||||||
def _resolve_font_size(self, font_size: Union[FontSize, int]) -> int:
|
def _resolve_font_size(self, font_size: Union[FontSize, int]) -> int:
|
||||||
"""Resolve font size to actual pixel/point size."""
|
"""Resolve font size to actual pixel/point size."""
|
||||||
if isinstance(font_size, int):
|
# Ensure we handle FontSize enums properly
|
||||||
# Already a concrete size, apply scaling
|
if isinstance(font_size, FontSize):
|
||||||
base_size = font_size
|
|
||||||
else:
|
|
||||||
# Semantic size, convert to multiplier
|
# Semantic size, convert to multiplier
|
||||||
multiplier = self._semantic_font_sizes.get(font_size, 1.0)
|
multiplier = self._semantic_font_sizes.get(font_size, 1.0)
|
||||||
base_size = int(self.context.base_font_size * multiplier)
|
base_size = int(self.context.base_font_size * multiplier)
|
||||||
|
elif isinstance(font_size, int):
|
||||||
|
# Already a concrete size, apply scaling
|
||||||
|
base_size = font_size
|
||||||
|
else:
|
||||||
|
# Fallback for any other type - try to convert to int
|
||||||
|
try:
|
||||||
|
base_size = int(font_size)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
# If conversion fails, use default
|
||||||
|
base_size = self.context.base_font_size
|
||||||
|
|
||||||
# Apply global font scaling
|
# Apply global font scaling
|
||||||
final_size = int(base_size * self.context.font_scale_factor)
|
final_size = int(base_size * self.context.font_scale_factor)
|
||||||
@ -238,7 +248,8 @@ class StyleResolver:
|
|||||||
if self.context.large_text:
|
if self.context.large_text:
|
||||||
final_size = int(final_size * 1.2)
|
final_size = int(final_size * 1.2)
|
||||||
|
|
||||||
return max(final_size, 8) # Minimum 8pt font
|
# Ensure we always return an int, minimum 8pt font
|
||||||
|
return max(int(final_size), 8)
|
||||||
|
|
||||||
def _resolve_color(self, color: Union[str, Tuple[int, int, int]]) -> Tuple[int, int, int]:
|
def _resolve_color(self, color: Union[str, Tuple[int, int, int]]) -> Tuple[int, int, int]:
|
||||||
"""Resolve color to RGB tuple."""
|
"""Resolve color to RGB tuple."""
|
||||||
|
|||||||
@ -3,6 +3,10 @@ from PIL import ImageFont
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Tuple, Union, Optional
|
from typing import Tuple, Union, Optional
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Set up logging for font loading
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FontWeight(Enum):
|
class FontWeight(Enum):
|
||||||
@ -71,29 +75,46 @@ class Font:
|
|||||||
# Navigate to the assets/fonts directory
|
# Navigate to the assets/fonts directory
|
||||||
assets_dir = os.path.join(os.path.dirname(current_dir), 'assets', 'fonts')
|
assets_dir = os.path.join(os.path.dirname(current_dir), 'assets', 'fonts')
|
||||||
bundled_font_path = os.path.join(assets_dir, 'DejaVuSans.ttf')
|
bundled_font_path = os.path.join(assets_dir, 'DejaVuSans.ttf')
|
||||||
return bundled_font_path if os.path.exists(bundled_font_path) else None
|
|
||||||
|
logger.debug(f"Font loading: current_dir = {current_dir}")
|
||||||
|
logger.debug(f"Font loading: assets_dir = {assets_dir}")
|
||||||
|
logger.debug(f"Font loading: bundled_font_path = {bundled_font_path}")
|
||||||
|
logger.debug(f"Font loading: bundled font exists = {os.path.exists(bundled_font_path)}")
|
||||||
|
|
||||||
|
if os.path.exists(bundled_font_path):
|
||||||
|
logger.info(f"Found bundled font at: {bundled_font_path}")
|
||||||
|
return bundled_font_path
|
||||||
|
else:
|
||||||
|
logger.warning(f"Bundled font not found at: {bundled_font_path}")
|
||||||
|
return None
|
||||||
|
|
||||||
def _load_font(self):
|
def _load_font(self):
|
||||||
"""Load the font using PIL's ImageFont with consistent bundled font"""
|
"""Load the font using PIL's ImageFont with consistent bundled font"""
|
||||||
try:
|
try:
|
||||||
if self._font_path:
|
if self._font_path:
|
||||||
# Use specified font path
|
# Use specified font path
|
||||||
|
logger.info(f"Loading font from specified path: {self._font_path}")
|
||||||
self._font = ImageFont.truetype(
|
self._font = ImageFont.truetype(
|
||||||
self._font_path,
|
self._font_path,
|
||||||
self._font_size
|
self._font_size
|
||||||
)
|
)
|
||||||
|
logger.info(f"Successfully loaded font from: {self._font_path}")
|
||||||
else:
|
else:
|
||||||
# Use bundled font for consistency across environments
|
# Use bundled font for consistency across environments
|
||||||
bundled_font_path = self._get_bundled_font_path()
|
bundled_font_path = self._get_bundled_font_path()
|
||||||
|
|
||||||
if bundled_font_path:
|
if bundled_font_path:
|
||||||
|
logger.info(f"Loading bundled font from: {bundled_font_path}")
|
||||||
self._font = ImageFont.truetype(bundled_font_path, self._font_size)
|
self._font = ImageFont.truetype(bundled_font_path, self._font_size)
|
||||||
|
logger.info(f"Successfully loaded bundled font at size {self._font_size}")
|
||||||
else:
|
else:
|
||||||
# Only fall back to PIL's default font if bundled font is not available
|
# Only fall back to PIL's default font if bundled font is not available
|
||||||
|
logger.warning(f"Bundled font not available, falling back to PIL default font")
|
||||||
self._font = ImageFont.load_default()
|
self._font = ImageFont.load_default()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Ultimate fallback to default font
|
# Ultimate fallback to default font
|
||||||
|
logger.error(f"Failed to load font: {e}, falling back to PIL default font")
|
||||||
self._font = ImageFont.load_default()
|
self._font = ImageFont.load_default()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@ -28,6 +28,11 @@ class TestDocumentLayouter:
|
|||||||
self.mock_page.can_fit_line = Mock(return_value=True)
|
self.mock_page.can_fit_line = Mock(return_value=True)
|
||||||
self.mock_page.add_child = Mock()
|
self.mock_page.add_child = Mock()
|
||||||
|
|
||||||
|
# Create mock page style with all required numeric properties
|
||||||
|
self.mock_page.style = Mock()
|
||||||
|
self.mock_page.style.max_font_size = 72 # Reasonable maximum font size
|
||||||
|
self.mock_page.style.line_spacing_multiplier = 1.2 # Standard line spacing
|
||||||
|
|
||||||
# Create mock style resolver
|
# Create mock style resolver
|
||||||
self.mock_style_resolver = Mock()
|
self.mock_style_resolver = Mock()
|
||||||
self.mock_page.style_resolver = self.mock_style_resolver
|
self.mock_page.style_resolver = self.mock_style_resolver
|
||||||
@ -51,12 +56,20 @@ class TestDocumentLayouter:
|
|||||||
self.mock_concrete_style.word_spacing_max = 8.0
|
self.mock_concrete_style.word_spacing_max = 8.0
|
||||||
self.mock_concrete_style.text_align = "left"
|
self.mock_concrete_style.text_align = "left"
|
||||||
|
|
||||||
# Create mock font that returns proper metrics
|
# Create mock font that returns proper numeric metrics (not Mock objects)
|
||||||
mock_font = Mock()
|
mock_font = Mock()
|
||||||
mock_font.getmetrics.return_value = (12, 4) # (ascent, descent)
|
# CRITICAL: getmetrics() must return actual numeric values, not Mock objects
|
||||||
|
# This prevents "TypeError: '>' not supported between instances of 'Mock' and 'Mock'"
|
||||||
|
mock_font.getmetrics.return_value = (12, 4) # (ascent, descent) as actual integers
|
||||||
mock_font.font = mock_font # For accessing .font property
|
mock_font.font = mock_font # For accessing .font property
|
||||||
|
|
||||||
self.mock_concrete_style.create_font = Mock()
|
# Create mock font object that can be used by create_font
|
||||||
|
mock_font_instance = Mock()
|
||||||
|
mock_font_instance.font = mock_font
|
||||||
|
mock_font_instance.font_size = 16
|
||||||
|
mock_font_instance.colour = (0, 0, 0)
|
||||||
|
mock_font_instance.background = (255, 255, 255, 0)
|
||||||
|
self.mock_concrete_style.create_font = Mock(return_value=mock_font_instance)
|
||||||
|
|
||||||
# Update mock words to have proper style with font
|
# Update mock words to have proper style with font
|
||||||
for word in self.mock_words:
|
for word in self.mock_words:
|
||||||
@ -66,11 +79,15 @@ class TestDocumentLayouter:
|
|||||||
word.style.colour = (0, 0, 0)
|
word.style.colour = (0, 0, 0)
|
||||||
word.style.background = None
|
word.style.background = None
|
||||||
|
|
||||||
|
@patch('pyWebLayout.layout.document_layouter.StyleResolver')
|
||||||
@patch('pyWebLayout.layout.document_layouter.ConcreteStyleRegistry')
|
@patch('pyWebLayout.layout.document_layouter.ConcreteStyleRegistry')
|
||||||
@patch('pyWebLayout.layout.document_layouter.Line')
|
@patch('pyWebLayout.layout.document_layouter.Line')
|
||||||
def test_paragraph_layouter_basic_flow(self, mock_line_class, mock_style_registry_class):
|
def test_paragraph_layouter_basic_flow(self, mock_line_class, mock_style_registry_class, mock_style_resolver_class):
|
||||||
"""Test basic paragraph layouter functionality."""
|
"""Test basic paragraph layouter functionality."""
|
||||||
# Setup mocks
|
# Setup mocks for StyleResolver and ConcreteStyleRegistry
|
||||||
|
mock_style_resolver = Mock()
|
||||||
|
mock_style_resolver_class.return_value = mock_style_resolver
|
||||||
|
|
||||||
mock_style_registry = Mock()
|
mock_style_registry = Mock()
|
||||||
mock_style_registry_class.return_value = mock_style_registry
|
mock_style_registry_class.return_value = mock_style_registry
|
||||||
mock_style_registry.get_concrete_style.return_value = self.mock_concrete_style
|
mock_style_registry.get_concrete_style.return_value = self.mock_concrete_style
|
||||||
@ -88,8 +105,9 @@ class TestDocumentLayouter:
|
|||||||
assert failed_word_index is None
|
assert failed_word_index is None
|
||||||
assert remaining_pretext is None
|
assert remaining_pretext is None
|
||||||
|
|
||||||
# Verify style registry was used correctly
|
# Verify StyleResolver and ConcreteStyleRegistry were created correctly
|
||||||
mock_style_registry_class.assert_called_once_with(self.mock_page.style_resolver)
|
mock_style_resolver_class.assert_called_once()
|
||||||
|
mock_style_registry_class.assert_called_once_with(mock_style_resolver)
|
||||||
mock_style_registry.get_concrete_style.assert_called_once_with(self.mock_paragraph.style)
|
mock_style_registry.get_concrete_style.assert_called_once_with(self.mock_paragraph.style)
|
||||||
|
|
||||||
# Verify Line was created with correct spacing constraints
|
# Verify Line was created with correct spacing constraints
|
||||||
@ -98,16 +116,27 @@ class TestDocumentLayouter:
|
|||||||
call_args = mock_line_class.call_args
|
call_args = mock_line_class.call_args
|
||||||
assert call_args[1]['spacing'] == expected_spacing
|
assert call_args[1]['spacing'] == expected_spacing
|
||||||
|
|
||||||
|
@patch('pyWebLayout.layout.document_layouter.StyleResolver')
|
||||||
@patch('pyWebLayout.layout.document_layouter.ConcreteStyleRegistry')
|
@patch('pyWebLayout.layout.document_layouter.ConcreteStyleRegistry')
|
||||||
@patch('pyWebLayout.layout.document_layouter.Line')
|
@patch('pyWebLayout.layout.document_layouter.Line')
|
||||||
def test_paragraph_layouter_word_spacing_constraints_extraction(self, mock_line_class, mock_style_registry_class):
|
def test_paragraph_layouter_word_spacing_constraints_extraction(self, mock_line_class, mock_style_registry_class, mock_style_resolver_class):
|
||||||
"""Test that word spacing constraints are correctly extracted from style."""
|
"""Test that word spacing constraints are correctly extracted from style."""
|
||||||
# Create concrete style with specific constraints
|
# Create concrete style with specific constraints
|
||||||
concrete_style = Mock()
|
concrete_style = Mock()
|
||||||
concrete_style.word_spacing_min = 5.5
|
concrete_style.word_spacing_min = 5.5
|
||||||
concrete_style.word_spacing_max = 15.2
|
concrete_style.word_spacing_max = 15.2
|
||||||
concrete_style.text_align = "justify"
|
concrete_style.text_align = "justify"
|
||||||
concrete_style.create_font = Mock()
|
|
||||||
|
# Create a mock font that concrete_style.create_font returns
|
||||||
|
mock_font = Mock()
|
||||||
|
mock_font.font = Mock()
|
||||||
|
mock_font.font.getmetrics.return_value = (12, 4)
|
||||||
|
mock_font.font_size = 16
|
||||||
|
concrete_style.create_font = Mock(return_value=mock_font)
|
||||||
|
|
||||||
|
# Setup StyleResolver and ConcreteStyleRegistry mocks
|
||||||
|
mock_style_resolver = Mock()
|
||||||
|
mock_style_resolver_class.return_value = mock_style_resolver
|
||||||
|
|
||||||
mock_style_registry = Mock()
|
mock_style_registry = Mock()
|
||||||
mock_style_registry_class.return_value = mock_style_registry
|
mock_style_registry_class.return_value = mock_style_registry
|
||||||
@ -252,39 +281,51 @@ class TestDocumentLayouter:
|
|||||||
)
|
)
|
||||||
assert result == (True, None, None)
|
assert result == (True, None, None)
|
||||||
|
|
||||||
@patch('pyWebLayout.layout.document_layouter.paragraph_layouter')
|
def test_document_layouter_layout_document_success(self):
|
||||||
def test_document_layouter_layout_document_success(self, mock_paragraph_layouter):
|
|
||||||
"""Test DocumentLayouter.layout_document with successful layout."""
|
"""Test DocumentLayouter.layout_document with successful layout."""
|
||||||
mock_paragraph_layouter.return_value = (True, None, None)
|
from pyWebLayout.abstract import Paragraph
|
||||||
|
|
||||||
paragraphs = [self.mock_paragraph, Mock(), Mock()]
|
# Create Mock paragraphs that pass isinstance checks
|
||||||
|
paragraphs = [
|
||||||
|
Mock(spec=Paragraph),
|
||||||
|
Mock(spec=Paragraph),
|
||||||
|
Mock(spec=Paragraph)
|
||||||
|
]
|
||||||
|
|
||||||
with patch('pyWebLayout.layout.document_layouter.ConcreteStyleRegistry'):
|
with patch('pyWebLayout.layout.document_layouter.ConcreteStyleRegistry'):
|
||||||
layouter = DocumentLayouter(self.mock_page)
|
layouter = DocumentLayouter(self.mock_page)
|
||||||
|
|
||||||
|
# Mock the layout_paragraph method to return success
|
||||||
|
layouter.layout_paragraph = Mock(return_value=(True, None, None))
|
||||||
|
|
||||||
result = layouter.layout_document(paragraphs)
|
result = layouter.layout_document(paragraphs)
|
||||||
|
|
||||||
assert result is True
|
assert result is True
|
||||||
assert mock_paragraph_layouter.call_count == 3
|
assert layouter.layout_paragraph.call_count == 3
|
||||||
|
|
||||||
@patch('pyWebLayout.layout.document_layouter.paragraph_layouter')
|
def test_document_layouter_layout_document_failure(self):
|
||||||
def test_document_layouter_layout_document_failure(self, mock_paragraph_layouter):
|
|
||||||
"""Test DocumentLayouter.layout_document with layout failure."""
|
"""Test DocumentLayouter.layout_document with layout failure."""
|
||||||
# First paragraph succeeds, second fails
|
from pyWebLayout.abstract import Paragraph
|
||||||
mock_paragraph_layouter.side_effect = [
|
|
||||||
(True, None, None), # First paragraph succeeds
|
|
||||||
(False, 3, None), # Second paragraph fails
|
|
||||||
]
|
|
||||||
|
|
||||||
paragraphs = [self.mock_paragraph, Mock()]
|
# Create Mock paragraphs that pass isinstance checks
|
||||||
|
paragraphs = [
|
||||||
|
Mock(spec=Paragraph),
|
||||||
|
Mock(spec=Paragraph)
|
||||||
|
]
|
||||||
|
|
||||||
with patch('pyWebLayout.layout.document_layouter.ConcreteStyleRegistry'):
|
with patch('pyWebLayout.layout.document_layouter.ConcreteStyleRegistry'):
|
||||||
layouter = DocumentLayouter(self.mock_page)
|
layouter = DocumentLayouter(self.mock_page)
|
||||||
|
|
||||||
|
# Mock the layout_paragraph method: first succeeds, second fails
|
||||||
|
layouter.layout_paragraph = Mock(side_effect=[
|
||||||
|
(True, None, None), # First paragraph succeeds
|
||||||
|
(False, 3, None), # Second paragraph fails
|
||||||
|
])
|
||||||
|
|
||||||
result = layouter.layout_document(paragraphs)
|
result = layouter.layout_document(paragraphs)
|
||||||
|
|
||||||
assert result is False
|
assert result is False
|
||||||
assert mock_paragraph_layouter.call_count == 2
|
assert layouter.layout_paragraph.call_count == 2
|
||||||
|
|
||||||
def test_real_style_integration(self):
|
def test_real_style_integration(self):
|
||||||
"""Test integration with real style system."""
|
"""Test integration with real style system."""
|
||||||
|
|||||||
@ -11,6 +11,8 @@ from unittest.mock import Mock, patch
|
|||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
from pyWebLayout.layout.document_layouter import paragraph_layouter, DocumentLayouter
|
from pyWebLayout.layout.document_layouter import paragraph_layouter, DocumentLayouter
|
||||||
from pyWebLayout.style.abstract_style import AbstractStyle
|
from pyWebLayout.style.abstract_style import AbstractStyle
|
||||||
@ -21,13 +23,42 @@ from pyWebLayout.concrete.page import Page
|
|||||||
from pyWebLayout.concrete.text import Line, Text
|
from pyWebLayout.concrete.text import Line, Text
|
||||||
from pyWebLayout.abstract.inline import Word
|
from pyWebLayout.abstract.inline import Word
|
||||||
|
|
||||||
|
# Enable logging to see font loading messages
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_bundled_font_available():
|
||||||
|
"""Verify that the bundled font is available for integration tests."""
|
||||||
|
# Get the bundled font path
|
||||||
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
# Navigate up to pyWebLayout root, then to assets/fonts
|
||||||
|
project_root = os.path.dirname(os.path.dirname(current_dir))
|
||||||
|
bundled_font_path = os.path.join(project_root, 'pyWebLayout', 'assets', 'fonts', 'DejaVuSans.ttf')
|
||||||
|
|
||||||
|
logger.info(f"Integration tests checking for bundled font at: {bundled_font_path}")
|
||||||
|
|
||||||
|
if not os.path.exists(bundled_font_path):
|
||||||
|
pytest.fail(
|
||||||
|
f"INTEGRATION TEST FAILURE: Bundled font not found at {bundled_font_path}\n"
|
||||||
|
f"Integration tests require the bundled font to ensure consistent behavior.\n"
|
||||||
|
f"This likely means the font was not included in the package build."
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(f"Bundled font found at: {bundled_font_path}")
|
||||||
|
return bundled_font_path
|
||||||
|
|
||||||
|
|
||||||
class MockWord(Word):
|
class MockWord(Word):
|
||||||
"""A simple mock word that extends the real Word class."""
|
"""A simple mock word that extends the real Word class."""
|
||||||
|
|
||||||
def __init__(self, text, style=None):
|
def __init__(self, text, style=None):
|
||||||
if style is None:
|
if style is None:
|
||||||
|
# Integration tests MUST use the bundled font for consistency
|
||||||
style = Font(font_size=16)
|
style = Font(font_size=16)
|
||||||
|
# Verify the font loaded properly
|
||||||
|
if style.font.path is None:
|
||||||
|
logger.warning("Font loaded without explicit path - may be using PIL default")
|
||||||
# Initialize the base Word with required parameters
|
# Initialize the base Word with required parameters
|
||||||
super().__init__(text, style)
|
super().__init__(text, style)
|
||||||
self._concrete_texts = []
|
self._concrete_texts = []
|
||||||
@ -73,6 +104,11 @@ class MockParagraph:
|
|||||||
class TestDocumentLayouterIntegration:
|
class TestDocumentLayouterIntegration:
|
||||||
"""Integration tests using real components."""
|
"""Integration tests using real components."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setup_class(cls):
|
||||||
|
"""Verify bundled font is available before running any tests."""
|
||||||
|
verify_bundled_font_available()
|
||||||
|
|
||||||
def test_single_page_layout_with_real_components(self):
|
def test_single_page_layout_with_real_components(self):
|
||||||
"""Test layout on a single page using real Line and Text objects."""
|
"""Test layout on a single page using real Line and Text objects."""
|
||||||
# Create a real page that can fit content
|
# Create a real page that can fit content
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user