// The streaming decoder.
//
// A PNG image (W3C PNG Third Edition) is an 8-byte signature and a sequence
// of chunks, each a 4-byte length, a 4-byte type, the data, and a CRC-32 of
// the type and the data. The decoder reads a chunk in three units: its head
// (length and type) and its CRC gather in `held`, at most 8 bytes, and its
// data passes through, into IHDR's and PLTE's fields, into the zlib decoder
// for IDAT, or only into the CRC for a chunk it skips. Where a chunk may come
// is decided by the pure functions of `@rules` (table 7).
//
// The IDAT chunks' data is one zlib stream, which the decoder inflates into
// the current row's buffer: the filter type byte and the row's bytes. Once the
// row is whole its filter is reversed against the previous row, the two
// buffers swap, and the row waits there for the caller's slice. The previous
// row starts as zeros at each pass. An interlaced image comes out pass by
// pass (Adam7); a pass with no pixels has no rows, not even their filter
// bytes. The zlib decoder writes a whole row with `step_into`, which fills
// an array from its start: when the row buffer is exactly the row (an image
// that is not interlaced, or Adam7's last pass), and the row begins in this
// call. A row of a narrower pass, or one that a previous call began, goes
// through `step` into a view of the buffer. So the decoder holds two rows and the zlib decoder, allocated once
// after IHDR, whatever the image's height.
//
// The answers follow the image contract of `gaato/codec_image`: `Header` at
// IHDR, `Frame` at the first IDAT, `Rows`, and `FrameEnd` at the first chunk
// after the IDAT chunks, once every IDAT's CRC and the zlib stream's
// Adler-32 have been checked. A step that wrote rows stops before anything
// else would be reported; an error found after them is raised by the next
// step.

///|
priv enum State {
  /// The 8-byte signature, checked byte by byte as it comes.
  Signature
  /// IHDR has been reported: the rows' buffers come next.
  Allocate
  /// A chunk's length and type.
  Head
  /// A chunk's head is read and not yet acted on.
  Dispatch
  /// IHDR's 13 bytes.
  HeaderData
  /// PLTE's bytes.
  PaletteData
  /// An fdAT's sequence number.
  Sequence
  /// An IDAT's or an fdAT's frame data, through the zlib decoder.
  ImageData
  /// The bytes of a chunk that is read only through its CRC.
  Skip
  /// The bytes of an ancillary chunk read into fields, or into a block to
  /// report, or both.
  Collect
  /// A reported chunk's block, waiting for room.
  Emit
  /// A chunk's CRC.
  Crc
  /// IEND is checked; under `EndOfInput`, `Done` waits for `end`.
  Ended
}

///|
/// Why a run of the units stopped early, inside the decoder.
priv suberror Stop {
  /// The current unit needs bytes the input does not have yet.
  NeedInput
  /// The image is refused.
  Fail(@codec.ErrorKind, String)
}

///|
/// The 8 bytes every PNG image begins with (5.2).
let signature : FixedArray[Byte] = [
  b'\x89', b'P', b'N', b'G', b'\r', b'\n', b'\x1a', b'\n',
]

///|
/// The longest a chunk's data can be: 2^31 − 1 bytes (5.3).
const MAX_LENGTH : UInt32 = 0x7FFFFFFF

///|
/// The most `max_row_bytes` can be, so that two rows and the zlib decoder fit
/// in what `memory_usage` can count.
const MAX_ROW_BYTES : Int32 = 1_000_000_000

///|
/// The most `max_chunk_bytes` can be, for the same reason.
const MAX_CHUNK_BYTES : Int32 = 100_000_000

///|
/// The color type of an image (table 11): how its pixels' samples are laid
/// out in a row.
pub(all) enum ColorType {
  /// One sample a pixel, a gray level: color type 0.
  Grayscale
  /// Red, green and blue samples: color type 2.
  Truecolor
  /// One index into the palette (`palette`) a pixel: color type 3.
  IndexedColor
  /// A gray level and an alpha sample: color type 4.
  GrayscaleAlpha
  /// Red, green, blue and alpha samples: color type 6.
  TruecolorAlpha
} derive(Eq, @debug.Debug)

///|
/// `ColorType::equal` and `ColorType::not_equal`, the derived comparison.
pub extend ColorType with Eq::{equal, not_equal}

///|
/// `ColorType::to_repr`, the derived debug representation.
pub extend ColorType with @debug.Debug::{to_repr}

