nkscan 0.11.0

A platform-agnostic, performant driver for Nikon film scanners
Documentation
//! The capabilities a scanner advertises
//!
//! Every decision we make in operation will revolve around what a scanner says it can do, rather than trying to map a priori capabilities from known scanners
//! These are primarily built from the "page code field list" data starting from table 2-2-1-2

pub mod address;
pub mod ccd;
pub mod film;
pub mod frames;
pub mod identity;
pub mod other;
pub mod set_window;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("page {page:02X}h truncated: need {need} bytes, got {got}")]
    Truncated { page: u8, need: usize, got: usize },

    #[error("page {page:02X}h byte {byte}: {what} = {value:#04x}")]
    BadField {
        page: u8,
        byte: usize,
        what: &'static str,
        value: u32,
    },
}

#[derive(Debug)]
/// All of the capabilities the attached scanner reports
pub struct Capabilities {
    pub identity: identity::Identity,
    pub address: address::Address,
    pub features: other::Features,
    pub set_window: set_window::SetWindowFunction,
    /// Missing from the page-00h list, so a unit may genuinely not have it
    pub ccd: Option<ccd::CcdMeasurement>,
    /// Published only when `Address` byte 16 sets `FRAME_RECTS`
    pub frames: Option<frames::Frames>,
}

impl Capabilities {
    /// Whether the unit can read its CCD lines at once
    ///
    /// The SET WINDOW page is what offers the mode. `MULTI_LINE` asks a
    /// different question, whether the host has to re-register the rows
    /// afterwards, and a unit can do the first without ever asking for it
    pub fn reads_lines_at_once(&self) -> bool {
        self.address.lines > 1
            && self
                .set_window
                .interleaving
                .contains(set_window::ColorInterleaving::MULTILINE_SIMULTANEOUS)
    }

    /// Whether the unit can read its CCD lines at once at `dpi`
    ///
    /// The CCD rows are [`registration_gap`](address::Address::registration_gap)
    /// output lines apart. If the scanning pitch is larger than the line gap,
    /// that distance is zero. The rows then give the same output line, and the
    /// decoder cannot separate them.
    ///
    /// A unit with a gap of one line reads its rows at once only at the optical
    /// resolution.
    pub fn reads_lines_at_once_at(&self, dpi: u16) -> bool {
        self.reads_lines_at_once() && self.address.registration_gap(dpi) > 0
    }
}

/// One page from "Vital Product Data"
#[derive(Debug)]
pub struct Page {
    code: u8,
    bytes: Vec<u8>,
}

/// Utilities for reading values off the page
impl Page {
    /// Build a new page from VPD data
    pub fn new(code: u8, bytes: Vec<u8>) -> Result<Self, Error> {
        // byte 3 is the page length, so anything shorter is not a page
        if bytes.len() < 4 {
            return Err(Error::Truncated {
                page: code,
                need: 4,
                got: bytes.len(),
            });
        }
        // asked for one page, got another
        if bytes[1] != code {
            return Err(Error::BadField {
                page: code,
                byte: 1,
                what: "page code",
                value: bytes[1] as u32,
            });
        }
        Ok(Self { code, bytes })
    }

    fn array<const N: usize>(&self, i: usize) -> Result<[u8; N], Error> {
        self.bytes
            .get(i..i + N)
            .and_then(|s| s.try_into().ok())
            .ok_or_else(|| Error::Truncated {
                page: self.code,
                need: i + N,
                got: self.bytes.len(),
            })
    }

    fn u8(&self, i: usize) -> Result<u8, Error> {
        Ok(self.array::<1>(i)?[0])
    }

    fn be16(&self, i: usize) -> Result<u16, Error> {
        Ok(u16::from_be_bytes(self.array(i)?))
    }

    fn be32(&self, i: usize) -> Result<u32, Error> {
        Ok(u32::from_be_bytes(self.array(i)?))
    }

    /// A big-endian integer `len` bytes wide, where the unit gave the width
    fn be(&self, i: usize, len: usize) -> Result<u32, Error> {
        if len == 0 || len > 4 {
            return Err(Error::BadField {
                page: self.code,
                byte: i,
                what: "parameter width",
                value: len as u32,
            });
        }
        let mut value = 0u32;
        for n in 0..len {
            value = value << 8 | u32::from(self.u8(i + n)?);
        }
        Ok(value)
    }

    /// How many bytes the page says it holds
    ///
    /// Clamped to what arrived, since a unit declaring more than it sent is no
    /// license to read past the buffer
    fn declared_len(&self) -> usize {
        (4 + usize::from(self.bytes[3])).min(self.bytes.len())
    }

    /// The byte at `i`, if the page is long enough to carry it
    ///
    /// Several fields are documented to take a default when the unit "sends no
    /// value", which is a page that ends before them rather than a short read.
    /// Reading one off the buffer anyway takes whatever the transport left
    /// there, and a padded read looks exactly like data
    fn carried_u8(&self, i: usize) -> Option<u8> {
        match i < self.declared_len() {
            true => self.u8(i).ok(),
            false => None,
        }
    }

    /// A run of bit flags, as the bits and the bytes they took
    ///
    /// Bit 7 of each byte is the extend bit: set means the field carries on
    /// into the next one. How long it is therefore comes from the unit, and so
    /// does where every field after it starts. A spec prints the byte numbers
    /// of the unit it documents, which is one instance of the layout rather
    /// than the layout itself.
    ///
    /// The extend bits stay in the returned value; no flag is defined on one,
    /// so they truncate away with the rest of the reserved bits
    fn flags(&self, i: usize) -> Result<(u64, usize), Error> {
        let mut bits = 0u64;
        for n in 0..8 {
            let byte = self.u8(i + n)?;
            bits |= u64::from(byte) << (8 * n);
            if byte & 0x80 == 0 {
                return Ok((bits, n + 1));
            }
        }
        // Eight bytes is every bit a u64 holds, so a ninth has nowhere to go
        Err(Error::BadField {
            page: self.code,
            byte: i + 7,
            what: "flags run past 8 bytes",
            value: bits as u32,
        })
    }

    /// Zero means "absent" in several fields on this page
    fn opt_u8(&self, i: usize) -> Result<Option<u8>, Error> {
        Ok(Some(self.u8(i)?).filter(|&v| v != 0))
    }

    fn opt_be16(&self, i: usize) -> Result<Option<u16>, Error> {
        Ok(Some(self.be16(i)?).filter(|&v| v != 0))
    }
}

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

    /// Reading past what arrived is an error, not a panic. The one place
    /// this is checked, since every page goes through these accessors
    #[test]
    fn a_short_page_errors_rather_than_panicking() {
        let page = Page::new(0xC1, vec![0x06, 0xC1, 0x00, 0x04]).unwrap();
        assert!(matches!(
            page.be32(4),
            Err(Error::Truncated {
                need: 8,
                got: 4,
                ..
            })
        ));
    }

    /// Asking for one page and getting another
    #[test]
    fn a_mismatched_page_code_is_refused() {
        assert!(matches!(
            Page::new(0xC1, vec![0x06, 0xD1, 0x00, 0x04]),
            Err(Error::BadField { byte: 1, .. })
        ));
    }
}