nodeslicer/core/pdf_exporter/pdf_utils.py
2026-07-31 17:22:30 +03:00

156 lines
4.2 KiB
Python

"""
PDF Utilities Module.
Reusable utilities for PDF stream generation, text escaping, and drawing operations.
"""
from typing import Callable
def escape_pdf_text(text: str) -> str:
"""
Escape special characters for PDF text strings.
In PDF text strings delimited by parentheses, the following characters
must be escaped with a backslash: backslash, left-paren, right-paren.
Non-ASCII characters (like diameter symbol, degree, etc.) are left as-is and will be
encoded as cp1252 bytes in the PDF stream, matching the WinAnsiEncoding
used by the embedded font.
Args:
text: The text string to escape.
Returns:
The escaped text string safe for PDF text operators.
"""
return text.replace("\\", "\\\\").replace("(", "\\(").replace(")", "\\)")
def pdf_move_to(x_pt: float, y_pt: float) -> str:
"""Generate PDF move-to operator."""
return f"{x_pt:.2f} {y_pt:.2f} m"
def pdf_line_to(x_pt: float, y_pt: float) -> str:
"""Generate PDF line-to operator."""
return f"{x_pt:.2f} {y_pt:.2f} l"
def pdf_move_line(x1_pt: float, y1_pt: float, x2_pt: float, y2_pt: float) -> str:
"""Generate PDF move-to and line-to sequence."""
return f"{x1_pt:.2f} {y1_pt:.2f} m {x2_pt:.2f} {y2_pt:.2f} l"
def pdf_stroke() -> str:
"""Generate PDF stroke operator."""
return "S"
def pdf_text_show(text: str) -> str:
"""Generate PDF text show operator with escaped text."""
return f"({escape_pdf_text(text)}) Tj"
def pdf_text_begin() -> str:
"""Generate PDF begin text operator."""
return "BT"
def pdf_text_end() -> str:
"""Generate PDF end text operator."""
return "ET"
def pdf_set_font(font_name: str, size_pt: float) -> str:
"""Generate PDF font selection operator."""
return f"/{font_name} {size_pt:.1f} Tf"
def pdf_set_rgb_color(r: float, g: float, b: float, fill: bool = True) -> str:
"""
Generate PDF color setting operator.
Args:
r: Red component (0.0-1.0)
g: Green component (0.0-1.0)
b: Blue component (0.0-1.0)
fill: True for fill color (rg), False for stroke color (RG)
Returns:
PDF color operator string.
"""
color_op = "rg" if fill else "RG"
return f"{r:.3f} {g:.3f} {b:.3f} {color_op}"
def pdf_set_line_width(width_pt: float) -> str:
"""Generate PDF line width operator."""
return f"{width_pt:.2f} w"
def pdf_set_line_cap_join(cap: int = 1, join: int = 1) -> str:
"""
Generate PDF line cap and join style operators.
Args:
cap: 0=butt, 1=round, 2=projecting square
join: 0=miter, 1=round, 2=bevel
Returns:
PDF line style operator string.
"""
return f"{cap} j {join} J"
def pdf_set_graphics_state(name: str) -> str:
"""Generate PDF graphics state selection operator."""
return f"/{name} gs"
def pdf_transform_matrix(a: float, b: float, c: float, d: float, e: float, f: float) -> str:
"""Generate PDF transformation matrix operator."""
return f"{a:.2f} {b:.2f} {c:.2f} {d:.2f} {e:.2f} {f:.2f} Tm"
def pdf_arrowhead(
tip_x_pt: float,
tip_y_pt: float,
direction_x: float,
direction_y: float,
arrow_len_pt: float,
arrow_half_w_pt: float,
) -> str:
"""
Generate PDF commands for an arrowhead.
Args:
tip_x_pt: X coordinate of arrow tip
tip_y_pt: Y coordinate of arrow tip
direction_x: X direction vector (normalized)
direction_y: Y direction vector (normalized)
arrow_len_pt: Length of arrow in points
arrow_half_w_pt: Half-width of arrow in points
Returns:
PDF commands to draw the arrowhead.
"""
# Arrow base (opposite of tip direction)
base_x = tip_x_pt - direction_x * arrow_len_pt
base_y = tip_y_pt - direction_y * arrow_len_pt
# Perpendicular vectors for arrow wings
perp_x = direction_y * arrow_half_w_pt
perp_y = -direction_x * arrow_half_w_pt
# Left wing
left_x = base_x + perp_x
left_y = base_y + perp_y
# Right wing
right_x = base_x - perp_x
right_y = base_y - perp_y
return (
f"{tip_x_pt:.2f} {tip_y_pt:.2f} m "
f"{left_x:.2f} {left_y:.2f} l "
f"{right_x:.2f} {right_y:.2f} l S"
)