///|
/// Reads JPEG data from the current cursor position through the EOI marker.
///
/// The returned bytes include the terminal `0xFF 0xD9` marker. Raises
/// `PdfError::EndOfInput` if the marker is not found before EOF.
pub fn ByteCursor::read_jpeg_data(self : ByteCursor) -> PdfBytes raise PdfError {
let output : Array[Byte] = []
let mut last = 0
let mut done = false
while !done {
match self.read_byte() {
Some(byte) => {
let value = byte.to_int()
output.push(byte)
if value == 0xD9 && last == 0xFF {
done = true
} else {
last = value
}
}
None => raise EndOfInput
}
}
Bytes::from_array(output)
}
///|
/// Reads JPEG data from a cursor through the EOI marker.
///
/// This is the standalone form of `ByteCursor::read_jpeg_data`.
pub fn pdf_get_jpeg_data(input : ByteCursor) -> PdfBytes raise PdfError {
input.read_jpeg_data()
}
///|
fn pdf_jpeg_get(data : BytesView, index : Int) -> Int? {
if index >= 0 && index < data.length() {
Some(data[index].to_int())
} else {
None
}
}
///|
fn pdf_jpeg_get_or(
data : BytesView,
index : Int,
error : PdfError,
) -> Int raise PdfError {
match pdf_jpeg_get(data, index) {
Some(value) => value
None => raise error
}
}
///|
fn pdf_jpeg_word_at(
data : BytesView,
index : Int,
error : PdfError,
) -> Int raise PdfError {
pdf_jpeg_get_or(data, index, error) * 256 +
pdf_jpeg_get_or(data, index + 1, error)
}
///|
fn pdf_jpeg_has_app_header(data : BytesView) -> Bool {
data.length() >= 4 &&
data[0].to_int() == 0xFF &&
data[1].to_int() == 0xD8 &&
data[2].to_int() == 0xFF &&
(data[3].to_int() == 0xE0 || data[3].to_int() == 0xE1)
}
///|
fn pdf_jpeg_has_signature(data : BytesView, index : Int) -> Bool raise PdfError {
let first = pdf_jpeg_get_or(data, index + 2, BadJPEGString)
let second = pdf_jpeg_get_or(data, index + 3, BadJPEGString)
let third = pdf_jpeg_get_or(data, index + 4, BadJPEGString)
let fourth = pdf_jpeg_get_or(data, index + 5, BadJPEGString)
let zero = pdf_jpeg_get_or(data, index + 6, BadJPEGString)
(
first == 0x4A &&
second == 0x46 &&
third == 0x49 &&
fourth == 0x46 &&
zero == 0
) ||
(
first == 0x45 &&
second == 0x78 &&
third == 0x69 &&
fourth == 0x66 &&
zero == 0
)
}
///|
fn pdf_jpeg_dimensions_inner(data : BytesView) -> (Int, Int) raise PdfError {
if !pdf_jpeg_has_app_header(data) {
raise BadJPEGHeader
}
let mut index = 4
if !pdf_jpeg_has_signature(data, index) {
raise BadJPEGString
}
let mut block_length = pdf_jpeg_word_at(data, index, JPEGDimensionsExpected)
while index < data.length() {
index += block_length
if index >= data.length() {
raise JPEGDimensionsExpected
}
if pdf_jpeg_get_or(data, index, JPEGDimensionsExpected) != 0xFF {
raise InvalidJPEGBlock
}
if pdf_jpeg_get_or(data, index + 1, JPEGDimensionsExpected) == 0xC0 {
let height = pdf_jpeg_word_at(data, index + 5, JPEGDimensionsExpected)
let width = pdf_jpeg_word_at(data, index + 7, JPEGDimensionsExpected)
return (width, height)
}
index += 2
block_length = pdf_jpeg_word_at(data, index, JPEGDimensionsExpected)
}
raise JPEGDimensionsExpected
}
///|
/// Return the width and height of a baseline JFIF/Exif JPEG byte stream.
///
/// This matches cpdf's parser: the image must start with SOI plus APP0/APP1
/// JFIF or Exif metadata, and dimensions are read from the first SOF0 block.
/// Progressive JPEGs and JPEGs without SOF0 raise `PdfError::JPEGDimensionsExpected`.
pub fn pdf_jpeg_dimensions_view(data : BytesView) -> (Int, Int) raise PdfError {
pdf_jpeg_dimensions_inner(data)
}
///|
/// Return the width and height of a baseline JFIF/Exif JPEG byte stream.
pub fn pdf_jpeg_dimensions(data : PdfBytes) -> (Int, Int) raise PdfError {
pdf_jpeg_dimensions_view(data)
}