""" 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" )