///|
let pdf_png_ihdr_chunk_type : UInt = 0x49484452U
///|
let pdf_png_idat_chunk_type : UInt = 0x49444154U
///|
let pdf_png_plte_chunk_type : UInt = 0x504C5445U
///|
let pdf_png_trns_chunk_type : UInt = 0x74524E53U
///|
let pdf_png_max_int64 : Int64 = 2147483647L
///|
/// What a PNG without an alpha channel says of its transparency, in its
/// `tRNS` chunk.
pub(all) enum PdfPNGTransparency {
/// Greyscale and truecolour: the one colour whose pixels are fully
/// transparent, as its samples (grey, or red, green and blue) of the
/// image's bit depth.
PdfPNGColourKey(Array[Int])
/// A palette image: the alpha of the palette's first entries, a byte each;
/// the entries past them are opaque. Only with an entry below 255: a
/// `tRNS` of opaque entries declares no transparency.
PdfPNGPaletteAlpha(@core.PdfBytes)
} derive(Debug, Eq, ToJson)
///|
/// Parsed PNG metadata and concatenated IDAT payload.
///
/// This mirrors the small `Cpdfpng` surface needed for image insertion,
/// with the compressed IDAT bytes preserved. `idat` is always a
/// *non-interlaced* datastream — an Adam7 source is rebuilt into the
/// equivalent sequential one when it is read, so consumers never see
/// interlacing. `palette` carries the PLTE payload (RGB triples) for
/// colour type 3 and is `None` for every other type.
/// `transparency` is what a `tRNS` chunk before the image data declares,
/// `None` without one.
pub(all) struct PdfPNG {
width : Int
height : Int
bit_depth : Int
color_type : Int
idat : @core.PdfBytes
palette : @core.PdfBytes?
transparency : PdfPNGTransparency?
} derive(Debug, Eq, ToJson)
///|
fn pdf_png_u32_to_int(
value : UInt,
context : String,
) -> Int raise @core.PdfError {
let wide = value.to_int64()
if wide > pdf_png_max_int64 {
raise BadPNG(context)
}
wide.to_int()
}
///|
fn pdf_png_read_chunk(
cursor : @core.ByteCursor,
) -> (UInt, @core.PdfBytes) raise @core.PdfError {
let length = pdf_png_u32_to_int(
byte_cursor_read_u32_be(cursor),
"read_png: chunk too large",
)
let chunk_type = byte_cursor_read_u32_be(cursor)
let chunk_data = cursor.read_bytes(length)
ignore(byte_cursor_read_u32_be(cursor))
(chunk_type, chunk_data)
}
///|
fn pdf_png_concat(chunks : ArrayView[@core.PdfBytes]) -> @core.PdfBytes {
let mut length = 0
for chunk in chunks {
length += chunk.length()
}
let output = Array::make(length, b'\x00')
let mut position = 0
for chunk in chunks {
for index in 0.. PdfPNG raise @core.PdfError {
let cursor = @core.byte_cursor_of_view(data, source="png")
try! cursor.seek(8)
let (first_chunk_type, first_chunk_data) = pdf_png_read_chunk(cursor)
if first_chunk_type != pdf_png_ihdr_chunk_type {
raise BadPNG("read_png: first table not IHDR")
}
let header = @core.byte_cursor_of_view(first_chunk_data, source="png IHDR")
let width = pdf_png_u32_to_int(
byte_cursor_read_u32_be(header),
"read_png: width too large",
)
let height = pdf_png_u32_to_int(
byte_cursor_read_u32_be(header),
"read_png: height too large",
)
// PNG has no image without pixels
if width == 0 || height == 0 {
raise BadPNG("read_png: zero width or height")
}
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 != 3 &&
color_type != 4 &&
color_type != 6 {
raise BadPNG("read_png: unknown colour type " + color_type.to_string())
}
// Palette depth is validated here even on the sequential path (which
// historically checks no depths): indices cap at one byte, and a
// 16-bit depth passed through would emit an /Indexed image PDF forbids
// rather than fail.
if color_type == 3 &&
bit_depth != 1 &&
bit_depth != 2 &&
bit_depth != 4 &&
bit_depth != 8 {
raise BadPNG(
"read_png: bit depth " +
bit_depth.to_string() +
" invalid for a palette image",
)
}
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 && interlace_method != 1 {
raise BadPNG(
"read_png: unknown interlace method " + interlace_method.to_string(),
)
}
let idat_chunks : Array[@core.PdfBytes] = []
let mut palette : @core.PdfBytes? = None
let mut trns : @core.PdfBytes? = None
let mut transparency : PdfPNGTransparency? = None
let mut done = false
while !done {
try pdf_png_read_chunk(cursor) catch {
_ => done = true
} noraise {
(chunk_type, chunk_data) =>
if chunk_type == pdf_png_idat_chunk_type {
idat_chunks.push(chunk_data)
} else if chunk_type == pdf_png_plte_chunk_type {
palette = Some(chunk_data)
} else if chunk_type == pdf_png_trns_chunk_type &&
(color_type == 0 || color_type == 2) {
// A colour key: one two-byte sample for grey, three for red, green
// and blue. As decoders have it, only a chunk of just that, before
// the image data, counts, and the first of them; the bits above
// the bit depth are cleared.
let samples = if color_type == 0 { 1 } else { 3 }
if transparency is None &&
idat_chunks.is_empty() &&
chunk_data.length() == 2 * samples {
let sample_mask = if bit_depth < 16 {
(1 << bit_depth) - 1
} else {
0xFFFF
}
transparency = Some(
PdfPNGColourKey(
Array::makei(samples, i => {
(
(chunk_data[2 * i].to_int() << 8) |
chunk_data[2 * i + 1].to_int()
) &
sample_mask
}),
),
)
}
} else if chunk_type == pdf_png_trns_chunk_type &&
color_type == 3 &&
idat_chunks.is_empty() {
// A palette's per-entry alpha (as a colour key, only a chunk before
// the image data counts).
// PNG allows one tRNS at most. Last-write-wins would let a
// second, shorter chunk hide that the first named entries the
// palette does not have.
if trns is Some(_) {
raise BadPNG("read_png: duplicate tRNS chunk")
}
trns = Some(chunk_data)
}
}
}
let palette = match (palette, color_type) {
(Some(plte), 3) => {
if plte.length() == 0 || plte.length() % 3 != 0 || plte.length() > 768 {
raise BadPNG(
"read_png: malformed palette of " +
plte.length().to_string() +
" bytes",
)
}
// tRNS may not name more entries than the palette has. Checked
// here rather than at the chunk, because the scan is
// order-independent and the palette may not have been seen yet.
match trns {
Some(alpha) =>
if alpha.length() > plte.length() / 3 {
raise BadPNG("read_png: tRNS longer than the palette")
}
None => ()
}
Some(plte)
}
(None, 3) => raise BadPNG("read_png: palette image without a PLTE chunk")
// PLTE on a truecolour image is a quantisation hint; on greyscale it
// is invalid. Neither affects how the pixels decode, so it is dropped
// rather than carried.
(_, _) => None
}
// A tRNS whose entries are all 255 declares no transparency at all —
// every listed entry is fully opaque and PNG defines the unlisted rest as
// opaque too — so that common encoder artefact needs no mask.
let transparency = match trns {
Some(alpha) if palette is Some(_) && alpha.iter().any(a => a != b'\xff') =>
Some(PdfPNGPaletteAlpha(alpha))
_ => transparency
}
let concatenated = pdf_png_concat(idat_chunks)
let idat = if interlace_method == 1 {
pdf_png_deinterlace(width, height, bit_depth, color_type, concatenated)
} else {
concatenated
}
{ width, height, bit_depth, color_type, idat, palette, transparency, }
}
///|
/// Read a PNG from a byte view, de-interlacing an Adam7 image into a
/// sequential IDAT stream. Palette images carry their PLTE, and their tRNS
/// when it makes any entry transparent.
pub fn pdf_read_png_view(data : BytesView) -> PdfPNG raise @core.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 PNG from owned bytes, de-interlacing an Adam7 image into a
/// sequential IDAT stream. Palette images carry their PLTE, and their tRNS
/// when it makes any entry transparent.
pub fn pdf_read_png(data : @core.PdfBytes) -> PdfPNG raise @core.PdfError {
pdf_read_png_view(data)
}