Skip to main content

src/internal/encoder/base64.gleam

//// Encode base64 strings according to RFC 2045.
////
//// See the following link for reference:
//// - <https://tools.ietf.org/html/rfc2045>

import gleam/bit_array
import gleam/list
import gleam/pair
import gleam/string

import internal/encoder/encoding

/// Estimates the base64 encoded size of a string in bytes.
pub fn estimate_encoded_size(
  of string: String,
) -> Result(Int, encoding.EncoderError) {
  Ok(string.byte_size(string) * 8 / 6)
}

/// Base64 encode a string, taking the preferred line size into
/// account and pretending to start the first line at the passed
/// start position.
pub fn encode_string(
  encode string: String,
  start position: Int,
  preferred_size preferred_size: Int,
) -> Result(List(String), encoding.EncoderError) {
  let chunk_size = preferred_size * 6 / 8

  string
  |> string.split("")
  |> chunk(position, chunk_size)
  |> list.map(bit_array.from_string)
  |> list.map(bit_array.base64_encode(_, True))
  |> Ok
}

fn chunk(
  characters: List(String),
  used_size: Int,
  chunk_size: Int,
) -> List(String) {
  characters
  |> list.fold(#(used_size, []), fn(accumulator, character) {
    let #(used_size, chunks) = accumulator

    let character_size = string.byte_size(character)
    case chunks {
      [] -> #(character_size, [character])
      [chunk, ..other_chunks] if used_size + character_size <= chunk_size -> #(
        used_size + character_size,
        [chunk <> character, ..other_chunks],
      )
      chunks -> #(character_size, [character, ..chunks])
    }
  })
  |> pair.second()
  |> list.reverse()
}