///|
/// A streaming PNG decoder. Feed it input in chunks of any size with `step`
/// and collect whole rows in slices of at least `room_needed` bytes; it holds
/// two rows and a zlib decoder (a 32 KiB window and its tables), never the
/// whole image.
pub struct Decoder {
  priv max_row_bytes : Int32
  priv until : @codec_image.Until
  // The IDAT chunks' zlib stream.
  priv inflater : @zlib.Decoder
  // The signature, a chunk's head or its CRC, gathered so far.
  priv held : FixedArray[Byte]
  priv mut held_len : Int32
  // IHDR's bytes and what they say.
  priv ihdr : FixedArray[Byte]
  priv mut ihdr_len : Int32
  priv mut width : Int32
  priv mut height : Int32
  priv mut bit_depth : Int32
  priv mut color_type : Int32
  priv mut interlaced : Bool
  // Bits a pixel, and bytes a whole pixel (at least 1).
  priv mut bits : Int32
  priv mut unit : Int32
  // PLTE's bytes, and the entries the image may use.
  priv plte : FixedArray[Byte]
  priv mut plte_len : Int32
  priv mut entries : Int32
  // The chunks so far, as table 7 sees them, and the current one.
  priv mut state : State
  priv mut position : @rules.Position
  priv mut seen : UInt32
  priv mut chunk : UInt32
  priv mut index : Int32
  priv mut left : Int32
  priv mut crc : UInt32
  priv mut ignoring : Bool
  priv mut ignored : Int32
  // The rows: the previous one, and the current one with its filter type
  // byte first; once whole and unfiltered it is `prev`, waiting (`ready`).
  priv mut prev : FixedArray[Byte]
  priv mut cur : FixedArray[Byte]
  priv mut cur_len : Int32
  priv mut ready : Bool
  priv mut framed : Bool
  priv mut in_frame : Bool
  priv mut pass : Int32
  priv mut pass_rows : Int32
  priv mut pass_bytes : Int32
  priv mut row : Int32
  priv mut rows_done : Bool
  priv mut inflated : Bool
  // The zlib stream's error, raised once the rows before it are out.
  priv mut inflate_error : (@codec.ErrorKind, String)?
  // The current step.
  priv mut pos : Int32
  priv mut produced : Int32
  priv mut consumed : Int32
  priv mut rows_pass : Int32
  priv mut rows_first : Int32
  priv mut rows_count : Int32
  priv mut rows_bytes : Int32
  priv mut failure : @codec.CodecError?
  // The fixed-length ancillary chunks read so far, the current one's bytes,
  // and whether it is read into fields.
  priv fields : Fields
  priv scratch : FixedArray[Byte]
  priv mut scratch_len : Int32
  priv mut field : Bool
  // The ancillary chunk types to report, and the current block.
  priv reported : FixedArray[UInt32]
  priv max_chunk_bytes : Int32
  priv block : Block
  priv mut reporting : Bool
  priv mut metadata_chunk : UInt32
  // APNG: whether to read it, whether the image is one, what acTL says, the
  // fcTL chunks read and the sequence number the next must have, the fcTL
  // whose frame has not begun, and the current frame.
  priv animation : Bool
  priv mut animated : Bool
  priv mut frames : UInt32
  priv mut plays : UInt32
  priv mut controls : UInt32
  priv mut sequence : UInt32
  priv mut pending : Bool
  priv next : Control
  priv control : Control
  priv mut fdat_frame : Bool
  priv mut in_animation : Bool
  // An fcTL that came before acTL, kept for IDAT.
  priv early_control : FixedArray[Byte]
  priv mut early : Bool
  // The caller's array, during a `step_into`.
  priv mut fixed_out : FixedArray[Byte]?
}

///|
/// A decoder at the start of an image.
///
/// `max_row_bytes` bounds the bytes of a row (ceil(width × bits a pixel /
/// 8)): 8,000,000 by default, a million pixels of 16-bit RGBA, and at most
/// 1,000,000,000. The decoder holds two rows; an image whose rows are longer
/// raises `LimitExceeded(Memory)` with a row's bytes, at the step after
/// `Header`, before anything is allocated for it. Its height is not bounded,
/// since rows are handed out as they come.
///
/// `report` names the ancillary chunk types to hand out, each as one block
/// of `Metadata` once its CRC has passed (see `metadata_type`); by default
/// none. A name that is not an ancillary chunk type matches nothing. Their
/// data is held until then, so a reported chunk of more than
/// `max_chunk_bytes` bytes (1 MiB by default, at most 100,000,000) raises
/// `LimitExceeded(Memory)` with its length, and so does one that inflates to
/// more. iCCP's profile, zTXt's text and iTXt's compressed text come out
/// inflated, through a second zlib decoder, which the decoder holds only
/// when one of them is asked for.
///
/// `animation` says whether to read APNG's chunks (acTL, fcTL, fdAT): each
/// frame of an animation is then a `Frame` with its rows. With
/// `animation=false` they are skipped as unknown chunks, and an animated
/// image gives its default image only.
///
/// `until` says where the image ends. `EndOfInput`, the default, decodes the
/// image up to the end of the input, and bytes after IEND raise
/// `TrailingData`. `EndOfMember` stops right after IEND and consumes nothing
/// past it, so that `input[last_consumed():]` is what follows.
pub fn Decoder::Decoder(
  max_row_bytes~ : Int32 = 8_000_000,
  report~ : Array[String] = [],
  max_chunk_bytes~ : Int32 = 1 << 20,
  animation~ : Bool = true,
  until~ : @codec_image.Until = EndOfInput,
) -> Decoder {
  let none = FixedArray::make(0, b'\x00')
  let max_chunk_bytes = max_chunk_bytes.max(0).min(MAX_CHUNK_BYTES)
  let reported = FixedArray::from_array(
    report.filter_map(name => {
      guard name.length() == 4 && name.iter().all(c => c.to_int() < 128) else {
        return None
      }
      let t = @rules.chunk_type(
        name[0].to_byte(),
        name[1].to_byte(),
        name[2].to_byte(),
        name[3].to_byte(),
      )
      if @rules.is_valid_type(t) && !@rules.is_critical(t) {
        Some(t)
      } else {
        None
      }
    }),
  )
  {
    max_row_bytes: max_row_bytes.max(0).min(MAX_ROW_BYTES),
    until,
    inflater: @zlib.Decoder::Decoder(until=EndOfMember),
    held: FixedArray::make(8, b'\x00'),
    held_len: 0,
    ihdr: FixedArray::make(13, b'\x00'),
    ihdr_len: 0,
    width: 0,
    height: 0,
    bit_depth: 0,
    color_type: 0,
    interlaced: false,
    bits: 0,
    unit: 1,
    plte: FixedArray::make(768, b'\x00'),
    plte_len: 0,
    entries: 0,
    state: Signature,
    position: BeforeHeader,
    seen: 0,
    chunk: 0,
    index: -1,
    left: 0,
    crc: 0,
    ignoring: false,
    ignored: 0,
    prev: none,
    cur: none,
    cur_len: 0,
    ready: false,
    framed: false,
    in_frame: false,
    pass: 0,
    pass_rows: 0,
    pass_bytes: 0,
    row: 0,
    rows_done: false,
    inflated: false,
    inflate_error: None,
    pos: 0,
    produced: 0,
    consumed: 0,
    rows_pass: 0,
    rows_first: 0,
    rows_count: 0,
    rows_bytes: 0,
    failure: None,
    fixed_out: None,
    fields: Fields::new(),
    scratch: FixedArray::make(FIELD_BYTES, b'\x00'),
    scratch_len: 0,
    field: false,
    reported,
    max_chunk_bytes,
    block: Block::new(
      max_chunk_bytes,
      reported
      .iter()
      .any(t => @rules.known(t) is (@rules.ICCP | @rules.ZTXT | @rules.ITXT)),
    ),
    reporting: false,
    metadata_chunk: 0,
    animation,
    animated: false,
    frames: 0,
    plays: 0,
    controls: 0,
    sequence: 0,
    pending: false,
    next: Control::new(),
    control: Control::new(),
    fdat_frame: false,
    in_animation: false,
    early_control: FixedArray::make(26, b'\x00'),
    early: false,
  }
}

