///|
let pdf_png_ihdr_chunk_type : UInt = 0x49484452U
///|
let pdf_png_idat_chunk_type : UInt = 0x49444154U
///|
let pdf_png_max_int64 : Int64 = 2147483647L
///|
/// Parsed PNG metadata and concatenated IDAT payload.
///
/// This mirrors the small `Cpdfpng` surface needed for image insertion:
/// non-palette, non-interlaced PNGs with their compressed IDAT bytes preserved.
pub(all) struct PdfPNG {
width : Int
height : Int
bit_depth : Int
color_type : Int
idat : PdfBytes
} derive(Debug, Eq, ToJson)
///|
fn pdf_png_u32_to_int(value : UInt, context : String) -> Int raise PdfError {
let wide = value.to_int64()
if wide > pdf_png_max_int64 {
raise BadPNG(context)
}
wide.to_int()
}
///|
fn ByteCursor::pdf_png_read_chunk(
self : ByteCursor,
) -> (UInt, PdfBytes) raise PdfError {
let length = pdf_png_u32_to_int(
self.read_u32_be(),
"read_png: chunk too large",
)
let chunk_type = self.read_u32_be()
let chunk_data = self.read_bytes(length)
ignore(self.read_u32_be())
(chunk_type, chunk_data)
}
///|
fn pdf_png_concat(chunks : ArrayView[PdfBytes]) -> PdfBytes {
let output : Array[Byte] = []
for chunk in chunks {
for index in 0.. PdfPNG raise PdfError {
let cursor = byte_cursor_of_view(data, source="png")
try! cursor.seek(8)
let (first_chunk_type, first_chunk_data) = cursor.pdf_png_read_chunk()
if first_chunk_type != pdf_png_ihdr_chunk_type {
raise BadPNG("read_png: first table not IHDR")
}
let header = byte_cursor_of_view(first_chunk_data, source="png IHDR")
let width = pdf_png_u32_to_int(
header.read_u32_be(),
"read_png: width too large",
)
let height = pdf_png_u32_to_int(
header.read_u32_be(),
"read_png: height too large",
)
let bit_depth = match header.read_byte() {
Some(byte) => byte.to_int()
None => raise EndOfInput
}
let color_type = match header.read_byte() {
Some(byte) => byte.to_int()
None => raise EndOfInput
}
if color_type != 0 && color_type != 2 && color_type != 4 && color_type != 6 {
raise BadPNG("read_png: only non-palette PNGs")
}
match header.read_byte() {
Some(_) => ()
None => raise EndOfInput
}
match header.read_byte() {
Some(_) => ()
None => raise EndOfInput
}
let interlace_method = match header.read_byte() {
Some(byte) => byte.to_int()
None => raise EndOfInput
}
if interlace_method != 0 {
raise BadPNG("read_png: interlaced PNGs not supported")
}
let idat_chunks : Array[PdfBytes] = []
let mut done = false
while !done {
try cursor.pdf_png_read_chunk() catch {
_ => done = true
} noraise {
(chunk_type, chunk_data) =>
if chunk_type == pdf_png_idat_chunk_type {
idat_chunks.push(chunk_data)
}
}
}
{ width, height, bit_depth, color_type, idat: pdf_png_concat(idat_chunks) }
}
///|
/// Read a non-palette, non-interlaced PNG from a byte view.
pub fn pdf_read_png_view(data : BytesView) -> PdfPNG raise PdfError {
pdf_read_png_view_inner(data) catch {
BadPNG(message) => raise BadPNG(message)
EndOfInput => raise BadPNG("read_png: truncated input")
InvalidReadLength(length) =>
raise BadPNG("read_png: invalid chunk length " + length.to_string())
error => raise error
}
}
///|
/// Read a non-palette, non-interlaced PNG from owned bytes.
pub fn pdf_read_png(data : PdfBytes) -> PdfPNG raise PdfError {
pdf_read_png_view(data)
}