@@ -53,7 +53,15 @@ def paragraph_layouter(paragraph: Paragraph, page: Page, start_word: int = 0, pr
|
||||
text_align = Alignment.LEFT # Default alignment
|
||||
else:
|
||||
# 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_registry = ConcreteStyleRegistry(style_resolver)
|
||||
concrete_style = style_registry.get_concrete_style(paragraph.style)
|
||||
|
||||
@@ -159,6 +159,8 @@ class StyleResolver:
|
||||
# Resolve each property
|
||||
font_path = self._resolve_font_path(abstract_style.font_family)
|
||||
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)
|
||||
background_color = self._resolve_background_color(abstract_style.background_color)
|
||||
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_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)
|
||||
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
|
||||
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:
|
||||
"""Resolve font size to actual pixel/point size."""
|
||||
if isinstance(font_size, int):
|
||||
# Already a concrete size, apply scaling
|
||||
base_size = font_size
|
||||
else:
|
||||
# Ensure we handle FontSize enums properly
|
||||
if isinstance(font_size, FontSize):
|
||||
# Semantic size, convert to multiplier
|
||||
multiplier = self._semantic_font_sizes.get(font_size, 1.0)
|
||||
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
|
||||
final_size = int(base_size * self.context.font_scale_factor)
|
||||
@@ -238,7 +248,8 @@ class StyleResolver:
|
||||
if self.context.large_text:
|
||||
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]:
|
||||
"""Resolve color to RGB tuple."""
|
||||
|
||||
@@ -3,6 +3,10 @@ from PIL import ImageFont
|
||||
from enum import Enum
|
||||
from typing import Tuple, Union, Optional
|
||||
import os
|
||||
import logging
|
||||
|
||||
# Set up logging for font loading
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FontWeight(Enum):
|
||||
@@ -71,29 +75,46 @@ class Font:
|
||||
# Navigate to the assets/fonts directory
|
||||
assets_dir = os.path.join(os.path.dirname(current_dir), 'assets', 'fonts')
|
||||
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):
|
||||
"""Load the font using PIL's ImageFont with consistent bundled font"""
|
||||
try:
|
||||
if self._font_path:
|
||||
# Use specified font path
|
||||
logger.info(f"Loading font from specified path: {self._font_path}")
|
||||
self._font = ImageFont.truetype(
|
||||
self._font_path,
|
||||
self._font_size
|
||||
)
|
||||
logger.info(f"Successfully loaded font from: {self._font_path}")
|
||||
else:
|
||||
# Use bundled font for consistency across environments
|
||||
bundled_font_path = self._get_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)
|
||||
logger.info(f"Successfully loaded bundled font at size {self._font_size}")
|
||||
else:
|
||||
# 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()
|
||||
|
||||
except Exception as e:
|
||||
# Ultimate fallback to default font
|
||||
logger.error(f"Failed to load font: {e}, falling back to PIL default font")
|
||||
self._font = ImageFont.load_default()
|
||||
|
||||
@property
|
||||
|
||||
Reference in New Issue
Block a user