qpdf-sys 0.2.1

Rust bindings to QPDF C++ library via FFI and bindgen
use std::{env, path::PathBuf};

#[cfg(feature = "vendored")]
const ZLIB_SRC: &[&str] = &[
    "adler32.c",
    "compress.c",
    "crc32.c",
    "deflate.c",
    "infback.c",
    "inffast.c",
    "inflate.c",
    "inftrees.c",
    "trees.c",
    "uncompr.c",
    "zutil.c",
];

#[cfg(feature = "vendored")]
const JPEG_SRC: &[&str] = &[
    "jaricom.c",
    "jcapimin.c",
    "jcapistd.c",
    "jcarith.c",
    "jccoefct.c",
    "jccolor.c",
    "jcdctmgr.c",
    "jchuff.c",
    "jcinit.c",
    "jcmainct.c",
    "jcmarker.c",
    "jcmaster.c",
    "jcomapi.c",
    "jcparam.c",
    "jcprepct.c",
    "jcsample.c",
    "jctrans.c",
    "jdapimin.c",
    "jdapistd.c",
    "jdarith.c",
    "jdatadst.c",
    "jdatasrc.c",
    "jdcoefct.c",
    "jdcolor.c",
    "jddctmgr.c",
    "jdhuff.c",
    "jdinput.c",
    "jdmainct.c",
    "jdmarker.c",
    "jdmaster.c",
    "jdmerge.c",
    "jdpostct.c",
    "jdsample.c",
    "jdtrans.c",
    "jerror.c",
    "jfdctflt.c",
    "jfdctfst.c",
    "jfdctint.c",
    "jidctflt.c",
    "jidctfst.c",
    "jidctint.c",
    "jmemmgr.c",
    "jmemnobs.c",
    "jquant1.c",
    "jquant2.c",
    "jutils.c",
];

#[cfg(feature = "vendored")]
const QPDF_SRC: &[&str] = &[
    "AES_PDF_native.cc",
    "BitStream.cc",
    "BitWriter.cc",
    "Buffer.cc",
    "BufferInputSource.cc",
    "ClosedFileInputSource.cc",
    "ContentNormalizer.cc",
    "CryptoRandomDataProvider.cc",
    "FileInputSource.cc",
    "InputSource.cc",
    "InsecureRandomDataProvider.cc",
    "JSON.cc",
    "MD5_native.cc",
    "MD5.cc",
    "NNTree.cc",
    "OffsetInputSource.cc",
    "PDFVersion.cc",
    "Pipeline.cc",
    "Pl_AES_PDF.cc",
    "Pl_ASCII85Decoder.cc",
    "Pl_ASCIIHexDecoder.cc",
    "Pl_Buffer.cc",
    "Pl_Concatenate.cc",
    "Pl_Count.cc",
    "Pl_DCT.cc",
    "Pl_Discard.cc",
    "Pl_Flate.cc",
    "Pl_LZWDecoder.cc",
    "Pl_MD5.cc",
    "Pl_PNGFilter.cc",
    "Pl_QPDFTokenizer.cc",
    "Pl_RC4.cc",
    "Pl_RunLength.cc",
    "Pl_SHA2.cc",
    "Pl_StdioFile.cc",
    "Pl_TIFFPredictor.cc",
    "QPDF_Array.cc",
    "QPDF_Bool.cc",
    "QPDF_Dictionary.cc",
    "QPDF_encryption.cc",
    "QPDF_InlineImage.cc",
    "QPDF_Integer.cc",
    "QPDF_linearization.cc",
    "QPDF_Name.cc",
    "QPDF_Null.cc",
    "QPDF_Operator.cc",
    "QPDF_optimization.cc",
    "QPDF_pages.cc",
    "QPDF_Real.cc",
    "QPDF_Reserved.cc",
    "QPDF_Stream.cc",
    "QPDF_String.cc",
    "qpdf-c.cc",
    "QPDF.cc",
    "QPDFAcroFormDocumentHelper.cc",
    "QPDFAnnotationObjectHelper.cc",
    "QPDFCrypto_native.cc",
    "QPDFCryptoProvider.cc",
    "QPDFEFStreamObjectHelper.cc",
    "QPDFEmbeddedFileDocumentHelper.cc",
    "QPDFExc.cc",
    "QPDFFileSpecObjectHelper.cc",
    "QPDFFormFieldObjectHelper.cc",
    "QPDFMatrix.cc",
    "QPDFNameTreeObjectHelper.cc",
    "QPDFNumberTreeObjectHelper.cc",
    "QPDFObject.cc",
    "QPDFObjectHandle.cc",
    "QPDFObjGen.cc",
    "QPDFOutlineDocumentHelper.cc",
    "QPDFOutlineObjectHelper.cc",
    "QPDFPageDocumentHelper.cc",
    "QPDFPageLabelDocumentHelper.cc",
    "QPDFPageObjectHelper.cc",
    "QPDFStreamFilter.cc",
    "QPDFSystemError.cc",
    "QPDFTokenizer.cc",
    "QPDFWriter.cc",
    "QPDFXRefEntry.cc",
    "QTC.cc",
    "QUtil.cc",
    "RC4_native.cc",
    "RC4.cc",
    "ResourceFinder.cc",
    "rijndael.cc",
    "SecureRandomDataProvider.cc",
    "SF_FlateLzwDecode.cc",
    "SHA2_native.cc",
    "SparseOHArray.cc",
];

