use std::ffi::CString;
use std::path::Path;
use std::io::Write;
use std::slice;
use std::str;
use base64::Engine;
use graphviz_sys as sys;
use crate::error::GraphvizError;
use crate::graph::Graph;
use crate::layout::Context;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Format {
Png,
Svg,
Pdf,
Ps,
Eps,
Gif,
Jpeg,
Json,
Dot,
Xdot,
Plain,
Canon,
Fig,
Vrml,
Cmapx,
Imap,
Bmp,
Svgz,
}
impl Format {
pub(crate) fn as_cstr(&self) -> Result<CString, GraphvizError> {
let name = match self {
Format::Png => "png",
Format::Svg => "svg",
Format::Pdf => "pdf",
Format::Ps => "ps",
Format::Eps => "eps",
Format::Gif => "gif",
Format::Jpeg => "jpeg",
Format::Json => "json",
Format::Dot => "dot",
Format::Xdot => "xdot",
Format::Plain => "plain",
Format::Canon => "canon",
Format::Fig => "fig",
Format::Vrml => "vrml",
Format::Cmapx => "cmapx",
Format::Imap => "imap",
Format::Bmp => "bmp",
Format::Svgz => "svgz",
};
CString::new(name).map_err(|_| GraphvizError::InvalidFormat)
}
pub fn is_binary(&self) -> bool {
match self {
Format::Png | Format::Gif | Format::Jpeg | Format::Pdf |
Format::Bmp | Format::Svgz => true,
Format::Svg | Format::Dot | Format::Xdot | Format::Plain |
Format::Canon | Format::Json | Format::Ps | Format::Eps |
Format::Fig | Format::Vrml | Format::Cmapx | Format::Imap => false,
}
}
pub fn all() -> impl Iterator<Item = Format> {
[
Format::Png,
Format::Svg,
Format::Pdf,
Format::Ps,
Format::Eps,
Format::Gif,
Format::Jpeg,
Format::Json,
Format::Dot,
Format::Xdot,
Format::Plain,
Format::Canon,
Format::Fig,
Format::Vrml,
Format::Cmapx,
Format::Imap,
Format::Bmp,
Format::Svgz,
].iter().copied()
}
pub fn mime_type(&self) -> &'static str {
match self {
Format::Png => "image/png",
Format::Svg => "image/svg+xml",
Format::Pdf => "application/pdf",
Format::Ps => "application/postscript",
Format::Eps => "application/postscript",
Format::Gif => "image/gif",
Format::Jpeg => "image/jpeg",
Format::Json => "application/json",
Format::Dot => "text/vnd.graphviz",
Format::Xdot => "text/vnd.graphviz",
Format::Plain => "text/plain",
Format::Canon => "text/vnd.graphviz",
Format::Fig => "image/x-xfig",
Format::Vrml => "model/vrml",
Format::Cmapx => "text/html",
Format::Imap => "application/x-httpd-imap",
Format::Bmp => "image/bmp",
Format::Svgz => "image/svg+xml",
}
}
pub fn extension(&self) -> &'static str {
match self {
Format::Png => "png",
Format::Svg => "svg",
Format::Pdf => "pdf",
Format::Ps => "ps",
Format::Eps => "eps",
Format::Gif => "gif",
Format::Jpeg => "jpg",
Format::Json => "json",
Format::Dot => "dot",
Format::Xdot => "xdot",
Format::Plain => "txt",
Format::Canon => "dot",
Format::Fig => "fig",
Format::Vrml => "wrl",
Format::Cmapx => "map",
Format::Imap => "map",
Format::Bmp => "bmp",
Format::Svgz => "svgz",
}
}
}
pub fn render_to_file<P: AsRef<Path>>(
context: &Context,
graph: &Graph,
format: Format,
path: P,
) -> Result<(), GraphvizError> {
let format_cstr = format.as_cstr()?;
let path_str = path.as_ref().to_string_lossy();
let path_cstr = CString::new(path_str.as_bytes())?;
let result = unsafe {
sys::gvRenderFilename(
context.inner,
graph.inner,
format_cstr.as_ptr(),
path_cstr.as_ptr(),
)
};
if result == 0 {
Ok(())
} else {
Err(GraphvizError::RenderFailed)
}
}
pub fn render_to_string(
context: &Context,
graph: &Graph,
format: Format,
) -> Result<String, GraphvizError> {
let format_cstr = format.as_cstr()?;
let mut buffer_ptr: *mut std::os::raw::c_char = std::ptr::null_mut();
let mut length: std::os::raw::c_uint = 0;
let result = unsafe {
sys::gvRenderData(
context.inner,
graph.inner,
format_cstr.as_ptr(),
&mut buffer_ptr,
&mut length,
)
};
if result != 0 {
return Err(GraphvizError::RenderFailed);
}
if buffer_ptr.is_null() {
return Err(GraphvizError::NullPointer("Render buffer is null"));
}
let rendered_string = if format.is_binary() {
let data_slice = unsafe {
slice::from_raw_parts(buffer_ptr as *const u8, length as usize)
};
base64::engine::general_purpose::STANDARD.encode(data_slice)
} else {
let data_slice = unsafe {
slice::from_raw_parts(buffer_ptr as *const u8, length as usize)
};
match str::from_utf8(data_slice) {
Ok(s) => s.to_owned(),
Err(_) => {
unsafe { sys::gvFreeRenderData(buffer_ptr) };
return Err(GraphvizError::InvalidUtf8);
}
}
};
unsafe { sys::gvFreeRenderData(buffer_ptr) };
Ok(rendered_string)
}
pub fn render_to_bytes(
context: &Context,
graph: &Graph,
format: Format,
) -> Result<Vec<u8>, GraphvizError> {
let format_cstr = format.as_cstr()?;
let mut buffer_ptr: *mut std::os::raw::c_char = std::ptr::null_mut();
let mut length: std::os::raw::c_uint = 0;
let result = unsafe {
sys::gvRenderData(
context.inner,
graph.inner,
format_cstr.as_ptr(),
&mut buffer_ptr,
&mut length,
)
};
if result != 0 {
return Err(GraphvizError::RenderFailed);
}
if buffer_ptr.is_null() {
return Err(GraphvizError::NullPointer("Render buffer is null"));
}
let data_slice = unsafe {
slice::from_raw_parts(buffer_ptr as *const u8, length as usize)
};
let bytes = data_slice.to_vec();
unsafe { sys::gvFreeRenderData(buffer_ptr) };
Ok(bytes)
}
pub fn render_to_writer<W: Write>(
context: &Context,
graph: &Graph,
format: Format,
mut writer: W,
) -> Result<(), GraphvizError> {
let bytes = render_to_bytes(context, graph, format)?;
writer.write_all(&bytes)?;
Ok(())
}
pub struct RenderOptions {
pub anti_alias: bool,
pub transparent: bool,
pub dpi: Option<f64>,
pub background: Option<String>,
pub show_bb: bool,
pub scale: Option<f64>,
pub size: Option<(f64, f64)>,
pub quality: Option<u32>,
}
impl Default for RenderOptions {
fn default() -> Self {
RenderOptions {
anti_alias: true,
transparent: false,
dpi: None,
background: None,
show_bb: false,
scale: None,
size: None,
quality: None,
}
}
}
impl RenderOptions {
pub fn new() -> Self {
Default::default()
}
pub fn apply(&self, graph: &Graph) -> Result<(), GraphvizError> {
if !self.anti_alias {
graph.set_attribute("smoothing", "0")?;
}
if self.transparent {
graph.set_attribute("bgcolor", "transparent")?;
}
if let Some(dpi) = self.dpi {
graph.set_attribute("dpi", &dpi.to_string())?;
}
if let Some(ref background) = self.background {
graph.set_attribute("bgcolor", background)?;
}
if self.show_bb {
graph.set_attribute("bb", "true")?;
}
if let Some(scale) = self.scale {
graph.set_attribute("scale", &scale.to_string())?;
}
if let Some((width, height)) = self.size {
graph.set_attribute("size", &format!("{},{}!", width, height))?;
}
if let Some(quality) = self.quality {
graph.set_attribute("quality", &quality.to_string())?;
}
Ok(())
}
pub fn with_anti_alias(mut self, anti_alias: bool) -> Self {
self.anti_alias = anti_alias;
self
}
pub fn with_transparency(mut self, transparent: bool) -> Self {
self.transparent = transparent;
self
}
pub fn with_dpi(mut self, dpi: f64) -> Self {
self.dpi = Some(dpi);
self
}
pub fn with_background(mut self, color: &str) -> Self {
self.background = Some(color.to_owned());
self
}
pub fn with_show_bb(mut self, show_bb: bool) -> Self {
self.show_bb = show_bb;
self
}
pub fn with_scale(mut self, scale: f64) -> Self {
self.scale = Some(scale);
self
}
pub fn with_size(mut self, width: f64, height: f64) -> Self {
self.size = Some((width, height));
self
}
pub fn with_quality(mut self, quality: u32) -> Self {
self.quality = Some(quality);
self
}
}