///|
fn pdf_content_parse_section(
  operands : ArrayView[@syntax.PdfObject],
  operator : BytesView,
) -> @content.PdfContentOp raise @core.PdfError {
  match @content.pdf_content_parse_known_section(operands, operator) {
    Some(op) => op
    None => pdf_content_section_unknown(operands, operator)
  }
}

///|
fn pdf_content_section_unknown(
  operands : ArrayView[@syntax.PdfObject],
  operator : BytesView,
) -> @content.PdfContentOp raise @core.PdfError {
  Op_Unknown(pdf_content_section_bytes(operands, operator))
}

///|
/// Parse standalone content bytes without document or resource context.
///
/// This is suitable for ordinary operators and inline images that can be read
/// from explicit length or filter markers. Use the document methods when stream
/// decoding or resource-based inline-image sizing is required.
pub fn pdf_parse_content_ops_from_bytes(
  data : @core.PdfBytes,
) -> Array[@content.PdfContentOp] raise @core.PdfError {
  pdf_parse_content_ops_from_view(data)
}

///|
/// Parse a standalone content byte view without document or resource context.
pub fn pdf_parse_content_ops_from_view(
  data : BytesView,
) -> Array[@content.PdfContentOp] raise @core.PdfError {
  pdf_parse_content_ops_from_view_with_context(
    data,
    pdf_content_parse_context(None),
  )
}

///|
fn pdf_parse_content_ops_from_view_with_context(
  data : BytesView,
  context : PdfContentParseContext,
) -> Array[@content.PdfContentOp] raise @core.PdfError {
  let cursor = @core.byte_cursor_of_view(data)
  let op_capacity = pdf_content_parse_ops_capacity(data)
  let ops : Array[@content.PdfContentOp] = Array(capacity=op_capacity)
  let operands : Array[@syntax.PdfObject] = Array(capacity=8)
  let mut done = false
  while !done {
    let before = cursor.position()
    match pdf_content_lex_next(cursor) {
      ContentEnd => done = true
      ContentComment => ()
      ContentOperand(object) => operands.push(object)
      ContentOperator(operator) =>
        if operator == pdf_content_inline_image_begin_operator &&
          operands.length() == 0 {
          ops.push(pdf_content_read_inline_image(cursor, context))
        } else {
          ops.push(pdf_content_parse_section(operands, operator))
          operands.clear()
        }
    }
    // A non-terminal lexeme that consumed no input means the stream is
    // malformed (e.g. a stray closing delimiter that no rule advances past,
    // which lexes to an empty operator token). Without this guard the loop
    // would spin forever, growing `ops`/`operands` until memory is exhausted.
    if !done && cursor.position() == before {
      raise ContentOperatorExpected
    }
  }
  if operands.length() > 0 {
    raise ContentOperatorExpected
  }
  ops
}

///|
fn pdf_content_parse_ops_capacity(data : BytesView) -> Int {
  let estimated = data.length() / 12
  if estimated < 4 {
    4
  } else if estimated > 4096 {
    4096
  } else {
    estimated
  }
}

///|
fn pdf_content_parse_context(
  colour_space_components : PdfContentColourSpaceComponents?,
) -> PdfContentParseContext {
  {
    decode_stream: pdf_decode_stream_until_unknown_value,
    colour_space_components,
  }
}