include-pnm 1.0.0

Include PNM images directly in Rust code.
Documentation
//! Embed PNM images directly into Rust code.
//!
//! This supports *most* PNM files in a mostly reasonable way. The idea here is that PNM is a very
//! simple format that is also compressible enough to embed directly into version control
//! repositories, and so, very suited to loading in at compile time.
//!
//! Most PNM images are supported, with the primary exception applying to PPM images and 3-channel
//! PAM images: these must have a `MAXVAL` of 255 or 65535 in order to be loaded at all.
//!
//! # Conversions
//!
//! Similar to `netpbm`, all formats of PNM images are supported, and then converted to the result
//! depending on the mode requested. Color space is completely ignored when converting between
//! formats, and conversion is only allowed in the forward direction, where e.g. grayscale images
//! can be converted to RGB by duplicating values three times.
//!
//! Similarly, conversion from 8-bit color to 16-bit color is done under the assumption that the
//! scaling does not change, and all values get scaled by 65535/255.
//!
//! # "Indexed" images
//!
//! While PGM and 1-channel PAM images may have any allowed `MAXVAL`, images without a `MAXVAL` of
//! 1, 255, or 65535 may not be converted into any other color format, under the assumption that
//! they were intended to reference palette indices instead of colors.
//!
//! # Tracking included images
//!
//! On stable Rust, you must add a build script and output `cargo:rerun-if-changed=path` for each
//! included image to ensure that your crate recompiles whenever the image files change. However,
//! on nightly Rust with the `nightly` feature enabled, the unstable `track_path` feature will be
//! used so this happens automatically.
#![cfg_attr(feature = "nightly", feature(track_path))]
use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};

mod args;
mod cold;
mod formats;
mod metadata;
mod parse;
use self::{
    metadata::DepthChannels,
    parse::{Magic, Reader},
};
use crate::formats::Nesting;

/// Load PNM file and output it as an array.
fn include_file(depth_channels: DepthChannels, nesting: Nesting, path: String) -> TokenStream {
    let span = Span::call_site();

    // rust-analyzer does not implement `Span::local_file` at all, so, detect this and just
    // return something that will compile instead.
    if span.file().is_empty() && span.local_file().is_none() {
        eprintln!("warning: rust-analyzer does not implement `Span::local_file`");
        eprintln!(
            "warning: see this issue: https://github.com/rust-lang/rust-analyzer/issues/1595"
        );

        // unsafe { ::core::mem::zeroed() }
        let mut result = TokenStream::new();
        result.extend(Some(TokenTree::Ident(Ident::new("unsafe", span))));
        {
            let mut block = TokenStream::new();
            block.extend(Some(TokenTree::Punct(Punct::new(':', Spacing::Alone))));
            block.extend(Some(TokenTree::Punct(Punct::new(':', Spacing::Joint))));
            block.extend(Some(TokenTree::Ident(Ident::new("core", span))));
            block.extend(Some(TokenTree::Punct(Punct::new(':', Spacing::Alone))));
            block.extend(Some(TokenTree::Punct(Punct::new(':', Spacing::Joint))));
            block.extend(Some(TokenTree::Ident(Ident::new("mem", span))));
            block.extend(Some(TokenTree::Punct(Punct::new(':', Spacing::Alone))));
            block.extend(Some(TokenTree::Punct(Punct::new(':', Spacing::Joint))));
            block.extend(Some(TokenTree::Ident(Ident::new("zeroed", span))));
            block.extend(Some(TokenTree::Group(Group::new(
                Delimiter::Parenthesis,
                TokenStream::new(),
            ))));
            result.extend(Some(TokenTree::Group(Group::new(Delimiter::Brace, block))));
        }
        result
    } else {
        #[cfg(feature = "nightly")]
        {
            proc_macro::tracked_path::path(&path);
        }
        let mut reader = Reader::open(&path);
        match reader.read_magic() {
            Magic::P1 => formats::load_plain_pbm(&mut reader)
                .convert(depth_channels)
                .into_tokens(nesting),
            Magic::P2 => formats::load_plain_pgm(&mut reader)
                .convert(depth_channels)
                .into_tokens(nesting),
            Magic::P3 => formats::load_plain_ppm(&mut reader)
                .convert(depth_channels)
                .into_tokens(nesting),
            Magic::P4 => formats::load_pbm(&mut reader)
                .convert(depth_channels)
                .into_tokens(nesting),
            Magic::P5 => formats::load_pgm(&mut reader)
                .convert(depth_channels)
                .into_tokens(nesting),
            Magic::P6 => formats::load_ppm(&mut reader)
                .convert(depth_channels)
                .into_tokens(nesting),
            Magic::P7 => formats::load_pam(&mut reader)
                .convert(depth_channels)
                .into_tokens(nesting),
        }
    }
}

