use super::parser::{WmfParser, WmfRecord};
use crate::common::error::{Error, Result};
use crate::images::svg::*;
use crate::ole::binary::read_i16_le;
use rayon::prelude::*;
pub struct WmfSvgConverter {
parser: WmfParser,
}
impl WmfSvgConverter {
pub fn new(parser: WmfParser) -> Self {
Self { parser }
}
pub fn convert_to_svg(&self) -> Result<String> {
let width = self.parser.width() as f64;
let height = self.parser.height() as f64;
let mut builder = SvgBuilder::new(width, height);
builder = builder.with_viewbox(0.0, 0.0, width, height);
let elements: Vec<SvgElement> = self
.parser
.records
.par_iter()
.filter_map(|record| self.process_record(record).ok().flatten())
.collect();
for element in elements {
builder.add_element(element);
}
Ok(builder.build())
}
pub fn convert_to_svg_bytes(&self) -> Result<Vec<u8>> {
Ok(self.convert_to_svg()?.into_bytes())
}
fn process_record(&self, record: &WmfRecord) -> Result<Option<SvgElement>> {
match record.function {
0x041B => self.parse_rectangle(record),
0x0418 => self.parse_ellipse(record),
0x0324 => self.parse_polygon(record),
0x0325 => self.parse_polyline(record),
0x0817 => self.parse_arc(record),
0x081A => self.parse_pie(record),
0x0830 => self.parse_chord(record),
0x0F43 => self.parse_stretchdib(record),
0x0B41 => self.parse_dibstretchblt(record),
0x0940 => self.parse_dibbitblt(record),
0x061C => self.parse_roundrect(record),
0x0213 => self.parse_lineto(record),
_ => Ok(None),
}
}
fn parse_rectangle(&self, record: &WmfRecord) -> Result<Option<SvgElement>> {
if record.params.len() < 8 {
return Ok(None);
}
let bottom = read_i16_le(&record.params, 0).unwrap_or(0) as f64;
let right = read_i16_le(&record.params, 2).unwrap_or(0) as f64;
let top = read_i16_le(&record.params, 4).unwrap_or(0) as f64;
let left = read_i16_le(&record.params, 6).unwrap_or(0) as f64;
Ok(Some(SvgElement::Rect(SvgRect {
x: left,
y: top,
width: right - left,
height: bottom - top,
fill: None,
stroke: Some("#000000".to_string()),
stroke_width: 1.0,
})))
}
fn parse_ellipse(&self, record: &WmfRecord) -> Result<Option<SvgElement>> {
if record.params.len() < 8 {
return Ok(None);
}
let bottom = read_i16_le(&record.params, 0).unwrap_or(0) as f64;
let right = read_i16_le(&record.params, 2).unwrap_or(0) as f64;
let top = read_i16_le(&record.params, 4).unwrap_or(0) as f64;
let left = read_i16_le(&record.params, 6).unwrap_or(0) as f64;
let cx = (left + right) / 2.0;
let cy = (top + bottom) / 2.0;
let rx = (right - left) / 2.0;
let ry = (bottom - top) / 2.0;
Ok(Some(SvgElement::Ellipse(SvgEllipse {
cx,
cy,
rx,
ry,
fill: None,
stroke: Some("#000000".to_string()),
stroke_width: 1.0,
})))
}
fn parse_polygon(&self, record: &WmfRecord) -> Result<Option<SvgElement>> {
if record.params.len() < 2 {
return Ok(None);
}
let count = read_i16_le(&record.params, 0).unwrap_or(0) as usize;
if record.params.len() < 2 + count * 4 {
return Ok(None);
}
let mut commands = Vec::with_capacity(count + 1);
let points: Vec<(f64, f64)> = (0..count)
.into_par_iter()
.map(|i| {
let offset = 2 + i * 4;
let x = read_i16_le(&record.params, offset).unwrap_or(0) as f64;
let y = read_i16_le(&record.params, offset + 2).unwrap_or(0) as f64;
(x, y)
})
.collect();
for (i, (x, y)) in points.into_iter().enumerate() {
if i == 0 {
commands.push(PathCommand::MoveTo { x, y });
} else {
commands.push(PathCommand::LineTo { x, y });
}
}
commands.push(PathCommand::ClosePath);
Ok(Some(SvgElement::Path(
SvgPath::new(commands)
.with_stroke("#000000".to_string())
.with_fill("none".to_string()),
)))
}
fn parse_polyline(&self, record: &WmfRecord) -> Result<Option<SvgElement>> {
if record.params.len() < 2 {
return Ok(None);
}
let count = read_i16_le(&record.params, 0).unwrap_or(0) as usize;
if record.params.len() < 2 + count * 4 {
return Ok(None);
}
let mut commands = Vec::with_capacity(count);
let points: Vec<(f64, f64)> = (0..count)
.into_par_iter()
.map(|i| {
let offset = 2 + i * 4;
let x = read_i16_le(&record.params, offset).unwrap_or(0) as f64;
let y = read_i16_le(&record.params, offset + 2).unwrap_or(0) as f64;
(x, y)
})
.collect();
for (i, (x, y)) in points.into_iter().enumerate() {
if i == 0 {
commands.push(PathCommand::MoveTo { x, y });
} else {
commands.push(PathCommand::LineTo { x, y });
}
}
Ok(Some(SvgElement::Path(
SvgPath::new(commands)
.with_stroke("#000000".to_string())
.with_fill("none".to_string()),
)))
}
fn parse_arc(&self, _record: &WmfRecord) -> Result<Option<SvgElement>> {
Ok(None)
}
fn parse_pie(&self, _record: &WmfRecord) -> Result<Option<SvgElement>> {
Ok(None)
}
fn parse_chord(&self, _record: &WmfRecord) -> Result<Option<SvgElement>> {
Ok(None)
}
fn parse_roundrect(&self, record: &WmfRecord) -> Result<Option<SvgElement>> {
if record.params.len() < 12 {
return Ok(None);
}
let corner_height = read_i16_le(&record.params, 0).unwrap_or(0) as f64;
let corner_width = read_i16_le(&record.params, 2).unwrap_or(0) as f64;
let bottom = read_i16_le(&record.params, 4).unwrap_or(0) as f64;
let right = read_i16_le(&record.params, 6).unwrap_or(0) as f64;
let top = read_i16_le(&record.params, 8).unwrap_or(0) as f64;
let left = read_i16_le(&record.params, 10).unwrap_or(0) as f64;
let rx = corner_width / 2.0;
let ry = corner_height / 2.0;
let commands = vec![
PathCommand::MoveTo { x: left + rx, y: top },
PathCommand::LineTo { x: right - rx, y: top },
PathCommand::Arc {
rx,
ry,
x_axis_rotation: 0.0,
large_arc: false,
sweep: true,
x: right,
y: top + ry,
},
PathCommand::LineTo { x: right, y: bottom - ry },
PathCommand::Arc {
rx,
ry,
x_axis_rotation: 0.0,
large_arc: false,
sweep: true,
x: right - rx,
y: bottom,
},
PathCommand::LineTo { x: left + rx, y: bottom },
PathCommand::Arc {
rx,
ry,
x_axis_rotation: 0.0,
large_arc: false,
sweep: true,
x: left,
y: bottom - ry,
},
PathCommand::LineTo { x: left, y: top + ry },
PathCommand::Arc {
rx,
ry,
x_axis_rotation: 0.0,
large_arc: false,
sweep: true,
x: left + rx,
y: top,
},
PathCommand::ClosePath,
];
Ok(Some(SvgElement::Path(
SvgPath::new(commands)
.with_stroke("#000000".to_string())
.with_fill("none".to_string()),
)))
}
fn parse_lineto(&self, record: &WmfRecord) -> Result<Option<SvgElement>> {
if record.params.len() < 4 {
return Ok(None);
}
let y = read_i16_le(&record.params, 0).unwrap_or(0) as f64;
let x = read_i16_le(&record.params, 2).unwrap_or(0) as f64;
Ok(Some(SvgElement::Path(
SvgPath::new(vec![
PathCommand::MoveTo { x: 0.0, y: 0.0 },
PathCommand::LineTo { x, y },
])
.with_stroke("#000000".to_string()),
)))
}
fn parse_stretchdib(&self, record: &WmfRecord) -> Result<Option<SvgElement>> {
if record.params.len() < 20 {
return Ok(None);
}
let dest_height = read_i16_le(&record.params, 6).unwrap_or(0) as f64;
let dest_width = read_i16_le(&record.params, 8).unwrap_or(0) as f64;
let dest_y = read_i16_le(&record.params, 10).unwrap_or(0) as f64;
let dest_x = read_i16_le(&record.params, 12).unwrap_or(0) as f64;
if let Ok(png_data) = self.extract_and_convert_dib(&record.params[20..]) {
return Ok(Some(SvgElement::Image(SvgImage::from_png_data(
dest_x,
dest_y,
dest_width,
dest_height,
&png_data,
))));
}
Ok(None)
}
fn parse_dibstretchblt(&self, record: &WmfRecord) -> Result<Option<SvgElement>> {
if record.params.len() < 20 {
return Ok(None);
}
if let Ok(png_data) = self.extract_and_convert_dib(&record.params[18..]) {
let dest_x = read_i16_le(&record.params, 6).unwrap_or(0) as f64;
let dest_y = read_i16_le(&record.params, 8).unwrap_or(0) as f64;
let dest_width = read_i16_le(&record.params, 10).unwrap_or(0) as f64;
let dest_height = read_i16_le(&record.params, 12).unwrap_or(0) as f64;
return Ok(Some(SvgElement::Image(SvgImage::from_png_data(
dest_x,
dest_y,
dest_width,
dest_height,
&png_data,
))));
}
Ok(None)
}
fn parse_dibbitblt(&self, record: &WmfRecord) -> Result<Option<SvgElement>> {
if record.params.len() < 16 {
return Ok(None);
}
if let Ok(png_data) = self.extract_and_convert_dib(&record.params[14..]) {
let dest_x = read_i16_le(&record.params, 4).unwrap_or(0) as f64;
let dest_y = read_i16_le(&record.params, 6).unwrap_or(0) as f64;
let width = read_i16_le(&record.params, 8).unwrap_or(0) as f64;
let height = read_i16_le(&record.params, 10).unwrap_or(0) as f64;
return Ok(Some(SvgElement::Image(SvgImage::from_png_data(
dest_x,
dest_y,
width,
height,
&png_data,
))));
}
Ok(None)
}
fn extract_and_convert_dib(&self, dib_data: &[u8]) -> Result<Vec<u8>> {
if dib_data.len() < 40 {
return Err(Error::ParseError("DIB data too small".into()));
}
let file_size = 14u32 + dib_data.len() as u32;
let pixel_data_offset = 14u32 + 40u32;
let mut bmp_data = Vec::with_capacity(file_size as usize);
bmp_data.extend_from_slice(b"BM");
bmp_data.extend_from_slice(&file_size.to_le_bytes());
bmp_data.extend_from_slice(&[0u8; 4]);
bmp_data.extend_from_slice(&pixel_data_offset.to_le_bytes());
bmp_data.extend_from_slice(dib_data);
let img = image::load_from_memory(&bmp_data)
.map_err(|e| Error::ParseError(format!("Failed to load DIB: {}", e)))?;
let mut png_data = Vec::new();
let mut cursor = std::io::Cursor::new(&mut png_data);
img.write_to(&mut cursor, image::ImageFormat::Png)
.map_err(|e| Error::ParseError(format!("Failed to encode PNG: {}", e)))?;
Ok(png_data)
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_wmf_svg_converter_creation() {
}
}