156 lines
4.2 KiB
Python
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"
|
|
)
|