///|
/// Starts a new image with the same options. The rows' buffers are dropped.
pub fn Decoder::reset(self : Decoder) -> Unit {
  self.inflater.reset()
  self.held_len = 0
  self.ihdr_len = 0
  self.width = 0
  self.height = 0
  self.bit_depth = 0
  self.color_type = 0
  self.interlaced = false
  self.bits = 0
  self.unit = 1
  self.plte_len = 0
  self.entries = 0
  self.state = Signature
  self.position = BeforeHeader
  self.seen = 0
  self.chunk = 0
  self.index = -1
  self.left = 0
  self.crc = 0
  self.ignoring = false
  self.ignored = 0
  self.prev = FixedArray::make(0, b'\x00')
  self.cur = self.prev
  self.cur_len = 0
  self.ready = false
  self.framed = false
  self.in_frame = false
  self.pass = 0
  self.pass_rows = 0
  self.pass_bytes = 0
  self.row = 0
  self.rows_done = false
  self.inflated = false
  self.inflate_error = None
  self.produced = 0
  self.consumed = 0
  self.rows_count = 0
  self.failure = None
  self.fields.reset()
  self.scratch_len = 0
  self.field = false
  self.block.release()
  self.reporting = false
  self.animated = false
  self.frames = 0
  self.plays = 0
  self.controls = 0
  self.sequence = 0
  self.pending = false
  self.fdat_frame = false
  self.in_animation = false
  self.early = false
}

///|
/// Bytes held for the image: the zlib decoder's 46,592 (see
/// `@zlib.Decoder::memory_usage`), the 768 of the palette, and from the step
/// after `Header` two rows of 1 + `row_bytes` bytes each, for the image's
/// full width. With chunks to report, the block of the one being read, and
/// the second zlib decoder's 46,592 when iCCP, zTXt or iTXt is reported. The
/// signature, chunk heads and CRCs (8 bytes at a time), IHDR's 13 bytes and
/// the 256 into which a fixed-length ancillary chunk is read are not
/// counted.
pub fn Decoder::memory_usage(self : Decoder) -> Int32 {
  self.inflater.memory_usage() +
  self.plte.length() +
  self.prev.length() +
  self.cur.length() +
  self.block.memory()
}

///|
/// Input bytes the latest `step` consumed: all of them on `NeedMoreInput`.
/// A step that raised reports the bytes up to the error, and zero when an
/// earlier step found it; for `TrailingData`, exactly up to the end of the
/// image.
pub fn Decoder::last_consumed(self : Decoder) -> Int32 {
  self.consumed
}

///|
/// Output bytes the latest `step` wrote: those of its rows after `Rows`, and
/// zero after any other answer, an error included.
pub fn Decoder::last_produced(self : Decoder) -> Int32 {
  self.produced
}

///|
/// The smallest output slice the next step can write into: a reported
/// chunk's block when one waits, the bytes of the current pass's rows while
/// the frame has rows to come, else zero.
pub fn Decoder::room_needed(self : Decoder) -> Int32 {
  if self.state is Emit {
    self.block.len
  } else if self.in_frame && !self.rows_done {
    self.pass_bytes
  } else {
    0
  }
}

///|
/// The image's width, in pixels. Valid from `Header` until `reset`.
pub fn Decoder::width(self : Decoder) -> Int32 {
  self.width
}

///|
/// The image's height, in pixels. Valid from `Header` until `reset`.
pub fn Decoder::height(self : Decoder) -> Int32 {
  self.height
}

///|
/// Bits a sample, or a palette index: 1, 2, 4, 8 or 16 (table 12). Valid
/// from `Header` until `reset`.
pub fn Decoder::bit_depth(self : Decoder) -> Int32 {
  self.bit_depth
}

///|
/// How the samples of a pixel are laid out. Valid from `Header` until
/// `reset`.
pub fn Decoder::color_type(self : Decoder) -> ColorType {
  match self.color_type {
    0 => Grayscale
    2 => Truecolor
    3 => IndexedColor
    4 => GrayscaleAlpha
    _ => TruecolorAlpha
  }
}

