///|
fn pdf_text_valid_unicode_codepoint(codepoint : Int) -> Bool {
  codepoint >= 0 &&
  codepoint <= 0x10FFFF &&
  !(codepoint >= 0xD800 && codepoint <= 0xDFFF)
}

///|
fn pdf_text_packed_charcode_length(code : Int) -> Int {
  if code >= 0 && code <= 0xFF {
    1
  } else if code >= 0 && code <= 0xFFFF {
    2
  } else if code >= 0 && code <= 0xFFFFFF {
    3
  } else {
    4
  }
}

///|
fn pdf_text_packed_charcode_bytes(code : Int, length : Int) -> Bytes {
  Bytes::makei(length, fn(index) {
    ((code >> ((length - index - 1) * 8)) & 0xFF).to_byte()
  })
}

///|
fn pdf_text_pack_two_bytes(first : Int, second : Int) -> Int {
  (first << 8) | second
}

///|
fn pdf_text_pack_four_bytes(
  first : Int,
  second : Int,
  third : Int,
  fourth : Int,
) -> Int {
  (((((first << 8) | second) << 8) | third) << 8) | fourth
}

///|
fn pdf_text_pack_bytes_as_charcode(bytes : BytesView) -> Int {
  let mut code = 0
  for byte in bytes {
    code = (code << 8) | byte.to_int()
  }
  code
}

///|
fn pdf_text_predefined_utf8_codepoints(code : Int) -> Array[Int]? {
  let bytes = pdf_text_packed_charcode_bytes(
    code,
    pdf_text_packed_charcode_length(code),
  )
  try pdf_codepoints_of_utf8(bytes) catch {
    _ => None
  } noraise {
    codepoints => Some(codepoints)
  }
}

///|
fn pdf_text_predefined_utf8_charcode(codepoint : Int) -> Int? {
  guard pdf_text_valid_unicode_codepoint(codepoint) else { return None }
  if codepoint <= 0x7F {
    Some(codepoint)
  } else if codepoint <= 0x7FF {
    Some(
      pdf_text_pack_two_bytes(
        0xC0 | (codepoint >> 6),
        0x80 | (codepoint & 0x3F),
      ),
    )
  } else if codepoint <= 0xFFFF {
    Some(
      pdf_text_pack_bytes_as_charcode(
        Bytes::makei(3, fn(index) {
          match index {
            0 => (0xE0 | (codepoint >> 12)).to_byte()
            1 => (0x80 | ((codepoint >> 6) & 0x3F)).to_byte()
            _ => (0x80 | (codepoint & 0x3F)).to_byte()
          }
        }),
      ),
    )
  } else {
    Some(
      pdf_text_pack_four_bytes(
        0xF0 | (codepoint >> 18),
        0x80 | ((codepoint >> 12) & 0x3F),
        0x80 | ((codepoint >> 6) & 0x3F),
        0x80 | (codepoint & 0x3F),
      ),
    )
  }
}

///|
fn pdf_text_predefined_utf16_codepoints(code : Int) -> Array[Int]? {
  if code >= 0 && code <= 0xFFFF {
    if pdf_text_valid_unicode_codepoint(code) {
      Some([code])
    } else {
      None
    }
  } else if pdf_text_packed_charcode_length(code) == 4 {
    let bytes = pdf_text_packed_charcode_bytes(code, 4)
    try pdf_codepoints_of_utf16be(bytes) catch {
      _ => None
    } noraise {
      codepoints => Some(codepoints)
    }
  } else {
    None
  }
}

///|
fn pdf_text_predefined_utf16_charcode(codepoint : Int) -> Int? {
  guard pdf_text_valid_unicode_codepoint(codepoint) else { return None }
  if codepoint <= 0xFFFF {
    Some(codepoint)
  } else {
    let shifted = codepoint - 0x10000
    let high = 0xD800 | (shifted >> 10)
    let low = 0xDC00 | (shifted & 0x3FF)
    Some(pdf_text_pack_four_bytes(high >> 8, high & 0xFF, low >> 8, low & 0xFF))
  }
}

///|
fn pdf_text_predefined_utf32_codepoints(code : Int) -> Array[Int]? {
  if pdf_text_valid_unicode_codepoint(code) {
    Some([code])
  } else {
    None
  }
}

///|
fn pdf_text_predefined_utf32_charcode(codepoint : Int) -> Int? {
  if pdf_text_valid_unicode_codepoint(codepoint) {
    Some(codepoint)
  } else {
    None
  }
}

///|
fn pdf_text_predefined_direct_unicode_codepoints(
  name : @core.PdfName,
  code : Int,
) -> Array[Int]? {
  if pdf_text_predefined_cmap_uses_utf8_charcodes(name) {
    pdf_text_predefined_utf8_codepoints(code)
  } else if pdf_text_predefined_cmap_uses_utf16_charcodes(name) {
    pdf_text_predefined_utf16_codepoints(code)
  } else if pdf_text_predefined_cmap_uses_utf32_charcodes(name) {
    pdf_text_predefined_utf32_codepoints(code)
  } else if pdf_text_predefined_cmap_uses_ucs2_charcodes(name) {
    pdf_text_predefined_utf32_codepoints(code)
  } else {
    None
  }
}

///|
fn pdf_text_predefined_direct_unicode_charcode(
  name : @core.PdfName,
  codepoint : Int,
) -> Int? {
  if pdf_text_predefined_cmap_uses_utf8_charcodes(name) {
    pdf_text_predefined_utf8_charcode(codepoint)
  } else if pdf_text_predefined_cmap_uses_utf16_charcodes(name) {
    pdf_text_predefined_utf16_charcode(codepoint)
  } else if pdf_text_predefined_cmap_uses_utf32_charcodes(name) {
    pdf_text_predefined_utf32_charcode(codepoint)
  } else if pdf_text_predefined_cmap_uses_ucs2_charcodes(name) {
    if codepoint <= 0xFFFF && pdf_text_valid_unicode_codepoint(codepoint) {
      Some(codepoint)
    } else {
      None
    }
  } else {
    None
  }
}