fixed bug in pdf export
fixed bug in asset loading
This commit is contained in:
+248
-9
@@ -74,10 +74,10 @@ def test_pdf_exporter_with_text():
|
||||
"""Test PDF export with text boxes"""
|
||||
project = Project("Test Text Project")
|
||||
project.page_size_mm = (210, 297)
|
||||
|
||||
|
||||
# Create page with text box
|
||||
page = Page(page_number=1, is_double_spread=False)
|
||||
|
||||
|
||||
# Add a text box
|
||||
text_box = TextBoxData(
|
||||
text_content="Hello, World!",
|
||||
@@ -86,24 +86,261 @@ def test_pdf_exporter_with_text():
|
||||
x=50, y=50, width=100, height=30
|
||||
)
|
||||
page.layout.add_element(text_box)
|
||||
|
||||
|
||||
project.add_page(page)
|
||||
|
||||
|
||||
# Export to temporary file
|
||||
with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp:
|
||||
tmp_path = tmp.name
|
||||
|
||||
|
||||
try:
|
||||
exporter = PDFExporter(project)
|
||||
success, warnings = exporter.export(tmp_path)
|
||||
|
||||
|
||||
assert success, f"Export failed: {warnings}"
|
||||
assert os.path.exists(tmp_path), "PDF file was not created"
|
||||
|
||||
|
||||
print(f"✓ Text box PDF export successful: {tmp_path}")
|
||||
if warnings:
|
||||
print(f" Warnings: {warnings}")
|
||||
|
||||
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.remove(tmp_path)
|
||||
|
||||
|
||||
def test_pdf_text_position_and_size():
|
||||
"""
|
||||
Test that text in PDF is correctly positioned and sized relative to its text box.
|
||||
|
||||
This test verifies:
|
||||
1. Font size is properly scaled (not used directly as PDF points)
|
||||
2. Text is positioned inside the text box (not above it)
|
||||
3. Text respects the top-alignment used in the UI
|
||||
"""
|
||||
import pdfplumber
|
||||
|
||||
project = Project("Test Text Position")
|
||||
project.page_size_mm = (210, 297) # A4
|
||||
project.working_dpi = 96
|
||||
|
||||
# Create page with text box
|
||||
page = Page(page_number=1, is_double_spread=False)
|
||||
|
||||
# Create a text box with specific dimensions in pixels (at 96 DPI)
|
||||
# Text box: 200px wide x 100px tall, positioned at (100, 100)
|
||||
# Font size: 48 pixels (stored in same units as element size)
|
||||
text_box_x_px = 100
|
||||
text_box_y_px = 100
|
||||
text_box_width_px = 200
|
||||
text_box_height_px = 100
|
||||
font_size_px = 48 # Font size in same pixel units as element
|
||||
|
||||
text_box = TextBoxData(
|
||||
text_content="Test",
|
||||
font_settings={"family": "Helvetica", "size": font_size_px, "color": (0, 0, 0)},
|
||||
alignment="left",
|
||||
x=text_box_x_px,
|
||||
y=text_box_y_px,
|
||||
width=text_box_width_px,
|
||||
height=text_box_height_px
|
||||
)
|
||||
page.layout.add_element(text_box)
|
||||
project.add_page(page)
|
||||
|
||||
# Calculate expected PDF values
|
||||
MM_TO_POINTS = 2.834645669
|
||||
dpi = project.working_dpi
|
||||
page_height_pt = 297 * MM_TO_POINTS # ~842 points
|
||||
|
||||
# Convert text box dimensions to points
|
||||
text_box_x_mm = text_box_x_px * 25.4 / dpi
|
||||
text_box_y_mm = text_box_y_px * 25.4 / dpi
|
||||
text_box_width_mm = text_box_width_px * 25.4 / dpi
|
||||
text_box_height_mm = text_box_height_px * 25.4 / dpi
|
||||
|
||||
text_box_x_pt = text_box_x_mm * MM_TO_POINTS
|
||||
text_box_y_pt_bottom = page_height_pt - (text_box_y_mm * MM_TO_POINTS) - (text_box_height_mm * MM_TO_POINTS)
|
||||
text_box_y_pt_top = text_box_y_pt_bottom + (text_box_height_mm * MM_TO_POINTS)
|
||||
text_box_height_pt = text_box_height_mm * MM_TO_POINTS
|
||||
|
||||
# Font size should also be converted from pixels to points
|
||||
expected_font_size_pt = font_size_px * 25.4 / dpi * MM_TO_POINTS
|
||||
|
||||
# Export to temporary file
|
||||
with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp:
|
||||
tmp_path = tmp.name
|
||||
|
||||
try:
|
||||
exporter = PDFExporter(project)
|
||||
success, warnings = exporter.export(tmp_path)
|
||||
|
||||
assert success, f"Export failed: {warnings}"
|
||||
|
||||
# Extract text position from PDF
|
||||
with pdfplumber.open(tmp_path) as pdf:
|
||||
page_pdf = pdf.pages[0]
|
||||
chars = page_pdf.chars
|
||||
|
||||
assert len(chars) > 0, "No text found in PDF"
|
||||
|
||||
# Get the first character's position and font size
|
||||
first_char = chars[0]
|
||||
text_x = first_char['x0']
|
||||
text_y_baseline = first_char['y0'] # This is the baseline y position
|
||||
actual_font_size = first_char['size']
|
||||
|
||||
print(f"\nText Position Analysis:")
|
||||
print(f" Text box (in pixels at {dpi} DPI): x={text_box_x_px}, y={text_box_y_px}, "
|
||||
f"w={text_box_width_px}, h={text_box_height_px}")
|
||||
print(f" Text box (in PDF points): x={text_box_x_pt:.1f}, "
|
||||
f"y_bottom={text_box_y_pt_bottom:.1f}, y_top={text_box_y_pt_top:.1f}, "
|
||||
f"height={text_box_height_pt:.1f}")
|
||||
print(f" Font size (pixels): {font_size_px}")
|
||||
print(f" Expected font size (points): {expected_font_size_pt:.1f}")
|
||||
print(f" Actual font size (points): {actual_font_size:.1f}")
|
||||
print(f" Actual text x: {text_x:.1f}")
|
||||
print(f" Actual text y (baseline): {text_y_baseline:.1f}")
|
||||
|
||||
# Verify font size is scaled correctly (tolerance of 1pt)
|
||||
font_size_diff = abs(actual_font_size - expected_font_size_pt)
|
||||
assert font_size_diff < 2.0, (
|
||||
f"Font size mismatch: expected ~{expected_font_size_pt:.1f}pt, "
|
||||
f"got {actual_font_size:.1f}pt (diff: {font_size_diff:.1f}pt). "
|
||||
f"Font size should be converted from pixels to points."
|
||||
)
|
||||
|
||||
# Verify text X position is near the left edge of the text box
|
||||
x_diff = abs(text_x - text_box_x_pt)
|
||||
assert x_diff < 5.0, (
|
||||
f"Text X position mismatch: expected ~{text_box_x_pt:.1f}, "
|
||||
f"got {text_x:.1f} (diff: {x_diff:.1f}pt)"
|
||||
)
|
||||
|
||||
# Verify text Y baseline is INSIDE the text box (not above it)
|
||||
# For top-aligned text, baseline should be within the box bounds
|
||||
# pdfplumber y-coordinates use PDF coordinate system: origin at bottom-left, y increases upward
|
||||
# So y0 is already the y-coordinate from the bottom of the page
|
||||
text_y_from_bottom = text_y_baseline
|
||||
|
||||
# Text baseline should be between box bottom and box top
|
||||
# Allow some margin for ascender/descender
|
||||
margin = actual_font_size * 0.3 # 30% margin for font metrics
|
||||
|
||||
assert text_y_from_bottom >= text_box_y_pt_bottom - margin, (
|
||||
f"Text is below the text box! "
|
||||
f"Text baseline y={text_y_from_bottom:.1f}, box bottom={text_box_y_pt_bottom:.1f}"
|
||||
)
|
||||
assert text_y_from_bottom <= text_box_y_pt_top + margin, (
|
||||
f"Text baseline is above the text box! "
|
||||
f"Text baseline y={text_y_from_bottom:.1f}, box top={text_box_y_pt_top:.1f}. "
|
||||
f"Text should be positioned inside the box, not above it."
|
||||
)
|
||||
|
||||
print(f" Text y (from bottom): {text_y_from_bottom:.1f}")
|
||||
print(f" Text is inside box bounds: ✓")
|
||||
print(f"\n✓ Text position and size test passed!")
|
||||
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.remove(tmp_path)
|
||||
|
||||
|
||||
def test_pdf_text_wrapping():
|
||||
"""
|
||||
Test that text wraps correctly within the text box boundaries.
|
||||
|
||||
This test verifies:
|
||||
1. Long text is word-wrapped to fit within the box width
|
||||
2. Multiple lines are rendered correctly
|
||||
3. Text stays within the box boundaries
|
||||
"""
|
||||
import pdfplumber
|
||||
|
||||
project = Project("Test Text Wrapping")
|
||||
project.page_size_mm = (210, 297) # A4
|
||||
project.working_dpi = 96
|
||||
|
||||
# Create page with text box
|
||||
page = Page(page_number=1, is_double_spread=False)
|
||||
|
||||
# Create a text box with long text that should wrap
|
||||
text_box_x_px = 100
|
||||
text_box_y_px = 100
|
||||
text_box_width_px = 200 # Narrow box to force wrapping
|
||||
text_box_height_px = 200 # Tall enough for multiple lines
|
||||
font_size_px = 24
|
||||
|
||||
long_text = "This is a long piece of text that should wrap to multiple lines within the text box boundaries."
|
||||
|
||||
text_box = TextBoxData(
|
||||
text_content=long_text,
|
||||
font_settings={"family": "Helvetica", "size": font_size_px, "color": (0, 0, 0)},
|
||||
alignment="left",
|
||||
x=text_box_x_px,
|
||||
y=text_box_y_px,
|
||||
width=text_box_width_px,
|
||||
height=text_box_height_px
|
||||
)
|
||||
page.layout.add_element(text_box)
|
||||
project.add_page(page)
|
||||
|
||||
# Calculate box boundaries in PDF points
|
||||
MM_TO_POINTS = 2.834645669
|
||||
dpi = project.working_dpi
|
||||
|
||||
text_box_x_mm = text_box_x_px * 25.4 / dpi
|
||||
text_box_width_mm = text_box_width_px * 25.4 / dpi
|
||||
text_box_x_pt = text_box_x_mm * MM_TO_POINTS
|
||||
text_box_width_pt = text_box_width_mm * MM_TO_POINTS
|
||||
text_box_right_pt = text_box_x_pt + text_box_width_pt
|
||||
|
||||
# Export to temporary file
|
||||
with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp:
|
||||
tmp_path = tmp.name
|
||||
|
||||
try:
|
||||
exporter = PDFExporter(project)
|
||||
success, warnings = exporter.export(tmp_path)
|
||||
|
||||
assert success, f"Export failed: {warnings}"
|
||||
|
||||
# Extract text from PDF
|
||||
with pdfplumber.open(tmp_path) as pdf:
|
||||
page_pdf = pdf.pages[0]
|
||||
chars = page_pdf.chars
|
||||
|
||||
assert len(chars) > 0, "No text found in PDF"
|
||||
|
||||
# Get all unique Y positions (lines)
|
||||
y_positions = sorted(set(round(c['top'], 1) for c in chars))
|
||||
|
||||
print(f"\nText Wrapping Analysis:")
|
||||
print(f" Text box width: {text_box_width_pt:.1f}pt")
|
||||
print(f" Text box x: {text_box_x_pt:.1f}pt to {text_box_right_pt:.1f}pt")
|
||||
print(f" Number of lines: {len(y_positions)}")
|
||||
print(f" Line Y positions: {y_positions[:5]}...") # Show first 5
|
||||
|
||||
# Verify text wrapped to multiple lines
|
||||
assert len(y_positions) > 1, (
|
||||
f"Text should wrap to multiple lines but only found {len(y_positions)} line(s)"
|
||||
)
|
||||
|
||||
# Verify all characters are within box width (with small tolerance)
|
||||
tolerance = 5.0 # Small tolerance for rounding
|
||||
for char in chars:
|
||||
char_x = char['x0']
|
||||
char_right = char['x1']
|
||||
assert char_x >= text_box_x_pt - tolerance, (
|
||||
f"Character '{char['text']}' at x={char_x:.1f} is left of box start {text_box_x_pt:.1f}"
|
||||
)
|
||||
assert char_right <= text_box_right_pt + tolerance, (
|
||||
f"Character '{char['text']}' ends at x={char_right:.1f} which exceeds box right {text_box_right_pt:.1f}"
|
||||
)
|
||||
|
||||
print(f" All characters within box width: ✓")
|
||||
print(f"\n✓ Text wrapping test passed!")
|
||||
|
||||
finally:
|
||||
if os.path.exists(tmp_path):
|
||||
os.remove(tmp_path)
|
||||
@@ -113,7 +350,7 @@ def test_pdf_exporter_facing_pages_alignment():
|
||||
"""Test that double spreads align to facing pages"""
|
||||
project = Project("Test Facing Pages")
|
||||
project.page_size_mm = (210, 297)
|
||||
|
||||
|
||||
# Add single page (page 1)
|
||||
page1 = Page(page_number=1, is_double_spread=False)
|
||||
project.add_page(page1)
|
||||
@@ -761,6 +998,8 @@ if __name__ == "__main__":
|
||||
test_pdf_exporter_basic()
|
||||
test_pdf_exporter_double_spread()
|
||||
test_pdf_exporter_with_text()
|
||||
test_pdf_text_position_and_size()
|
||||
test_pdf_text_wrapping()
|
||||
test_pdf_exporter_facing_pages_alignment()
|
||||
test_pdf_exporter_missing_image()
|
||||
test_pdf_exporter_spanning_image()
|
||||
|
||||
Reference in New Issue
Block a user