document-svg 2.0.2

Convert PDF, Word, Excel, PowerPoint, diagram and CAD files into one SVG per page, locally — a Rust library and the docsvg command
Documentation
//! SVG to STEP (ISO 10303-21) Part 21 mechanical CAD wireframe reverse converter.
//!
//! Converts 2D SVG vector lines, rectangles, polygons, circles, and paths
//! into standard ISO 10303-21 AP203/AP214 mechanical CAD exchange files
//! with topological CARTESIAN_POINT, VERTEX_POINT, DIRECTION, VECTOR, LINE, and EDGE_CURVE entities.

use std::io::Write;

use crate::cad::svg_reader::{Point2D, SvgElement, parse_svg_elements};
use crate::error::Result;

#[derive(Clone, Copy, Debug)]
struct LineSeg {
    p1: Point2D,
    p2: Point2D,
}

/// Converts SVG vector drawing elements into a standard ISO 10303-21 Part 21 STEP file.
pub fn write_svg_to_step<W: Write>(svg_content: &str, mut writer: W) -> Result<()> {
    let doc = parse_svg_elements(svg_content)?;

    let mut segments: Vec<LineSeg> = Vec::new();

    for elem in doc.elements {
        match elem {
            SvgElement::Line { p1, p2, .. } => {
                if p1.x.is_finite() && p1.y.is_finite() && p2.x.is_finite() && p2.y.is_finite() {
                    segments.push(LineSeg { p1, p2 });
                }
            }
            SvgElement::Rect {
                x,
                y,
                width,
                height,
                ..
            } => {
                if x.is_finite()
                    && y.is_finite()
                    && width.is_finite()
                    && height.is_finite()
                    && width > 0.0
                    && height > 0.0
                {
                    let p1 = Point2D::new(x, y);
                    let p2 = Point2D::new(x + width, y);
                    let p3 = Point2D::new(x + width, y + height);
                    let p4 = Point2D::new(x, y + height);
                    segments.push(LineSeg { p1, p2 });
                    segments.push(LineSeg { p1: p2, p2: p3 });
                    segments.push(LineSeg { p1: p3, p2: p4 });
                    segments.push(LineSeg { p1: p4, p2: p1 });
                }
            }
            SvgElement::Circle { center, radius, .. } => {
                if center.x.is_finite()
                    && center.y.is_finite()
                    && radius.is_finite()
                    && radius > 0.0
                {
                    let segments_count = 32;
                    let mut pts = Vec::with_capacity(segments_count);
                    for i in 0..segments_count {
                        let theta = (i as f64) * std::f64::consts::TAU / (segments_count as f64);
                        pts.push(Point2D::new(
                            center.x + radius * theta.cos(),
                            center.y + radius * theta.sin(),
                        ));
                    }
                    for i in 0..segments_count {
                        segments.push(LineSeg {
                            p1: pts[i],
                            p2: pts[(i + 1) % segments_count],
                        });
                    }
                }
            }
            SvgElement::Polyline {
                points, is_closed, ..
            } => {
                let valid_pts: Vec<_> = points
                    .into_iter()
                    .filter(|p| p.x.is_finite() && p.y.is_finite())
                    .collect();
                if valid_pts.len() >= 2 {
                    for w in valid_pts.windows(2) {
                        segments.push(LineSeg { p1: w[0], p2: w[1] });
                    }
                    if is_closed
                        && valid_pts.len() >= 3
                        && let (Some(&first), Some(&last)) = (valid_pts.first(), valid_pts.last())
                    {
                        segments.push(LineSeg {
                            p1: last,
                            p2: first,
                        });
                    }
                }
            }
            SvgElement::Text { .. } => {}
        }
    }

    if segments.is_empty() {
        // Fallback: 100x100 square wireframe
        let p1 = Point2D::new(0.0, 0.0);
        let p2 = Point2D::new(100.0, 0.0);
        let p3 = Point2D::new(100.0, 100.0);
        let p4 = Point2D::new(0.0, 100.0);
        segments.push(LineSeg { p1, p2 });
        segments.push(LineSeg { p1: p2, p2: p3 });
        segments.push(LineSeg { p1: p3, p2: p4 });
        segments.push(LineSeg { p1: p4, p2: p1 });
    }

    // Write STEP Header
    writeln!(writer, "ISO-10303-21;")?;
    writeln!(writer, "HEADER;")?;
    writeln!(
        writer,
        "FILE_DESCRIPTION(('document-svg generated ISO 10303-21 wireframe model'),'2;1');"
    )?;
    writeln!(
        writer,
        "FILE_NAME('model.step','2026-09-11T00:00:00',('document-svg'),('CAD'),'document-svg STEP Generator','document-svg','');"
    )?;
    writeln!(writer, "FILE_SCHEMA(('CONFIG_CONTROL_DESIGN'));")?;
    writeln!(writer, "ENDSEC;")?;
    writeln!(writer, "DATA;")?;

    let mut id_seq: u64 = 1;

    for seg in &segments {
        let dx = seg.p2.x - seg.p1.x;
        let dy = seg.p2.y - seg.p1.y;
        let len = (dx * dx + dy * dy).sqrt();
        let (dir_x, dir_y) = if len > 1e-9 {
            (dx / len, dy / len)
        } else {
            (1.0, 0.0)
        };

        let pt1_id = id_seq;
        writeln!(
            writer,
            "#{pt1_id} = CARTESIAN_POINT('', ({:.4}, {:.4}, 0.0));",
            seg.p1.x, seg.p1.y
        )?;
        id_seq += 1;

        let pt2_id = id_seq;
        writeln!(
            writer,
            "#{pt2_id} = CARTESIAN_POINT('', ({:.4}, {:.4}, 0.0));",
            seg.p2.x, seg.p2.y
        )?;
        id_seq += 1;

        let v1_id = id_seq;
        writeln!(writer, "#{v1_id} = VERTEX_POINT('', #{pt1_id});")?;
        id_seq += 1;

        let v2_id = id_seq;
        writeln!(writer, "#{v2_id} = VERTEX_POINT('', #{pt2_id});")?;
        id_seq += 1;

        let dir_id = id_seq;
        writeln!(
            writer,
            "#{dir_id} = DIRECTION('', ({:.6}, {:.6}, 0.0));",
            dir_x, dir_y
        )?;
        id_seq += 1;

        let vec_id = id_seq;
        writeln!(
            writer,
            "#{vec_id} = VECTOR('', #{dir_id}, {:.4});",
            len.max(1e-4)
        )?;
        id_seq += 1;

        let line_id = id_seq;
        writeln!(writer, "#{line_id} = LINE('', #{pt1_id}, #{vec_id});")?;
        id_seq += 1;

        let edge_id = id_seq;
        writeln!(
            writer,
            "#{edge_id} = EDGE_CURVE('', #{v1_id}, #{v2_id}, #{line_id}, .T.);"
        )?;
        id_seq += 1;
    }

    writeln!(writer, "ENDSEC;")?;
    writeln!(writer, "END-ISO-10303-21;")?;

    Ok(())
}