#[cfg(feature = "vendored")]
fn base_build() -> cc::Build {
    let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let mut build = cc::Build::new();

    build
        .warnings(false)
        .extra_warnings(false)
        .include(root.join("include"));

    build
}

#[cfg(feature = "vendored")]
fn is_msvc() -> bool {
    env::var("TARGET").unwrap().ends_with("-msvc")
}

#[cfg(feature = "vendored")]
fn build_cc(name: &str, dir: &str, files: &[&str]) {
    let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let path = root.join(dir);

    let cpp_flags: &[&str] = if is_msvc() { &["/D_CRT_SECURE_NO_WARNINGS"] } else { &[] };

    let mut build = base_build();
    for flag in cpp_flags {
        build.flag(flag);
    }

    build
        .include(&path)
        .files(files.iter().map(|f| path.join(f)).collect::<Vec<_>>())
        .compile(name);
}

#[cfg(feature = "vendored")]
fn build_qpdf() {
    let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let cpp_flags: &[&str] = if is_msvc() {
        &["/std:c++14", "/EHsc"]
    } else {
        &["-std=c++14"]
    };

    let mut build = base_build();
    for flag in cpp_flags {
        build.flag(flag);
    }

    build
        .cpp(true)
        .include(root.join("zlib-1.2.11"))
        .include(root.join("jpeg-9d"))
        .include(root.join("qpdf").join("include"))
        .include(root.join("qpdf").join("libqpdf"))
        .files(
            QPDF_SRC
                .iter()
                .map(|f| root.join("qpdf").join("libqpdf").join(f))
                .collect::<Vec<_>>(),
        )
        .compile("qpdf");

    build_cc("sha2", "qpdf/libqpdf", &["sha2.c", "sha2big.c"]);
}

#[cfg(feature = "vendored")]
fn build_bindings() {
    let root = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");
    let existing = root
        .join("bindings")
        .join(format!("{}.rs", env::var("TARGET").unwrap()));

    if existing.exists() {
        std::fs::copy(&existing, &out_path).unwrap();
    } else {
        let path = root.join("qpdf").join("include");
        let bindings = bindgen::builder()
            .clang_arg(format!("-I{}", path.display()))
            .header(format!("{}/qpdf/qpdf-c.h", path.display()))
            .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
            .generate()
            .unwrap();

        bindings.write_to_file(&out_path).expect("Couldn't write bindings!");
    }
}

#[cfg(feature = "vendored")]
fn main() {
    build_bindings();
    build_cc("zlib", "zlib-1.2.11", ZLIB_SRC);
    build_cc("jpeg", "jpeg-9d", JPEG_SRC);
    build_qpdf();
}

#[cfg(not(feature = "vendored"))]
fn main() {
    let lib = pkg_config::Config::new()
        .atleast_version("10.6.3")
        .probe("libqpdf")
        .unwrap();

    let mut builder = bindgen::builder();

    for path in lib.include_paths {
        builder = builder.clang_arg(format!("-I{}", path.to_str().unwrap()));

        let header_path = path.join("qpdf/qpdf-c.h");
        if header_path.exists() {
            builder = builder.header(header_path.into_os_string().into_string().unwrap());
        }
    }

    for (key, val) in lib.defines {
        builder = builder.clang_arg(match val {
            Some(val) => format!("-D{}={}", key, val),
            None => format!("-D{}", key),
        });
    }

    let bindings = builder
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .generate()
        .unwrap();

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs");

    bindings.write_to_file(&out_path).unwrap();
}