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
//! AutoCAD Color Index (ACI) to RGB hex color conversion.
//!
//! DXF assigns colors to layers and entities using either an ACI palette index (1-255),
//! ByBlock (0), ByLayer (256), negative values (layer turned off), or 24-bit TrueColor.

/// Standard AutoCAD Color Index (ACI 1..=255) RGB lookup table.
/// Index 0 is unused (placeholder `#000000`).
/// Colors 1-7 are the primary CAD drafting colors.
/// For drafting on white backgrounds (such as SVG documents), ACI 7 (normally white/black)
/// resolves to black `#000000`.
pub const ACI_PALETTE: [[u8; 3]; 256] = [
    [0, 0, 0],       // 0: ByBlock placeholder
    [255, 0, 0],     // 1: Red
    [255, 255, 0],   // 2: Yellow
    [0, 255, 0],     // 3: Green
    [0, 255, 255],   // 4: Cyan
    [0, 0, 255],     // 5: Blue
    [255, 0, 255],   // 6: Magenta
    [0, 0, 0],       // 7: White/Black (black on white paper)
    [128, 128, 128], // 8: Dark Gray
    [192, 192, 192], // 9: Light Gray
    // 10 - 255 standard ACI palette
    [255, 0, 0],
    [255, 127, 127],
    [204, 0, 0],
    [204, 102, 102],
    [153, 0, 0],
    [153, 76, 76],
    [127, 0, 0],
    [127, 63, 63],
    [76, 0, 0],
    [76, 38, 38],
    [255, 63, 0],
    [255, 159, 127],
    [204, 51, 0],
    [204, 127, 102],
    [153, 38, 0],
    [153, 95, 76],
    [127, 31, 0],
    [127, 79, 63],
    [76, 19, 0],
    [76, 47, 38],
    [255, 127, 0],
    [255, 191, 127],
    [204, 102, 0],
    [204, 153, 102],
    [153, 76, 0],
    [153, 114, 76],
    [127, 63, 0],
    [127, 95, 63],
    [76, 38, 0],
    [76, 57, 38],
    [255, 191, 0],
    [255, 223, 127],
    [204, 153, 0],
    [204, 178, 102],
    [153, 114, 0],
    [153, 133, 76],
    [127, 95, 0],
    [127, 111, 63],
    [76, 57, 0],
    [76, 66, 38],
    [255, 255, 0],
    [255, 255, 127],
    [204, 204, 0],
    [204, 204, 102],
    [153, 153, 0],
    [153, 153, 76],
    [127, 127, 0],
    [127, 127, 63],
    [76, 76, 0],
    [76, 76, 38],
    [191, 255, 0],
    [223, 255, 127],
    [153, 204, 0],
    [178, 204, 102],
    [114, 153, 0],
    [133, 153, 76],
    [95, 127, 0],
    [111, 127, 63],
    [57, 76, 0],
    [66, 76, 38],
    [127, 255, 0],
    [191, 255, 127],
    [102, 204, 0],
    [153, 204, 102],
    [76, 153, 0],
    [114, 153, 76],
    [63, 127, 0],
    [95, 127, 63],
    [38, 76, 0],
    [57, 76, 38],
    [63, 255, 0],
    [159, 255, 127],
    [51, 204, 0],
    [127, 204, 102],
    [38, 153, 0],
    [95, 153, 76],
    [31, 127, 0],
    [79, 127, 63],
    [19, 76, 0],
    [47, 76, 38],
    [0, 255, 0],
    [127, 255, 127],
    [0, 204, 0],
    [102, 204, 102],
    [0, 153, 0],
    [76, 153, 76],
    [0, 127, 0],
    [63, 127, 63],
    [0, 76, 0],
    [38, 76, 38],
    [0, 255, 63],
    [127, 255, 159],
    [0, 204, 51],
    [102, 204, 127],
    [0, 153, 38],
    [76, 153, 95],
    [0, 127, 31],
    [63, 127, 79],
    [0, 76, 19],
    [38, 76, 47],
    [0, 255, 127],
    [127, 255, 191],
    [0, 204, 102],
    [102, 204, 153],
    [0, 153, 76],
    [76, 153, 114],
    [0, 127, 63],
    [63, 127, 95],
    [0, 76, 38],
    [38, 76, 57],
    [0, 255, 191],
    [127, 255, 223],
    [0, 204, 153],
    [102, 204, 178],
    [0, 153, 114],
    [76, 153, 133],
    [0, 127, 95],
    [63, 127, 111],
    [0, 76, 57],
    [38, 76, 66],
    [0, 255, 255],
    [127, 255, 255],
    [0, 204, 204],
    [102, 204, 204],
    [0, 153, 153],
    [76, 153, 153],
    [0, 127, 127],
    [63, 127, 127],
    [0, 76, 76],
    [38, 76, 76],
    [0, 191, 255],
    [127, 223, 255],
    [0, 153, 204],
    [102, 178, 204],
    [0, 114, 153],
    [76, 133, 153],
    [0, 95, 127],
    [63, 111, 127],
    [0, 57, 76],
    [38, 66, 76],
    [0, 127, 255],
    [127, 191, 255],
    [0, 102, 204],
    [102, 153, 204],
    [0, 76, 153],
    [76, 114, 153],
    [0, 63, 127],
    [63, 95, 127],
    [0, 38, 76],
    [38, 57, 76],
    [0, 63, 255],
    [127, 159, 255],
    [0, 51, 204],
    [102, 127, 204],
    [0, 38, 153],
    [76, 95, 153],
    [0, 31, 127],
    [63, 79, 127],
    [0, 19, 76],
    [38, 47, 76],
    [0, 0, 255],
    [127, 127, 255],
    [0, 0, 204],
    [102, 102, 204],
    [0, 0, 153],
    [76, 76, 153],
    [0, 0, 127],
    [63, 63, 127],
    [0, 0, 76],
    [38, 38, 76],
    [63, 0, 255],
    [159, 127, 255],
    [51, 0, 204],
    [127, 102, 204],
    [38, 0, 153],
    [95, 76, 153],
    [31, 0, 127],
    [79, 63, 127],
    [19, 0, 76],
    [47, 38, 76],
    [127, 0, 255],
    [191, 127, 255],
    [102, 0, 204],
    [153, 102, 204],
    [76, 0, 153],
    [114, 76, 153],
    [63, 0, 127],
    [95, 63, 127],
    [38, 0, 76],
    [57, 38, 76],
    [191, 0, 255],
    [223, 127, 255],
    [153, 0, 204],
    [178, 102, 204],
    [114, 0, 153],
    [133, 76, 153],
    [95, 0, 127],
    [111, 63, 127],
    [57, 0, 76],
    [66, 38, 76],
    [255, 0, 255],
    [255, 127, 255],
    [204, 0, 204],
    [204, 102, 204],
    [153, 0, 153],
    [153, 76, 153],
    [127, 0, 127],
    [127, 63, 127],
    [76, 0, 76],
    [76, 38, 76],
    [255, 0, 191],
    [255, 127, 223],
    [204, 0, 153],
    [204, 102, 178],
    [153, 0, 114],
    [153, 76, 133],
    [127, 0, 95],
    [127, 63, 111],
    [76, 0, 57],
    [76, 38, 66],
    [255, 0, 127],
    [255, 127, 191],
    [204, 0, 102],
    [204, 102, 153],
    [153, 0, 76],
    [153, 76, 114],
    [127, 0, 63],
    [127, 63, 95],
    [76, 0, 38],
    [76, 38, 57],
    [255, 0, 63],
    [255, 127, 159],
    [204, 0, 51],
    [204, 102, 127],
    [153, 0, 38],
    [153, 76, 95],
    [127, 0, 31],
    [127, 63, 79],
    [76, 0, 19],
    [76, 38, 47],
    // Grayscale ramp: 250 - 255
    [51, 51, 51],
    [91, 91, 91],
    [132, 132, 132],
    [173, 173, 173],
    [214, 214, 214],
    [255, 255, 255],
];

