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 G-code (CNC & Laser Cutter toolpath) reverse converter.

use std::io::Write;

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

/// Converts SVG document content to standard G-code toolpaths for CNC routers and laser cutters.
pub fn write_svg_to_gcode<W: Write>(svg_content: &str, mut writer: W) -> Result<()> {
    let doc = parse_svg_elements(svg_content)?;
    let view_height = doc.height;

    let mut polylines: Vec<Vec<(f64, f64)>> = 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() {
                    polylines.push(vec![(p1.x, view_height - p1.y), (p2.x, view_height - p2.y)]);
                }
            }
            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 y0 = view_height - y;
                    let y1 = view_height - (y + height);
                    polylines.push(vec![
                        (x, y0),
                        (x + width, y0),
                        (x + width, y1),
                        (x, y1),
                        (x, y0),
                    ]);
                }
            }
            SvgElement::Circle { center, radius, .. } => {
                if center.x.is_finite()
                    && center.y.is_finite()
                    && radius.is_finite()
                    && radius > 0.0
                {
                    let dy = view_height - center.y;
                    let segments = crate::cad::DEFAULT_CIRCLE_SEGMENTS;
                    let mut circle_pts = Vec::with_capacity(segments + 1);
                    for i in 0..=segments {
                        let theta = (i as f64) * std::f64::consts::TAU / (segments as f64);
                        circle_pts
                            .push((center.x + radius * theta.cos(), dy + radius * theta.sin()));
                    }
                    polylines.push(circle_pts);
                }
            }
            SvgElement::Polyline {
                points, is_closed, ..
            } => {
                let mut pts: Vec<(f64, f64)> = points
                    .into_iter()
                    .filter(|p| p.x.is_finite() && p.y.is_finite())
                    .map(|p| (p.x, view_height - p.y))
                    .collect();
                if pts.len() >= 2 {
                    if is_closed
                        && pts.first() != pts.last()
                        && let Some(&first) = pts.first()
                    {
                        pts.push(first);
                    }
                    polylines.push(pts);
                }
            }
            SvgElement::Text { .. } => {}
        }
    }

    // Standard RS-274D / LinuxCNC / GRBL G-code program preamble
    writeln!(writer, "(Generated by document-svg)")?;
    writeln!(writer, "G21 (Units in millimeters)")?;
    writeln!(writer, "G90 (Absolute distance mode)")?;
    writeln!(writer, "G17 (XY plane selection)")?;
    writeln!(writer, "G94 (Feedrate per minute)")?;
    writeln!(writer, "G00 Z5.000 (Safe clearance height)")?;

    for (idx, poly) in polylines.iter().enumerate() {
        if poly.len() < 2 {
            continue;
        }
        let start = poly[0];
        writeln!(
            writer,
            "
(--- Toolpath #{idx} ---)"
        )?;
        // Rapid traverse to start of contour
        writeln!(writer, "G00 X{:.3} Y{:.3}", start.0, start.1)?;
        // Turn on spindle / laser
        writeln!(writer, "M03 S1000")?;
        writeln!(writer, "G01 Z0.000 F300.0 (Engage material)")?;

        // Cut path
        for pt in &poly[1..] {
            writeln!(writer, "G01 X{:.3} Y{:.3} F1200.0", pt.0, pt.1)?;
        }

        // Retract & turn off
        writeln!(writer, "M05")?;
        writeln!(writer, "G00 Z5.000")?;
    }

    // Return home & end program
    writeln!(
        writer,
        "
(--- End of program ---)"
    )?;
    writeln!(writer, "M05")?;
    writeln!(writer, "G00 Z10.000")?;
    writeln!(writer, "G00 X0.000 Y0.000")?;
    writeln!(writer, "M02")?;

    Ok(())
}