///|
/// Return whether `values` matches `view` starting at `position`.
///
/// Negative positions and ranges beyond the end of `view` return `false`.
pub fn pdf_view_matches_ascii_at(
  view : BytesView,
  position : Int,
  values : ArrayView[Int],
) -> Bool {
  if position < 0 || position + values.length() > view.length() {
    false
  } else {
    for i in 0.. Bool {
  view.length() == values.length() && pdf_view_matches_ascii_at(view, 0, values)
}

///|
/// Find the last occurrence of the ASCII byte sequence in `view`.
///
/// Returns `-1` when the sequence is absent. An empty sequence matches at
/// `view.length()`.
pub fn pdf_find_last_ascii(view : BytesView, values : ArrayView[Int]) -> Int {
  if values.length() == 0 {
    view.length()
  } else if view.length() < values.length() {
    -1
  } else {
    let max_start = view.length() - values.length()
    for offset in 0..<=max_start {
      let start = max_start - offset
      if pdf_view_matches_ascii_at(view, start, values) {
        break start
      }
    } nobreak {
      -1
    }
  }
}

///|
/// Return the portion of `data` before the last `%%EOF` marker.
///
/// If no EOF marker is present, returns the original view.
pub fn pdf_startxref_search_view(data : BytesView) -> BytesView {
  let eof = pdf_find_last_ascii(data, [37, 37, 69, 79, 70])
  if eof < 0 {
    data
  } else {
    data[:eof]
  }
}

///|
/// Find the first occurrence of the ASCII byte sequence at or after `start`.
///
/// Returns `-1` when the sequence is absent.
pub fn pdf_find_ascii_from(
  view : BytesView,
  start : Int,
  values : ArrayView[Int],
) -> Int {
  let max_start = view.length() - values.length()
  if start > max_start {
    -1
  } else {
    for position in start..<=max_start {
      if pdf_view_matches_ascii_at(view, position, values) {
        break position
      }
    } nobreak {
      -1
    }
  }
}

///|
/// Parse an unsigned 32-bit signed-range decimal integer from ASCII bytes.
///
/// Raises `PdfError::NumberExpected` for empty, non-digit, or overflow input.
pub fn pdf_parse_ascii_int_view(view : BytesView) -> Int raise PdfError {
  if view.length() == 0 {
    raise NumberExpected
  }
  let limit = 2147483647L
  let mut value = 0L
  for byte in view {
    let digit = byte.to_int()
    if !pdf_is_digit_byte(digit) {
      raise NumberExpected
    }
    value = value * 10L + (digit - 48).to_int64()
    if value > limit {
      raise NumberExpected
    }
  }
  value.to_int()
}

///|
/// Count non-empty tokens separated by PDF whitespace bytes.
pub fn pdf_count_whitespace_tokens(view : BytesView) -> Int {
  let mut count = 0
  let mut index = 0
  while index < view.length() {
    while index < view.length() && pdf_is_whitespace_byte(view[index].to_int()) {
      index += 1
    }
    let start = index
    while index < view.length() && !pdf_is_whitespace_byte(view[index].to_int()) {
      index += 1
    }
    if start < index {
      count += 1
    }
  }
  count
}

///|
/// Split a byte view into non-empty slices separated by PDF whitespace bytes.
///
/// The returned token views borrow from the input view.
pub fn pdf_split_whitespace_tokens(view : BytesView) -> Array[BytesView] {
  let tokens : Array[BytesView] = Array(
    capacity=pdf_count_whitespace_tokens(view),
  )
  let mut index = 0
  while index < view.length() {
    while index < view.length() && pdf_is_whitespace_byte(view[index].to_int()) {
      index += 1
    }
    let start = index
    while index < view.length() && !pdf_is_whitespace_byte(view[index].to_int()) {
      index += 1
    }
    if start < index {
      tokens.push(view[start:index])
    }
  }
  tokens
}