/// Converts an ACI index (1..=255) to a 6-digit hex color string (`#rrggbb`).
/// If index is 0, 256 or invalid, returns default black `#000000`.
#[must_use]
pub fn aci_to_hex(index: i16) -> String {
    let clamped_index = if (1..=255).contains(&index) {
        index as usize
    } else {
        7 // default to ACI 7 (black on white)
    };
    let [r, g, b] = ACI_PALETTE[clamped_index];
    format!("#{r:02x}{g:02x}{b:02x}")
}

/// Converts a 24-bit TrueColor integer (`0x00RRGGBB`) to a hex color string.
#[must_use]
pub fn truecolor_to_hex(val: u32) -> String {
    let r = ((val >> 16) & 0xff) as u8;
    let g = ((val >> 8) & 0xff) as u8;
    let b = (val & 0xff) as u8;
    format!("#{r:02x}{g:02x}{b:02x}")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn maps_primary_aci_colors() {
        assert_eq!(aci_to_hex(1), "#ff0000"); // Red
        assert_eq!(aci_to_hex(2), "#ffff00"); // Yellow
        assert_eq!(aci_to_hex(3), "#00ff00"); // Green
        assert_eq!(aci_to_hex(4), "#00ffff"); // Cyan
        assert_eq!(aci_to_hex(5), "#0000ff"); // Blue
        assert_eq!(aci_to_hex(6), "#ff00ff"); // Magenta
        assert_eq!(aci_to_hex(7), "#000000"); // Black
        assert_eq!(aci_to_hex(8), "#808080"); // Dark Gray
    }

    #[test]
    fn parses_truecolor() {
        assert_eq!(truecolor_to_hex(0x00123456), "#123456");
        assert_eq!(truecolor_to_hex(0x00ff8800), "#ff8800");
    }
}