///|
// Strategy: base64-byte-encoding
priv struct Base64Encoder {
wrap : Int32
mut column : Int32
mut pending : Int32
mut count : Int32
}
///|
fn Base64Encoder::new(wrap : Int32) -> Base64Encoder {
{ wrap, column: 0, pending: 0, count: 0, }
}
///|
let encode_alphabet : Bytes = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
///|
fn encode_word(word : Int32, output : FixedArray[Byte], offset : Int32) -> Unit {
output[offset] = encode_alphabet[(word >> 18) & 63]
output[offset + 1] = encode_alphabet[(word >> 12) & 63]
output[offset + 2] = encode_alphabet[(word >> 6) & 63]
output[offset + 3] = encode_alphabet[word & 63]
}
///|
fn Base64Encoder::wrapped(
self : Base64Encoder,
raw : FixedArray[Byte],
ending~ : Bool = false,
) -> Bytes {
if self.wrap == 0 {
return raw.unsafe_reinterpret_as_bytes()
}
let total = self.column.to_int64() + raw.length().to_int64()
let breaks = total / self.wrap.to_int64()
let tail = if ending && total % self.wrap.to_int64() != 0 { 1 } else { 0 }
let output = FixedArray::make(raw.length() + breaks.to_int() + tail, b'\n')
let mut source = 0
let mut destination = 0
while source < raw.length() {
let count = (self.wrap - self.column).min(raw.length() - source)
raw.blit_to(output, src_offset=source, dst_offset=destination, len=count)
source += count
destination += count
self.column += count
if self.column == self.wrap {
self.column = 0
destination += 1
}
}
output.unsafe_reinterpret_as_bytes()
}
///|
fn Base64Encoder::encode(self : Base64Encoder, input : BytesView) -> Bytes {
let output = FixedArray::make((input.length() + self.count) / 3 * 4, b'=')
let mut index = 0
let mut target = 0
if self.count != 0 {
while self.count < 3 && index < input.length() {
self.pending = (self.pending << 8) | input[index].to_int()
self.count += 1
index += 1
}
if self.count == 3 {
encode_word(self.pending, output, 0)
self.pending = 0
self.count = 0
target = 4
}
}
while index + 2 < input.length() {
let word = (input[index].to_int() << 16) |
(input[index + 1].to_int() << 8) |
input[index + 2].to_int()
encode_word(word, output, target)
index += 3
target += 4
}
while index < input.length() {
self.pending = (self.pending << 8) | input[index].to_int()
self.count += 1
index += 1
}
self.wrapped(output)
}
///|
fn Base64Encoder::finish(self : Base64Encoder) -> Bytes {
let output = FixedArray::make(if self.count == 0 { 0 } else { 4 }, b'=')
if self.count != 0 {
let word = self.pending << ((3 - self.count) * 8)
output[0] = encode_alphabet[(word >> 18) & 63]
output[1] = encode_alphabet[(word >> 12) & 63]
if self.count == 2 {
output[2] = encode_alphabet[(word >> 6) & 63]
}
}
self.pending = 0
self.count = 0
self.wrapped(output, ending=true)
}