/// Include a PAM image as a 2D array of the specified type and number of channels.
///
/// Should be called as `include_pnm!(ty, filename)`.
///
/// Supported types are:
///
/// * `[bool; 1]` (`include_pbm`)
/// * `[bool; 2]` (`include_pbma`)
/// * `[u8; 1]` (`include_pgm`)
/// * `[u8; 2]` (`include_pgma`)
/// * `[u8; 3]` (`include_ppm`)
/// * `[u8; 4]` (`include_ppma`)
/// * `[u16; 1]` (`include_pgm16`)
/// * `[u16; 2]` (`include_pgma16`)
/// * `[u16; 3]` (`include_ppm16`)
/// * `[u16; 4]` (`include_ppma16`)
#[proc_macro]
pub fn include_pnm(input: TokenStream) -> TokenStream {
    let caller = "include_pnm";
    let mut input = input.into_iter();
    let depth_channels = args::get_depth_channels(caller, &mut input);
    let file = args::get_file(caller, &mut input, Some(args::LeadingComma));
    include_file(depth_channels, Nesting::Arrays, file)
}

/// Include a PBM image as a 2D array of `bool`.
///
/// Requires PAM images which can be converted to `BLACKANDWHITE` format.
#[proc_macro]
pub fn include_pbm(input: TokenStream) -> TokenStream {
    let file = args::get_file("include_pbm", &mut input.into_iter(), None);
    include_file(DepthChannels::BlackAndWhite, Nesting::Inline, file)
}

/// Include a "PBMA" image as a 2D array of `[bool; 2]`.
///
/// Requires PAM images which can be converted to `BLACKANDWHITE_ALPHA` format.
#[proc_macro]
pub fn include_pbma(input: TokenStream) -> TokenStream {
    let file = args::get_file("include_pbma", &mut input.into_iter(), None);
    include_file(DepthChannels::BlackAndWhiteAlpha, Nesting::Inline, file)
}

/// Include a PGM image as a 2D array of `u8`.
///
/// Requires PAM images which can be converted to `GRAYSCALE` format.
#[proc_macro]
pub fn include_pgm(input: TokenStream) -> TokenStream {
    let file = args::get_file("include_pgm", &mut input.into_iter(), None);
    include_file(DepthChannels::Grayscale, Nesting::Inline, file)
}

/// Include a "PGMA" image as a 2D array of `[u8; 2]`.
///
/// Requires PAM images which can be converted to `GRAYSCALE_ALPHA` format.
#[proc_macro]
pub fn include_pgma(input: TokenStream) -> TokenStream {
    let file = args::get_file("include_pgma", &mut input.into_iter(), None);
    include_file(DepthChannels::GrayscaleAlpha, Nesting::Inline, file)
}

/// Include a PGM image as a 2D array of `u16`.
///
/// Requires PAM images which can be converted to `GRAYSCALE` format.
#[proc_macro]
pub fn include_pgm16(input: TokenStream) -> TokenStream {
    let file = args::get_file("include_pgm16", &mut input.into_iter(), None);
    include_file(DepthChannels::Grayscale16, Nesting::Inline, file)
}

/// Include a "PGMA" image as a 2D array of `[u16; 2]`.
///
/// Requires PAM images which can be converted to `GRAYSCALE_ALPHA` format.
#[proc_macro]
pub fn include_pgma16(input: TokenStream) -> TokenStream {
    let file = args::get_file("include_pgma", &mut input.into_iter(), None);
    include_file(DepthChannels::GrayscaleAlpha16, Nesting::Inline, file)
}

/// Include a PPM image as a 2D array of `[u8; 3]`.
///
/// Requires PAM images which can be converted to `RGB` format.
#[proc_macro]
pub fn include_ppm(input: TokenStream) -> TokenStream {
    let file = args::get_file("include_ppm", &mut input.into_iter(), None);
    include_file(DepthChannels::Rgb, Nesting::Inline, file)
}

/// Include a "PPMA" image as a 2D array of `[u8; 4]`.
///
/// Requires PAM images which can be converted to `RGB_ALPHA` format.
#[proc_macro]
pub fn include_ppma(input: TokenStream) -> TokenStream {
    let file = args::get_file("include_ppm", &mut input.into_iter(), None);
    include_file(DepthChannels::RgbAlpha, Nesting::Inline, file)
}

/// Include a PPM image as a 2D array of `[u16; 3]`.
///
/// Requires PAM images which can be converted to `RGB` format.
#[proc_macro]
pub fn include_ppm16(input: TokenStream) -> TokenStream {
    let file = args::get_file("include_ppm16", &mut input.into_iter(), None);
    include_file(DepthChannels::Rgb16, Nesting::Inline, file)
}

/// Include a "PPMA" image as a 2D array of `[u16; 4]`.
///
/// Requires PAM images which can be converted to `RGB_ALPHA` format.
#[proc_macro]
pub fn include_ppma16(input: TokenStream) -> TokenStream {
    let file = args::get_file("include_ppm16", &mut input.into_iter(), None);
    include_file(DepthChannels::RgbAlpha16, Nesting::Inline, file)
}