1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
//! Rust bindings to [pstoedit](htp://pstoedit.net).
//!
//! This crate contains Rust bindings to pstoedit, a C++ program that can
//! translate PostScript and PDF graphics into other vector formats.
//!
//! # Usage
//! First, the [`init`] function must be called. Then, interaction with pstoedit
//! is possible using [`Command`] or [`DriverInfo`].
//!
//! # Examples
//! ```no_run
//! use pstoedit::{DriverInfo, Command};
//!
//! pstoedit::init()?;
//!
//! // For every driver ...
//! for driver in &DriverInfo::get()? {
//!     let format = driver.symbolic_name()?;
//!     let extension = driver.extension()?;
//!     let output_name = format!("output-{}.{}", format, extension);
//!
//!     // ... convert input.ps to that format
//!     Command::new().args_slice(&["-f", format, "input.ps"])?.arg(output_name)?.run()?;
//! }
//! # Ok::<(), pstoedit::Error>(())
//! ```

mod command;
pub mod driver_info;
mod error;

use pstoedit_sys as ffi;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::ptr;

pub use command::Command;
pub use driver_info::DriverInfo;
pub use error::{Error, Result};

#[cfg(feature = "smallvec")]
type SmallVec<T> = smallvec::SmallVec<[T; 5]>;
#[cfg(feature = "smallvec")]
use smallvec::smallvec;
#[cfg(not(feature = "smallvec"))]
type SmallVec<T> = Vec<T>;
#[cfg(not(feature = "smallvec"))]
use vec as smallvec;

/// Initialize connection to pstoedit. Must be called before calling any other
/// function that requires a connection to pstoedit.
///
/// # Examples
/// See [`Command`][Command#examples].
///
/// # Errors
/// [`IncompatibleVersion`][Error::IncompatibleVersion] if the version of
/// pstoedit is not compatible with this crate.
pub fn init() -> Result<()> {
    if unsafe { ffi::pstoedit_checkversion(ffi::pstoeditdllversion) } != 0 {
        Ok(())
    } else {
        Err(Error::IncompatibleVersion)
    }
}

/// Thin safe wrapper to main pstoedit API.
///
/// Safety is ensured using the invariants of [`CStr`].
fn pstoedit_cstr<S, T>(argv: &[S], gs: Option<T>) -> Result<()>
where
    S: AsRef<CStr>,
    T: AsRef<CStr>,
{
    let argv: SmallVec<_> = argv.iter().map(|s| s.as_ref().as_ptr()).collect();
    // First as_ref is required to prevent move and drop if T = CString
    let gs = gs.as_ref().map_or(ptr::null(), |s| s.as_ref().as_ptr());
    // Safety: due to CStr input arguments it is ensured they are valid C strings
    unsafe { pstoedit_raw(&argv, gs) }
}

/// Thin wrapper to main pstoedit API that sets `argc` and converts errors.
///
/// # Safety
/// All pointers must be valid C strings; `gs` may be null.
unsafe fn pstoedit_raw(argv: &[*const c_char], gs: *const c_char) -> Result<()> {
    debug_assert!(argv.len() <= c_int::MAX as usize);
    let argc = argv.len() as c_int;
    pstoedit_result(ffi::pstoedit_plainC(argc, argv.as_ptr(), gs))
}

/// Interpret pstoedit return value as result.
fn pstoedit_result(error_code: c_int) -> Result<()> {
    match error_code {
        0 => Ok(()),
        -1 => Err(Error::NotInitialized),
        err => Err(Error::PstoeditError(err)),
    }
}

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

    #[test]
    fn test_init() {
        init().unwrap();
    }
}