///|
/// Whether the image is interlaced with Adam7: its rows then come in seven
/// passes, each a lattice of the image (`pass_x`, `pass_dx`, ...), and a
/// pass with no pixels is skipped. Valid from `Header` until `reset`.
pub fn Decoder::interlaced(self : Decoder) -> Bool {
  self.interlaced
}

///|
/// The palette: red, green and blue, one byte each, for every entry the
/// image can use. An indexed-color image has one (PLTE is required); entries
/// beyond what its bit depth can index are dropped, as libpng drops them. A
/// truecolor image may have one as a suggestion; other images have none.
/// Valid from `Frame` until `reset`. Rows hold indices as they are, so an
/// index past the palette is the caller's to handle (the specification
/// shows it as opaque black).
pub fn Decoder::palette(self : Decoder) -> ArrayView[Byte] {
  self.plte[:self.entries * 3]
}

///|
/// Ancillary chunks left out so far: those out of the place table 7 gives
/// them, second copies of chunks that may come once, those whose CRC does
/// not match, and a PLTE that a grayscale image does not use or that is out
/// of place in a truecolor one. Unknown ancillary chunks are skipped and not
/// counted.
pub fn Decoder::ignored_chunks(self : Decoder) -> Int32 {
  self.ignored
}

///|
/// The left edge of the current frame: its fcTL's x offset, and 0 for a
/// default image. Valid from `Frame` until the next `Frame` or `Done`.
pub fn Decoder::frame_x(self : Decoder) -> Int32 {
  self.control.x
}

///|
/// The top edge of the current frame. Valid from `Frame` until the next
/// `Frame` or `Done`.
pub fn Decoder::frame_y(self : Decoder) -> Int32 {
  self.control.y
}

///|
/// The current frame's width: the image's for a default image. Valid from
/// `Frame` until the next `Frame` or `Done`.
pub fn Decoder::frame_width(self : Decoder) -> Int32 {
  self.control.width
}

///|
/// The current frame's height. Valid from `Frame` until the next `Frame` or
/// `Done`.
pub fn Decoder::frame_height(self : Decoder) -> Int32 {
  self.control.height
}

///|
/// The pass the last `Rows` belong to: 0 to 6 for Adam7, 0 for an image that
/// is not interlaced. Valid after `Rows`, until the next step.
pub fn Decoder::pass(self : Decoder) -> Int32 {
  self.rows_pass
}

///|
/// The column of the image where the last `Rows`' pass begins. Valid after
/// `Rows`, until the next step.
pub fn Decoder::pass_x(self : Decoder) -> Int32 {
  @rules.pass_x(self.interlaced, self.rows_pass)
}

///|
/// The row of the image where the last `Rows`' pass begins. Valid after
/// `Rows`, until the next step.
pub fn Decoder::pass_y(self : Decoder) -> Int32 {
  @rules.pass_y(self.interlaced, self.rows_pass)
}

///|
/// The distance between the columns of the last `Rows`' pass. Valid after
/// `Rows`, until the next step.
pub fn Decoder::pass_dx(self : Decoder) -> Int32 {
  @rules.pass_dx(self.interlaced, self.rows_pass)
}

///|
/// The distance between the rows of the last `Rows`' pass. Valid after
/// `Rows`, until the next step.
pub fn Decoder::pass_dy(self : Decoder) -> Int32 {
  @rules.pass_dy(self.interlaced, self.rows_pass)
}

///|
/// The row of its pass the last `Rows` begin at. Valid after `Rows`, until
/// the next step.
pub fn Decoder::rows_first(self : Decoder) -> Int32 {
  self.rows_first
}

///|
/// How many rows the last `Rows` wrote. Valid after `Rows`, until the next
/// step.
pub fn Decoder::rows_count(self : Decoder) -> Int32 {
  self.rows_count
}

///|
/// The bytes of each row the last `Rows` wrote: the pass's pixels, packed as
/// the image's color type and bit depth say, without the filter type byte.
/// Valid after `Rows`, until the next step.
pub fn Decoder::row_bytes(self : Decoder) -> Int32 {
  self.rows_bytes
}

///|
/// Decodes from `input` into `output`, and says why it stopped; see
/// `last_consumed` and `last_produced` for how much of each it used.
///
/// Pass `end=true` once `input` holds the last bytes and `end=false` before:
/// an image cut short then raises `Truncated`. A step that writes, writes
/// whole rows of one pass and answers `Rows`; it stops at `Header`, `Frame`
/// and `FrameEnd` without writing. `NeedMoreOutput` means `output` is
/// shorter than the next row, `room_needed` bytes. Rows come out before the
/// CRCs and the Adler-32 that cover them are checked, so `ChecksumMismatch`
/// can follow rows already handed out; `FrameEnd` comes once they have
/// passed. A step that raises writes nothing: the rows before the error came
/// out in the steps before it. After an error the decoder raises it again,
/// consuming and writing nothing, until `reset`.
pub fn Decoder::step(
  self : Decoder,
  input : BytesView,
  output : MutArrayView[Byte],
  end~ : Bool,
) -> Status raise @codec.CodecError {
  self.step_with(input, output, None, end~)
}

///|
/// `step` into a whole `FixedArray`, with bulk copies of the rows.
pub fn Decoder::step_into(
  self : Decoder,
  input : BytesView,
  output : FixedArray[Byte],
  end~ : Bool,
) -> Status raise @codec.CodecError {
  self.step_with(input, output.mut_view(), Some(output), end~)
}

