use super::{
boundaries::Polarity,
pass::{Pass, Progress},
thumbnail,
};
use crate::{
error::Error,
protocol::{
caps::{Capabilities, address::CoordinateBase, film::FilmFormat, other::DataTypes},
data::{Boundary, FrameTable, Op, Rect},
decode::Samples,
},
session::Session,
};
use std::ops::ControlFlow;
use tracing::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Framing {
Published,
Thumbnail,
Perforation,
Address,
}
impl Framing {
pub fn choose(caps: &Capabilities) -> Self {
let rects = caps
.address
.coordinate_base
.contains(CoordinateBase::FRAME_RECTS);
if rects {
if caps.frames.as_ref().is_some_and(|f| f.measured()) {
return Self::Published;
}
if thumbnail::available(caps) {
return Self::Thumbnail;
}
return Self::Address;
}
if caps
.features
.data_types
.contains(DataTypes::PERFORATION_READ)
{
return Self::Perforation;
}
Self::Address
}
}
pub(crate) fn reachable(caps: &Capabilities, extent: u32) -> Result<(), Error> {
if extent == 0 {
return Err(Error::Unsupported {
op: "frame table",
reason: "a frame of length 0 is not a frame".into(),
});
}
let limit = caps.address.y_axis.boundary;
match extent > limit {
true => Err(Error::Unsupported {
op: "frame table",
reason: format!(
"a frame of {extent} is past the {limit} boundary and would stall the stage"
),
}),
false => Ok(()),
}
}
pub fn table(caps: &Capabilities) -> Result<Boundary, Error> {
let Some(published) = caps.frames.as_ref() else {
return Ok(Boundary::default());
};
let mut frames = Vec::new();
for image in &published.images {
let Some(extent) = image.length else {
continue;
};
let extent = super::window::whole_blocks(caps, extent);
reachable(caps, extent)?;
frames.push(Rect {
top: image.top,
left: image.left,
bottom: image.top + extent,
right: image.left + image.width,
});
}
Ok(Boundary { frames })
}
pub fn frames_on_medium(caps: &Capabilities) -> u32 {
let y = &caps.address.y_axis;
let span = y.address_range.last.saturating_sub(y.address_range.start) + 1;
match y.boundary {
0 => 1,
boundary => (span / boundary).max(1),
}
}
pub fn self_feeding(caps: &Capabilities) -> bool {
caps.features.execute.supports(Op::Load) && frames_on_medium(caps) == 1
}
pub fn frames(caps: &Capabilities) -> Result<Boundary, Error> {
let (x, y) = (&caps.address.x_axis, &caps.address.y_axis);
let extent = super::window::reachable_blocks(caps, y.boundary);
reachable(caps, extent)?;
let count = frames_on_medium(caps);
debug!(
count,
pitch = y.boundary,
extent,
"framed from the address page"
);
let frames = (0..count)
.map(|n| {
let top = y.address_range.start + n * y.boundary;
Rect {
top,
left: x.address_range.start,
bottom: top + extent,
right: x.address_range.start + x.boundary,
}
})
.collect();
Ok(Boundary { frames })
}
pub struct Discovery {
pub table: FrameTable,
pub frames: Vec<Rect>,
pub thumbnail: Option<Pass>,
}
pub fn discover(
session: &mut Session,
format: Option<FilmFormat>,
polarity: Polarity,
samples: &mut Samples,
) -> Result<Discovery, Error> {
discover_with(session, format, polarity, samples, |_| {
ControlFlow::Continue(())
})
}
pub fn discover_with(
session: &mut Session,
format: Option<FilmFormat>,
polarity: Polarity,
samples: &mut Samples,
on: impl FnMut(Progress) -> ControlFlow<()>,
) -> Result<Discovery, Error> {
let need_format = |session: &Session| FilmFormat::resolve(format, session.capabilities());
let mechanism = Framing::choose(session.capabilities());
debug!(?mechanism, "frame discovery");
match mechanism {
Framing::Published => {
let boundary = table(session.capabilities())?;
let found = boundary.frames.clone();
info!(frames = found.len(), "published frames");
Ok(Discovery {
table: FrameTable::Boundary(boundary),
frames: found,
thumbnail: None,
})
}
Framing::Thumbnail => {
let format = need_format(session)?;
let pass = session.scan_thumbnail_with(samples, on)?;
debug!(
rows = pass.rows,
cols = pass.cols,
complete = pass.complete,
"thumbnail"
);
let optical_dpi = session.capabilities().address.y_axis.optical_dpi;
let length = format.height_dots(optical_dpi);
info!(?format, length, "frame length");
let measured =
thumbnail::frames(session.capabilities(), &pass, samples, length, polarity)?;
if !measured.frames.is_empty() {
session.set_boundaries(&measured)?;
}
let found = measured.frames.clone();
info!(frames = found.len(), "detected frames");
Ok(Discovery {
table: FrameTable::Boundary(measured),
frames: found,
thumbnail: Some(pass),
})
}
Framing::Address => {
let boundary = frames(session.capabilities())?;
let found = boundary.frames.clone();
Ok(Discovery {
table: FrameTable::Boundary(boundary),
frames: found,
thumbnail: None,
})
}
Framing::Perforation => {
let format = need_format(session)?;
let _ = session.read_perforations()?;
let _ = session.read_boundaries_type2();
let pass = session.scan_thumbnail_with(samples, on)?;
debug!(
rows = pass.rows,
cols = pass.cols,
complete = pass.complete,
"thumbnail"
);
let optical_dpi = session.capabilities().address.y_axis.optical_dpi;
let length = format.height_dots(optical_dpi);
info!(?format, length, "frame length");
let perfs = session.read_perforations()?;
let (measured, length) = thumbnail::frames_type2(
session.capabilities(),
&pass,
samples,
&perfs,
length,
polarity,
)?;
if !measured.frames.is_empty() {
session.set_boundaries_type2(&measured)?;
}
let x_start = session.capabilities().address.x_axis.address_range.start;
let x_boundary = session.capabilities().address.x_axis.boundary;
let found = measured
.frames
.iter()
.map(|f| f.rect(x_start, x_boundary, length))
.collect::<Vec<_>>();
info!(frames = found.len(), "detected frames");
Ok(Discovery {
table: FrameTable::BoundaryType2(measured),
frames: found,
thumbnail: Some(pass),
})
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scan::window::tests::caps;
#[test]
fn a_medium_longer_than_the_gate_is_framed_along_its_range() {
let mut caps = caps();
assert_eq!(frames(&caps).expect("frames").frames.len(), 1);
caps.address.y_axis.address_range = (0..=111324).into();
caps.address.y_axis.boundary = 4453;
caps.address.line_gap = 0;
let found = frames(&caps).expect("frames").frames;
assert_eq!(found.len(), 25);
assert_eq!(found[0].bottom - found[0].top, 4453);
assert_eq!(found[24].top, 24 * 4453);
}
}