up
This commit is contained in:
parent
5b38682b93
commit
9b3b2cca94
4 changed files with 26 additions and 49 deletions
|
|
@ -31,6 +31,7 @@ from .sa2_measures import (
|
|||
from .writer import (
|
||||
MM_TO_PT,
|
||||
_format_pdf_lines,
|
||||
escape_pdf_text,
|
||||
export_mesh_to_pdf,
|
||||
export_wireframe_to_pdf,
|
||||
)
|
||||
|
|
@ -38,6 +39,7 @@ from .writer import (
|
|||
__all__ = [
|
||||
"export_mesh_to_pdf",
|
||||
"export_wireframe_to_pdf",
|
||||
"escape_pdf_text",
|
||||
"deduplicate_2d_edges",
|
||||
"extract_unique_top_view_edges",
|
||||
"extract_lineart_edges",
|
||||
|
|
|
|||
|
|
@ -19,28 +19,10 @@ from .sizing import SA2Sizing, compute_effective_stroke_and_font, MM_TO_PT
|
|||
|
||||
|
||||
def escape_pdf_text(text: str) -> str:
|
||||
"""
|
||||
Escapes special PDF characters (parentheses, backslashes) and converts
|
||||
WinAnsiEncoding symbols (Ø -> \330, ° -> \260, × -> \327, ± -> \261, µ -> \265)
|
||||
into PDF octal escape codes.
|
||||
"""
|
||||
res = []
|
||||
for ch in text:
|
||||
if ch == "(":
|
||||
res.append("\\(")
|
||||
elif ch == ")":
|
||||
res.append("\\)")
|
||||
elif ch == "\\":
|
||||
res.append("\\\\")
|
||||
else:
|
||||
code = ord(ch)
|
||||
if code < 128:
|
||||
res.append(ch)
|
||||
elif code <= 255:
|
||||
res.append(f"\\{code:03o}")
|
||||
else:
|
||||
res.append("?")
|
||||
return "".join(res)
|
||||
# For PDF text strings, escape only the PDF special characters: \, (, )
|
||||
# Non-ASCII characters will be encoded as cp1252 bytes in the stream
|
||||
# which matches the WinAnsiEncoding of the font
|
||||
return text.replace("\\", "\\\\").replace("(", "\\(").replace(")", "\\)")
|
||||
|
||||
|
||||
|
||||
|
|
@ -379,10 +361,10 @@ def render_sa2_measures_pdf(
|
|||
if abs(h_z_depth - total_z_span) < 0.2:
|
||||
depth_str = " THRU"
|
||||
elif h_z_depth > 0.1:
|
||||
depth_str = f" \u2334{h_z_depth:.1f}mm"
|
||||
depth_str = f" x {h_z_depth:.1f} DP"
|
||||
|
||||
dia_val = round(hdia, 1)
|
||||
dia_str = f"Ø{dia_val:g}mm{depth_str}"
|
||||
dia_str = f"Ø {dia_val:g}{depth_str}"
|
||||
y_text_offset = ys1 + 2.0 - (line_idx * font_size_pt * 1.1)
|
||||
|
||||
stream_cmds.append("BT")
|
||||
|
|
@ -417,7 +399,7 @@ def render_sa2_measures_pdf(
|
|||
stream_cmds.append(f"{p_e2[0,0]:.2f} {p_e2[0,1]:.2f} m {p_h2[0,0]:.2f} {p_h2[0,1]:.2f} l S")
|
||||
stream_cmds.append(f"{p_h1[0,0]:.2f} {p_h1[0,1]:.2f} m {p_h2[0,0]:.2f} {p_h2[0,1]:.2f} l S")
|
||||
|
||||
pitch_str = f"PITCH X: {dx_mm:.1f}mm"
|
||||
pitch_str = f"{dx_mm:.1f} C-C"
|
||||
stream_cmds.append("BT")
|
||||
stream_cmds.append(f"/F1 {font_size_pt * 0.8:.1f} Tf")
|
||||
stream_cmds.append(f"1 0 0 1 {(p_h1[0,0]+p_h2[0,0])/2.0:.2f} {p_h1[0,1] - font_size_pt * 0.9:.2f} Tm")
|
||||
|
|
@ -436,7 +418,7 @@ def render_sa2_measures_pdf(
|
|||
stream_cmds.append(f"{p_e2[0,0]:.2f} {p_e2[0,1]:.2f} m {p_v2[0,0]:.2f} {p_v2[0,1]:.2f} l S")
|
||||
stream_cmds.append(f"{p_v1[0,0]:.2f} {p_v1[0,1]:.2f} m {p_v2[0,0]:.2f} {p_v2[0,1]:.2f} l S")
|
||||
|
||||
pitch_str = f"PITCH Y: {dy_mm:.1f}mm"
|
||||
pitch_str = f"{dy_mm:.1f} C-C"
|
||||
stream_cmds.append("BT")
|
||||
stream_cmds.append(f"/F1 {font_size_pt * 0.8:.1f} Tf")
|
||||
stream_cmds.append(f"1 0 0 1 {p_v1[0,0] + 2.0:.2f} {(p_v1[0,1]+p_v2[0,1])/2.0:.2f} Tm")
|
||||
|
|
|
|||
|
|
@ -31,28 +31,10 @@ from .sizing import (
|
|||
|
||||
|
||||
def escape_pdf_text(text: str) -> str:
|
||||
"""
|
||||
Escapes special PDF characters (parentheses, backslashes) and converts
|
||||
WinAnsiEncoding symbols (Ø -> \330, ° -> \260, × -> \327, ± -> \261, µ -> \265)
|
||||
into PDF octal escape codes.
|
||||
"""
|
||||
res = []
|
||||
for ch in text:
|
||||
if ch == "(":
|
||||
res.append("\\(")
|
||||
elif ch == ")":
|
||||
res.append("\\)")
|
||||
elif ch == "\\":
|
||||
res.append("\\\\")
|
||||
else:
|
||||
code = ord(ch)
|
||||
if code < 128:
|
||||
res.append(ch)
|
||||
elif code <= 255:
|
||||
res.append(f"\\{code:03o}")
|
||||
else:
|
||||
res.append("?")
|
||||
return "".join(res)
|
||||
# For PDF text strings, escape only the PDF special characters: \, (, )
|
||||
# Non-ASCII characters will be encoded as cp1252 bytes in the stream
|
||||
# which matches the WinAnsiEncoding of the font
|
||||
return text.replace("\\", "\\\\").replace("(", "\\(").replace(")", "\\)")
|
||||
|
||||
|
||||
def _format_pdf_lines(lines_arr: NDArray[np.float64]) -> list[str]:
|
||||
|
|
@ -496,7 +478,7 @@ def export_mesh_to_pdf(
|
|||
stream_cmds.append("ET")
|
||||
|
||||
stream_content = "\n".join(stream_cmds) + "\n"
|
||||
stream_bytes = stream_content.encode("utf-8")
|
||||
stream_bytes = stream_content.encode("cp1252", errors="replace")
|
||||
|
||||
objects: list[bytes] = []
|
||||
objects.append(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n")
|
||||
|
|
|
|||
|
|
@ -50,12 +50,23 @@ from NodeSlicer.core.pdf_exporter import (
|
|||
extract_slicing_object_features,
|
||||
export_wireframe_to_pdf,
|
||||
export_mesh_to_pdf,
|
||||
escape_pdf_text,
|
||||
MM_TO_PT
|
||||
)
|
||||
|
||||
|
||||
class TestPdfExporter(unittest.TestCase):
|
||||
|
||||
def test_escape_pdf_text_special_characters(self):
|
||||
escaped = escape_pdf_text("Ø8mm (test) 45° × 2 ± 0.1 µm")
|
||||
# With cp1252 encoding, special chars pass through directly
|
||||
self.assertIn("Ø8mm", escaped)
|
||||
self.assertIn("\\(test\\)", escaped)
|
||||
self.assertIn("°", escaped)
|
||||
self.assertIn("×", escaped)
|
||||
self.assertIn("±", escaped)
|
||||
self.assertIn("µ", escaped)
|
||||
|
||||
def test_extract_edges_from_slicing_object(self):
|
||||
so = SlicingObject()
|
||||
# Create a simple triangle in 3D: (0,0,0), (10,0,0), (0,10,0) with normal (0,0,1)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue