From 9b3b2cca9422ecfa849856261b90016390c0f34b Mon Sep 17 00:00:00 2001 From: Nicolas Predella Date: Fri, 31 Jul 2026 15:15:31 +0300 Subject: [PATCH] up --- core/pdf_exporter/__init__.py | 2 ++ core/pdf_exporter/sa2_measures.py | 34 ++++++++----------------------- core/pdf_exporter/writer.py | 28 +++++-------------------- tests/test_pdf_exporter.py | 11 ++++++++++ 4 files changed, 26 insertions(+), 49 deletions(-) diff --git a/core/pdf_exporter/__init__.py b/core/pdf_exporter/__init__.py index 4289818..e7e7495 100644 --- a/core/pdf_exporter/__init__.py +++ b/core/pdf_exporter/__init__.py @@ -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", diff --git a/core/pdf_exporter/sa2_measures.py b/core/pdf_exporter/sa2_measures.py index 8e97d91..6a8102a 100644 --- a/core/pdf_exporter/sa2_measures.py +++ b/core/pdf_exporter/sa2_measures.py @@ -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") diff --git a/core/pdf_exporter/writer.py b/core/pdf_exporter/writer.py index 94176bc..a4d583a 100644 --- a/core/pdf_exporter/writer.py +++ b/core/pdf_exporter/writer.py @@ -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") diff --git a/tests/test_pdf_exporter.py b/tests/test_pdf_exporter.py index e36d493..fb644a5 100644 --- a/tests/test_pdf_exporter.py +++ b/tests/test_pdf_exporter.py @@ -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)