///|
/// A step into `output`, which is the whole of `fixed_output` when given.
fn Decoder::step_with(
  self : Decoder,
  input : BytesView,
  output : MutArrayView[Byte],
  fixed_output : FixedArray[Byte]?,
  end~ : Bool,
) -> Status raise @codec.CodecError {
  self.fixed_out = fixed_output
  defer {
    self.fixed_out = None
  }
  self.consumed = 0
  self.produced = 0
  self.rows_count = 0
  if self.failure is Some(err) {
    raise err
  }
  self.pos = 0
  let status = self.run(input, output, end~) catch {
    NeedInput if self.produced > 0 => Rows
    NeedInput if end =>
      self.fail(
        Truncated,
        if self.state is Signature && self.held_len == 0 {
          "the input holds no image"
        } else {
          "the input ends inside the image"
        },
      )
    NeedInput => NeedMoreInput
    Fail(kind, message) if self.produced > 0 => {
      self.failure = Some(CodecError(kind, message))
      Rows
    }
    Fail(kind, message) => self.fail(kind, message)
  }
  self.consumed = self.pos
  status
}

///|
/// Records a terminal error and raises it, from a step that wrote nothing.
fn[T] Decoder::fail(
  self : Decoder,
  kind : @codec.ErrorKind,
  message : String,
) -> T raise @codec.CodecError {
  let err = @codec.CodecError(kind, message)
  self.failure = Some(err)
  self.consumed = self.pos
  raise err
}

