diff --git a/core/pdf_exporter.py b/core/pdf_exporter.py index 8ee5b85..c1461c9 100644 --- a/core/pdf_exporter.py +++ b/core/pdf_exporter.py @@ -507,6 +507,66 @@ def export_wireframe_to_pdf( stream_cmds.extend(lines_str[start_idx:start_idx + 5000]) stream_cmds.append("S") + # 6. Size Scale Legend (Bottom Left, aligned directly on the bottom main grid lines with exact wireframe thickness) + stream_cmds.append("/GS100 gs") + x_grid_start_mm = grid_min_x + x_grid_end_mm = min(grid_max_x, grid_min_x + 10.0 * main_step) + num_main_steps = int(np.round((x_grid_end_mm - x_grid_start_mm) / main_step)) + + p_start_pt = to_pdf_pt(np.array([[x_grid_start_mm, grid_min_y]])) + p_end_pt = to_pdf_pt(np.array([[x_grid_end_mm, grid_min_y]])) + + x_start_pt = p_start_pt[0, 0] + x_end_pt = p_end_pt[0, 0] + y_bar_pt = p_start_pt[0, 1] + + # Exact same stroke thickness as object wireframe lines + stream_cmds.append(f"{line_width_pt:.2f} w") + stream_cmds.append("1 j 1 J") + + # Main horizontal legend line directly overlapping bottom grid line + stream_cmds.append(f"{x_start_pt:.2f} {y_bar_pt:.2f} m {x_end_pt:.2f} {y_bar_pt:.2f} l S") + + # Ticks directly overlapping vertical main grid lines + tick_cmds = [] + for k in range(num_main_steps + 1): + xk_mm = x_grid_start_mm + k * main_step + pk_pt = to_pdf_pt(np.array([[xk_mm, grid_min_y]])) + xk_pt = pk_pt[0, 0] + tick_h_pt = (5.0 if k in (0, 5, num_main_steps) else 3.0) * MM_TO_PT + tick_cmds.append(f"{xk_pt:.2f} {y_bar_pt:.2f} m {xk_pt:.2f} {y_bar_pt + tick_h_pt:.2f} l") + stream_cmds.extend(tick_cmds) + stream_cmds.append("S") + + # Proportionate font size scaling with linework thickness + font_size_pt = max(8.0, line_width_pt * 4.0) + + stream_cmds.append(f"{sr:.3f} {sg:.3f} {sb:.3f} rg") + stream_cmds.append("BT") + stream_cmds.append(f"/F1 {font_size_pt:.1f} Tf") + + for k in range(num_main_steps + 1): + xk_mm = x_grid_start_mm + k * main_step + pk_pt = to_pdf_pt(np.array([[xk_mm, grid_min_y]])) + xk_pt = pk_pt[0, 0] + tick_h_pt = (5.0 if k in (0, 5, num_main_steps) else 3.0) * MM_TO_PT + val_mm = k * main_step + + if main_step >= 1000.0: + val_num = val_mm / 1000.0 + unit = " m" if k == num_main_steps else "" + tick_str = f"{val_num:g}{unit}" + else: + unit = " mm" if k == num_main_steps else "" + tick_str = f"{val_mm:g}{unit}" + + x_text_pt = xk_pt - (len(tick_str) * font_size_pt * 0.28) + y_text_pt = y_bar_pt + tick_h_pt + (2.5 * MM_TO_PT) + stream_cmds.append(f"1 0 0 1 {x_text_pt:.2f} {y_text_pt:.2f} Tm") + stream_cmds.append(f"({tick_str}) Tj") + + stream_cmds.append("ET") + stream_content = "\n".join(stream_cmds) + "\n" stream_bytes = stream_content.encode("utf-8") @@ -516,7 +576,7 @@ def export_wireframe_to_pdf( page_header = ( f"3 0 obj\n" f"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {page_w_pt:.2f} {page_h_pt:.2f}] " - f"/Resources << /ExtGState << /GS25 5 0 R /GS50 6 0 R /GS100 7 0 R >> >> " + f"/Resources << /ExtGState << /GS25 5 0 R /GS50 6 0 R /GS100 7 0 R >> /Font << /F1 8 0 R >> >> " f"/Contents 4 0 R >>\n" f"endobj\n" ) @@ -527,6 +587,7 @@ def export_wireframe_to_pdf( objects.append(b"5 0 obj\n<< /Type /ExtGState /CA 0.25 /ca 0.25 >>\nendobj\n") objects.append(b"6 0 obj\n<< /Type /ExtGState /CA 0.5 /ca 0.5 >>\nendobj\n") objects.append(b"7 0 obj\n<< /Type /ExtGState /CA 1.0 /ca 1.0 >>\nendobj\n") + objects.append(b"8 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n") header = b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n"