///|
fn pdf_ascii_hex_digit(value : Int) -> Byte {
if value < 10 {
(48 + value).to_byte()
} else {
(55 + value).to_byte()
}
}
///|
fn pdf_ascii_hex_value(value : Int) -> Int? {
match value {
value if value >= 48 && value <= 57 => Some(value - 48)
value if value >= 65 && value <= 70 => Some(value - 55)
value if value >= 97 && value <= 102 => Some(value - 87)
_ => None
}
}
///|
/// Encode bytes using PDF ASCIIHexDecode syntax.
///
/// The returned bytes are uppercase hexadecimal pairs followed by the `>`
/// end-of-data marker.
pub fn pdf_ascii_hex_encode_view(data : BytesView) -> PdfBytes {
let output = Array::make(data.length() * 2 + 1, b'>')
let mut position = 0
for byte in data {
let value = byte.to_int()
output[position] = pdf_ascii_hex_digit(value / 16)
output[position + 1] = pdf_ascii_hex_digit(value % 16)
position += 2
}
Bytes::from_array(output)
}
///|
/// Owned-byte wrapper for `pdf_ascii_hex_encode_view`.
pub fn pdf_ascii_hex_encode(data : PdfBytes) -> PdfBytes {
pdf_ascii_hex_encode_view(data)
}
///|
/// Decode PDF ASCIIHexDecode data.
///
/// Whitespace is ignored and a trailing odd nibble is padded with zero when the
/// `>` marker is reached. Invalid hex digits raise `PdfError::InvalidHexEscape`.
pub fn pdf_ascii_hex_decode_view(data : BytesView) -> PdfBytes raise PdfError {
let output = Array::make((data.length() + 1) / 2, b'\x00')
let mut position = 0
let mut high : Int? = None
let mut done = false
for byte in data {
if !done {
let value = byte.to_int()
if pdf_is_whitespace_byte(value) {
()
} else if value == 62 {
match high {
Some(high_nibble) => {
output[position] = (high_nibble * 16).to_byte()
position += 1
}
None => ()
}
done = true
} else {
match pdf_ascii_hex_value(value) {
Some(nibble) =>
match high {
Some(high_nibble) => {
output[position] = (high_nibble * 16 + nibble).to_byte()
position += 1
high = None
}
None => high = Some(nibble)
}
None => raise InvalidHexEscape
}
}
}
}
Bytes::from_array(output[:position])
}
///|
/// Owned-byte wrapper for `pdf_ascii_hex_decode_view`.
pub fn pdf_ascii_hex_decode(data : PdfBytes) -> PdfBytes raise PdfError {
pdf_ascii_hex_decode_view(data)
}
///|
let pdf_ascii85_zero : Byte = 122
///|
let pdf_ascii85_terminator : Byte = 126
///|
let pdf_ascii85_max_word : UInt64 = 4294967295
///|
fn pdf_ascii85_encode_word(
output : Array[Byte],
position : Int,
word : UInt64,
output_length : Int,
compress_zero : Bool,
) -> Int {
let mut current = position
if compress_zero && word == 0 {
output[current] = pdf_ascii85_zero
current += 1
} else {
let digits = Array::make(5, b'!')
let mut value = word
for index in 0..<5 {
digits[index] = ((value % 85).to_int() + 33).to_byte()
value = value / 85
}
for i in 0.. UInt64 {
let mut word : UInt64 = 0
for i in 0..<4 {
let byte = if i < count { data[start + i] } else { 0 }
word = word * 256 + byte.to_uint64()
}
word
}
///|
/// Encode bytes using PDF ASCII85Decode syntax.
///
/// Four zero bytes are compressed as `z`, partial final groups are shortened,
/// and the result ends with `~>`.
pub fn pdf_ascii85_encode_view(data : BytesView) -> PdfBytes {
let output = Array::make((data.length() + 3) / 4 * 5 + 2, b' ')
let mut position = 0
let mut index = 0
while index < data.length() {
let remaining = data.length() - index
let count = if remaining < 4 { remaining } else { 4 }
let word = pdf_ascii85_word_from_view(data, index, count)
position = pdf_ascii85_encode_word(
output,
position,
word,
count + 1,
count == 4,
)
index += count
}
output[position] = pdf_ascii85_terminator
output[position + 1] = b'>'
Bytes::from_array(output[:position + 2])
}
///|
/// Owned-byte wrapper for `pdf_ascii85_encode_view`.
pub fn pdf_ascii85_encode(data : PdfBytes) -> PdfBytes {
pdf_ascii85_encode_view(data)
}
///|
fn pdf_ascii85_decode_group(
digits : ArrayView[Int],
output : Array[Byte],
position : Int,
output_length : Int,
) -> Int raise PdfError {
let mut word : UInt64 = 0
for digit in digits {
word = word * 85 + digit.to_uint64()
}
if word > pdf_ascii85_max_word {
raise InvalidASCII85Data
}
let mut current = position
if output_length >= 1 {
output[current] = (word / 16777216).to_byte()
current += 1
}
if output_length >= 2 {
output[current] = (word / 65536 % 256).to_byte()
current += 1
}
if output_length >= 3 {
output[current] = (word / 256 % 256).to_byte()
current += 1
}
if output_length >= 4 {
output[current] = (word % 256).to_byte()
current += 1
}
current
}
///|
fn pdf_ascii85_finish_partial(
digits : Array[Int],
output : Array[Byte],
position : Int,
) -> Int raise PdfError {
if digits.length() == 1 {
raise InvalidASCII85Data
} else if digits.length() > 1 {
let output_length = digits.length() - 1
while digits.length() < 5 {
digits.push(84)
}
pdf_ascii85_decode_group(digits, output, position, output_length)
} else {
position
}
}
///|
/// Decode PDF ASCII85Decode data.
///
/// Whitespace is ignored. Missing terminators, malformed partial groups,
/// invalid `z` placement, and overflowing groups raise `PdfError::InvalidASCII85Data`.
pub fn pdf_ascii85_decode_view(data : BytesView) -> PdfBytes raise PdfError {
let output = Array::make(data.length() * 4, b'\x00')
let mut position = 0
let mut digits : Array[Int] = Array::new(capacity=5)
let mut done = false
for byte in data {
if !done {
let value = byte.to_int()
if pdf_is_whitespace_byte(value) {
()
} else if byte == pdf_ascii85_terminator {
position = pdf_ascii85_finish_partial(digits, output, position)
done = true
} else if byte == pdf_ascii85_zero {
if digits.length() == 0 {
for _ in 0..<4 {
output[position] = b'\x00'
position += 1
}
} else {
raise InvalidASCII85Data
}
} else if value >= 33 && value <= 117 {
digits.push(value - 33)
if digits.length() == 5 {
position = pdf_ascii85_decode_group(digits, output, position, 4)
digits = Array::new(capacity=5)
}
} else {
raise InvalidASCII85Data
}
}
}
if !done {
raise InvalidASCII85Data
}
Bytes::from_array(output[:position])
}
///|
/// Owned-byte wrapper for `pdf_ascii85_decode_view`.
pub fn pdf_ascii85_decode(data : PdfBytes) -> PdfBytes raise PdfError {
pdf_ascii85_decode_view(data)
}