///|
/// Runs the image's units over the step's input.
fn Decoder::run(
  self : Decoder,
  input : BytesView,
  output : MutArrayView[Byte],
  end~ : Bool,
) -> Status raise Stop {
  for ;; {
    match self.state {
      Signature => {
        while self.held_len < 8 && self.pos < input.length() {
          if input[self.pos] != signature[self.held_len] {
            raise Fail(Corrupt, "the input does not begin with PNG's signature")
          }
          self.held_len += 1
          self.pos += 1
        }
        if self.held_len < 8 {
          raise NeedInput
        }
        self.held_len = 0
        self.state = Head
      }
      Allocate => {
        let bytes = @rules.row_bytes(
          self.width.reinterpret_as_uint(),
          self.bits,
          self.max_row_bytes,
        ) catch {
          CodecError(kind, message) => raise Fail(kind, message)
        }
        self.prev = FixedArray::make(1 + bytes, b'\x00')
        self.cur = FixedArray::make(1 + bytes, b'\x00')
        self.state = Head
      }
      Head => {
        self.gather(input, 8)
        let h = self.held
        let length = @rules.chunk_type(h[0], h[1], h[2], h[3])
        if length > MAX_LENGTH {
          raise Fail(Corrupt, "a chunk's length of \{length} bytes")
        }
        self.left = length.reinterpret_as_int()
        self.chunk = @rules.chunk_type(h[4], h[5], h[6], h[7])
        self.crc = @crc32.update(@crc32.INITIAL, h[4:8])
        self.held_len = 0
        self.state = Dispatch
      }
      Dispatch => {
        let status = self.dispatch()
        if status is Some(status) {
          return status
        }
      }
      HeaderData => {
        self.ihdr_len = self.take(input, self.ihdr, self.ihdr_len)
        if self.left > 0 {
          raise NeedInput
        }
        self.state = Crc
      }
      PaletteData => {
        self.plte_len = self.take(input, self.plte, self.plte_len)
        if self.left > 0 {
          raise NeedInput
        }
        self.state = Crc
      }
      Sequence => {
        self.gather(input, 4)
        let h = self.held
        self.crc = @crc32.update(self.crc, h[0:4])
        self.held_len = 0
        self.left -= 4
        self.check_sequence(@rules.chunk_type(h[0], h[1], h[2], h[3]))
        self.state = ImageData
        if !self.in_frame {
          self.control.copy(self.next)
          self.pending = false
          self.in_animation = true
          self.begin_frame(fdat=true)
          return Frame
        }
      }
      ImageData => {
        let status = self.image_data(input, output)
        if status is Some(status) {
          return status
        }
      }
      Skip => {
        let n = self.left.min(input.length() - self.pos)
        self.crc = @crc32.update_bytes(self.crc, input[self.pos:self.pos + n])
        self.pos += n
        self.left -= n
        if self.left > 0 {
          raise NeedInput
        }
        self.state = Crc
      }
      Collect => {
        let n = self.left.min(input.length() - self.pos)
        let data = input[self.pos:self.pos + n]
        self.crc = @crc32.update_bytes(self.crc, data)
        if self.field {
          self.scratch.blit_from_bytesview(self.scratch_len, data)
          self.scratch_len += n
        }
        if self.reporting {
          self.block.feed(data)
        }
        self.pos += n
        self.left -= n
        if self.left > 0 {
          raise NeedInput
        }
        self.state = Crc
      }
      Emit => {
        let n = self.block.len
        if n > output.length() - self.produced {
          return if self.produced > 0 { Rows } else { NeedMoreOutput }
        }
        // SAFETY: `n <= output.length()` was checked above, and the block
        // holds `n` bytes.
        match self.fixed_out {
          Some(out) => blit(out, 0, self.block.data, 0, n)
          None =>
            for i in 0.. {
        self.gather(input, 4)
        let h = self.held
        let stored = @rules.chunk_type(h[0], h[1], h[2], h[3])
        self.held_len = 0
        let status = self.close_chunk(stored == self.crc)
        if status is Some(status) {
          return status
        }
      }
      Ended =>
        return if self.until is EndOfMember {
          Done
        } else if self.pos < input.length() {
          raise Fail(TrailingData, "data after IEND")
        } else if end {
          Done
        } else {
          NeedMoreInput
        }
    }
  }
}

///|
/// Gathers bytes into `held` until it holds `n`.
fn Decoder::gather(
  self : Decoder,
  input : BytesView,
  n : Int32,
) -> Unit raise Stop {
  let take = (n - self.held_len).min(input.length() - self.pos)
  if take > 0 {
    self.held.blit_from_bytesview(
      self.held_len,
      input[self.pos:self.pos + take],
    )
    self.held_len += take
    self.pos += take
  }
  if self.held_len < n {
    raise NeedInput
  }
}

///|
/// Copies the chunk's data into `into` from `at`, through the CRC, and
/// returns how far `into` is filled.
fn Decoder::take(
  self : Decoder,
  input : BytesView,
  into : FixedArray[Byte],
  at : Int32,
) -> Int32 {
  let n = self.left.min(input.length() - self.pos)
  let data = input[self.pos:self.pos + n]
  into.blit_from_bytesview(at, data)
  self.crc = @crc32.update_bytes(self.crc, data)
  self.pos += n
  self.left -= n
  at + n
}

///|
/// Acts on a chunk whose head is read. Ends the frame first when the IDAT
/// chunks are over; a step that wrote rows stops before that, and the next
/// one comes back here.
fn Decoder::dispatch(self : Decoder) -> Status? raise Stop {
  let t = self.chunk
  if !@rules.is_valid_type(t) {
    raise Fail(Corrupt, "a chunk type that is not four letters")
  }
  let mut index = @rules.known(t)
  let apng = index is (@rules.ACTL | @rules.FCTL | @rules.FDAT)
  // Outside an APNG, its chunks are unknown ones; but an fcTL before IDAT
  // may come before acTL, and is kept until IDAT says which it was.
  let read_apng = index == @rules.ACTL ||
    self.animated ||
    (index == @rules.FCTL && !self.framed)
  if apng && !(self.animation && read_apng) {
    index = -1
  }
  // A frame of IDAT ends at the first other chunk; one of fdAT at the next
  // fcTL or IEND, since other chunks may come between its fdAT chunks.
  let ends = if self.fdat_frame {
    index is (@rules.FCTL | @rules.IEND)
  } else {
    index != @rules.IDAT
  }
  if self.in_frame && ends {
    if self.produced > 0 {
      return Some(Rows)
    }
    if !self.rows_done {
      raise Fail(Corrupt, "the image data ends before the image's last row")
    }
    if !self.inflated {
      raise Fail(Corrupt, "the zlib stream does not end in the IDAT chunks")
    }
    self.in_frame = false
    return Some(FrameEnd)
  }
  let verdict = if index == -1 && apng && !(self.position is BeforeHeader) {
    @rules.Verdict::Accept
  } else {
    @rules.place(self.position, t, self.seen, self.color_type)
  }
  if verdict is Refuse(kind, message) {
    raise Fail(kind, message)
  }
  // In an APNG, a second fcTL before IDAT or an fdAT before it is out of
  // the order of its sequence.
  if index is (@rules.FCTL | @rules.FDAT) && verdict is Ignore && self.animated {
    raise Fail(Corrupt, "\{@rules.type_name(t)} out of order")
  }
  // Nor can an APNG have a second acTL; one after IDAT, before which the
  // image was not animated, is left out like any misplaced chunk.
  if index == @rules.ACTL && verdict is Ignore && self.animated {
    raise Fail(Corrupt, "a second acTL")
  }
  self.position = @rules.after(self.position, t, verdict)
  self.index = index
  self.ignoring = verdict is Ignore
  if verdict is Accept && index >= 0 {
    self.seen = self.seen | (1U << index)
  }
  self.state = Skip
  match index {
    @rules.IHDR => {
      if self.left != 13 {
        raise Fail(Corrupt, "an IHDR of \{self.left} bytes")
      }
      self.state = HeaderData
    }
    @rules.PLTE if verdict is Accept => {
      let fits = self.left > 0 && self.left <= 768 && self.left % 3 == 0
      if fits {
        self.plte_len = 0
        self.state = PaletteData
      } else if self.color_type == 3 {
        raise Fail(Corrupt, "a PLTE of \{self.left} bytes")
      } else {
        self.ignoring = true
      }
    }
    @rules.IDAT => {
      self.state = ImageData
      if !self.framed {
        // The default image: the frame of the fcTL before it, if any, and
        // if acTL came before it too.
        if self.animated && self.early {
          self.early_control.blit_to(self.scratch, len=26)
          self.read_fctl()
        }
        self.framed = true
        self.in_animation = self.pending
        if self.pending {
          self.control.copy(self.next)
          self.pending = false
        } else {
          self.control.whole(self.width, self.height)
        }
        self.begin_frame(fdat=false)
        return Some(Frame)
      }
    }
    @rules.FDAT => {
      if !self.in_frame && !self.pending {
        raise Fail(Corrupt, "fdAT without an fcTL before it")
      }
      if self.left < 4 {
        raise Fail(Corrupt, "an fdAT of \{self.left} bytes")
      }
      self.state = Sequence
    }
    @rules.FCTL if verdict is Accept => {
      if self.pending {
        raise Fail(Corrupt, "an fcTL whose frame has no data")
      }
      if self.left != 26 {
        raise Fail(Corrupt, "an fcTL of \{self.left} bytes")
      }
      self.begin_ancillary(t, index)
    }
    @rules.IEND => {
      if self.animated && self.pending {
        raise Fail(Corrupt, "an fcTL whose frame has no data")
      }
      if self.animated && self.controls != self.frames {
        raise Fail(
          Corrupt,
          "acTL says \{self.frames} frames, and there are \{self.controls}",
        )
      }
      self.ignoring = self.left > 0
    }
    _ => if !self.ignoring { self.begin_ancillary(t, index) }
  }
  None
}

///|
/// Begins an ancillary chunk that table 7 places where it is: read into
/// fields, reported, or both, or only skipped through its CRC. A chunk to
/// read into fields that is longer than any of their kind is left out.
fn Decoder::begin_ancillary(
  self : Decoder,
  t : UInt32,
  index : Int32,
) -> Unit raise Stop {
  let fields = is_field(index) || index is (@rules.ACTL | @rules.FCTL)
  self.field = fields && self.left <= FIELD_BYTES
  if fields && !self.field {
    self.ignoring = true
    return
  }
  self.reporting = self.reported.contains(t)
  if self.reporting && self.left > self.max_chunk_bytes {
    raise Fail(
      LimitExceeded({
        resource: Memory,
        needed: Some(self.left.to_uint64()),
        limit: self.max_chunk_bytes.to_uint64(),
      }),
      "a \{@rules.type_name(t)} of \{self.left} bytes, over max_chunk_bytes",
    )
  }
  if self.reporting {
    self.block.start(t, index, self.left)
  }
  if self.field || self.reporting {
    self.scratch_len = 0
    self.state = Collect
  }
}

///|
/// Acts on a chunk once its CRC is read: `checked` says whether it matched.
/// A critical chunk's mismatch is refused, an ancillary one's leaves the
/// chunk out; PLTE is ancillary in an image that is not indexed-color.
fn Decoder::close_chunk(self : Decoder, checked : Bool) -> Status? raise Stop {
  // In an APNG, fcTL and fdAT are as needed as IDAT.
  let critical = (
      self.index >= 0 &&
      self.index <= @rules.IEND &&
      (self.index != @rules.PLTE || self.color_type == 3)
    ) ||
    (self.animated && self.index is (@rules.FCTL | @rules.FDAT))
  if !checked && critical {
    raise Fail(
      ChecksumMismatch,
      "the CRC of \{@rules.type_name(self.chunk)} does not match",
    )
  }
  let mut used = checked && !self.ignoring
  if used && self.field {
    used = match self.index {
      @rules.ACTL => self.read_actl()
      @rules.FCTL if self.animated => {
        self.read_fctl()
        true
      }
      @rules.FCTL => {
        // Before acTL: kept for IDAT.
        self.scratch.blit_to(self.early_control, len=26)
        self.early = true
        true
      }
      _ =>
        self.fields.read(
          self.index,
          self.scratch,
          self.scratch_len,
          self.color_type,
          self.bit_depth,
          self.entries,
        )
    }
  }
  if used && self.reporting {
    used = self.block.finish()
  }
  if !used {
    self.ignored += 1
    // A later valid copy of a chunk left out may still be read.
    if self.index > @rules.IEND {
      self.seen = self.seen & (1U << self.index).lnot()
    }
  }
  self.field = false
  self.state = Head
  if self.reporting {
    self.reporting = false
    if used {
      self.state = Emit
      return None
    }
    self.block.release()
  }
  match self.index {
    @rules.IHDR => {
      self.read_header()
      self.state = Allocate
      return Some(Header)
    }
    @rules.PLTE if used => {
      let most = if self.color_type == 3 { 1 << self.bit_depth } else { 256 }
      self.entries = (self.plte_len / 3).min(most)
    }
    @rules.IEND => self.state = Ended
    _ => ()
  }
  None
}

///|
/// Reads IHDR's fields, refusing those that describe no image.
fn Decoder::read_header(self : Decoder) -> Unit raise Stop {
  let h = self.ihdr
  let width = @rules.chunk_type(h[0], h[1], h[2], h[3])
  let height = @rules.chunk_type(h[4], h[5], h[6], h[7])
  let bit_depth = h[8].to_int()
  let color_type = h[9].to_int()
  if @rules.check_header(
      width,
      height,
      bit_depth,
      color_type,
      h[10].to_int(),
      h[11].to_int(),
      h[12].to_int(),
    )
    is Some(message) {
    raise Fail(Corrupt, "IHDR: \{message}")
  }
  self.width = width.reinterpret_as_int()
  self.height = height.reinterpret_as_int()
  self.bit_depth = bit_depth
  self.color_type = color_type
  self.interlaced = h[12] == b'\x01'
  self.bits = @rules.channels(color_type) * bit_depth
  self.unit = (self.bits / 8).max(1)
}

///|
/// Begins a frame's data: its zlib stream from the start, its rows from the
/// first pass that has pixels. The only place the zlib decoder is reset
/// within an image, so that no frame inherits another's stream.
fn Decoder::begin_frame(self : Decoder, fdat~ : Bool) -> Unit {
  self.inflater.reset()
  self.fdat_frame = fdat
  self.in_frame = true
  self.rows_done = false
  self.inflated = false
  self.inflate_error = None
  self.ready = false
  self.start_pass(0)
}

///|
/// Reads acTL: an animation of at least one frame, and how many times to
/// play it. One of no frames is left out, and the image is not animated.
fn Decoder::read_actl(self : Decoder) -> Bool {
  guard self.scratch_len == 8 else { return false }
  let frames = u32(self.scratch, 0)
  guard frames > 0 && frames <= 0x7FFFFFFF else { return false }
  self.frames = frames
  self.plays = u32(self.scratch, 4)
  self.animated = self.animation
  true
}

///|
/// Reads an fcTL, whose sequence number must be the next: the frame it
/// describes waits for its first data chunk.
fn Decoder::read_fctl(self : Decoder) -> Unit raise Stop {
  self.check_sequence(u32(self.scratch, 0))
  self.next.read(
    self.scratch,
    self.width,
    self.height,
    default_image=!self.framed,
    first=self.controls == 0,
  )
  self.controls += 1
  self.pending = true
}

///|
/// Checks a sequence number of fcTL or fdAT against the next one expected.
fn Decoder::check_sequence(self : Decoder, number : UInt32) -> Unit raise Stop {
  if number != self.sequence {
    raise Fail(
      Corrupt,
      "sequence number \{number} where \{self.sequence} was due",
    )
  }
  self.sequence += 1
}

///|
/// Starts the first pass from `pass` on that has pixels, with a previous row
/// of zeros, or ends the rows when none is left.
fn Decoder::start_pass(self : Decoder, pass : Int32) -> Unit {
  for p in pass..<@rules.passes(self.interlaced) {
    let columns = @rules.pass_columns(self.interlaced, p, self.control.width)
    let rows = @rules.pass_rows(self.interlaced, p, self.control.height)
    if columns > 0 && rows > 0 {
      self.pass = p
      self.pass_rows = rows
      self.pass_bytes = @rules.pass_row_bytes(columns, self.bits)
      self.row = 0
      self.cur_len = 0
      self.prev.fill(b'\x00', end=1 + self.pass_bytes)
      return
    }
  }
  self.rows_done = true
}

///|
/// Inflates an IDAT's data into rows and hands them out, until the input,
/// the chunk or the room runs out, or the pass ends.
fn Decoder::image_data(
  self : Decoder,
  input : BytesView,
  output : MutArrayView[Byte],
) -> Status? raise Stop {
  for ;; {
    if self.ready {
      if !self.write_row(output) {
        return Some(if self.produced > 0 { Rows } else { NeedMoreOutput })
      }
      if self.rows_done || self.pass != self.rows_pass {
        return Some(Rows)
      }
      continue
    }
    let n = self.left.min(input.length() - self.pos)
    let data = input[self.pos:self.pos + n]
    if self.inflate_error is Some((kind, message)) {
      raise Fail(kind, message)
    }
    if self.inflated {
      if !self.rows_done {
        raise Fail(Corrupt, "the zlib stream ends before the image's last row")
      }
      // Bytes after the zlib stream are dropped (11.2.3), through the CRC.
      self.crc = @crc32.update_bytes(self.crc, data)
      self.pos += n
      self.left -= n
    } else {
      let full = 1 + self.pass_bytes
      let inflater = self.inflater
      let status = try {
        if self.rows_done || (self.cur_len == 0 && self.cur.length() == full) {
          // A whole row, or once the rows are done, data past them, which
          // is inflated only to reach the Adler-32 and then dropped.
          inflater.step_into(data, self.cur, end=false)
        } else {
          inflater.step(
            data,
            self.cur.mut_view(start=self.cur_len, end=full),
            end=false,
          )
        }
      } catch {
        CodecError(kind, message) => {
          // What the zlib decoder wrote before its error may complete a
          // row, which comes out first.
          self.consume(data, inflater.last_consumed())
          self.inflate_error = Some((kind, "IDAT: \{message}"))
          if !self.rows_done {
            self.cur_len += inflater.last_produced()
            if self.cur_len == full {
              self.finish_row()
            }
          }
          continue
        }
      }
      let used = inflater.last_consumed()
      let made = inflater.last_produced()
      self.consume(data, used)
      if status is Done {
        self.inflated = true
      }
      if !self.rows_done {
        self.cur_len += made
        if self.cur_len == full {
          self.finish_row()
          continue
        }
      }
      if used > 0 || made > 0 || self.inflated {
        continue
      }
    }
    // Nothing more comes from this chunk's data until there is more.
    if self.left > 0 {
      raise NeedInput
    }
    self.state = Crc
    return None
  }
}

///|
/// Takes `n` bytes of `data`, the chunk's, through the CRC.
fn Decoder::consume(self : Decoder, data : BytesView, n : Int32) -> Unit {
  self.crc = @crc32.update_bytes(self.crc, data[:n])
  self.pos += n
  self.left -= n
}

///|
/// Reverses the whole current row's filter and makes it the row waiting to
/// be handed out.
fn Decoder::finish_row(self : Decoder) -> Unit raise Stop {
  let kind = self.cur[0]
  if !@filter.unfilter(kind, self.cur, self.prev, self.pass_bytes, self.unit) {
    raise Fail(Corrupt, "filter type \{kind.to_int()}")
  }
  let row = self.cur
  self.cur = self.prev
  self.prev = row
  self.cur_len = 0
  self.ready = true
}

///|
/// Writes the waiting row into `output` if it fits, and moves on to the next
/// row, or pass.
fn Decoder::write_row(self : Decoder, output : MutArrayView[Byte]) -> Bool {
  let n = self.pass_bytes
  if n > output.length() - self.produced {
    return false
  }
  let row = self.prev
  // SAFETY: `produced + n <= output.length()` was checked above, and `row`
  // holds 1 + the bytes of a full row, at least `1 + n`.
  match self.fixed_out {
    Some(out) => blit(out, self.produced, row, 1, n)
    None =>
      for i in 0.. Unit {
  FixedArray::unsafe_blit(dst, to, src, at, n)
}

///|
/// The same, byte by byte: on JS the block copy is a loop that checks every
/// index, and decoded the benchmarks' image 10% slower than this one
/// (bench/, 2026-10-22).
#cfg(target="js")
fn blit(
  dst : FixedArray[Byte],
  to : Int32,
  src : FixedArray[Byte],
  at : Int32,
  n : Int32,
) -> Unit {
  for i in 0..