#![cfg(feature = "api-level-29")]
use std::convert::TryFrom;
use std::ffi::{CStr, OsStr};
use std::fmt::{self, Write};
use std::os::unix::prelude::OsStrExt;
use std::path::Path;
use std::ptr::NonNull;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct FontWeight(u16);
impl FontWeight {
pub const fn new(value: u16) -> Result<Self, FontWeightValueError> {
if Self::MIN.0 <= value && value <= Self::MAX.0 {
Ok(Self(value))
} else {
Err(FontWeightValueError(()))
}
}
pub const fn to_u16(self) -> u16 {
self.0
}
pub const MIN: FontWeight = FontWeight(ffi::AFONT_WEIGHT_MIN as u16 + 1);
pub const THIN: FontWeight = FontWeight(ffi::AFONT_WEIGHT_THIN as u16);
pub const EXTRA_LIGHT: FontWeight = FontWeight(ffi::AFONT_WEIGHT_EXTRA_LIGHT as u16);
pub const LIGHT: FontWeight = FontWeight(ffi::AFONT_WEIGHT_LIGHT as u16);
pub const NORMAL: FontWeight = FontWeight(ffi::AFONT_WEIGHT_NORMAL as u16);
pub const MEDIUM: FontWeight = FontWeight(ffi::AFONT_WEIGHT_MEDIUM as u16);
pub const SEMI_BOLD: FontWeight = FontWeight(ffi::AFONT_WEIGHT_SEMI_BOLD as u16);
pub const BOLD: FontWeight = FontWeight(ffi::AFONT_WEIGHT_BOLD as u16);
pub const EXTRA_BOLD: FontWeight = FontWeight(ffi::AFONT_WEIGHT_EXTRA_BOLD as u16);
pub const BLACK: FontWeight = FontWeight(ffi::AFONT_WEIGHT_BLACK as u16);
pub const MAX: FontWeight = FontWeight(ffi::AFONT_WEIGHT_MAX as u16);
}
impl fmt::Display for FontWeight {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
FontWeight::THIN => f.write_str("Thin"),
FontWeight::EXTRA_LIGHT => f.write_str("Extra Light (Ultra Light)"),
FontWeight::LIGHT => f.write_str("Light"),
FontWeight::NORMAL => f.write_str("Normal (Regular)"),
FontWeight::MEDIUM => f.write_str("Medium"),
FontWeight::SEMI_BOLD => f.write_str("Semi Bold (Demi Bold)"),
FontWeight::BOLD => f.write_str("Bold"),
FontWeight::EXTRA_BOLD => f.write_str("Extra Bold (Ultra Bold)"),
FontWeight::BLACK => f.write_str("Black (Heavy)"),
_ => writeln!(f, "{}", self.0),
}
}
}
#[derive(Debug)]
pub struct FontWeightValueError(());
impl fmt::Display for FontWeightValueError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str("font weight must be positive and less than or equal to 1000")
}
}
impl std::error::Error for FontWeightValueError {}
impl TryFrom<u16> for FontWeight {
type Error = FontWeightValueError;
fn try_from(value: u16) -> Result<Self, Self::Error> {
FontWeight::new(value)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct AxisTag(u32);
impl AxisTag {
pub const fn from_be_bytes_checked(value: [u8; 4]) -> Result<Self, AxisTagValueError> {
macro_rules! check_byte_range {
($($e:expr)+) => {
$(
if !(value[$e] as char).is_ascii_graphic() && value[$e] != b' ' {
return Err(AxisTagValueError::InvalidCharacter);
}
)+
};
}
check_byte_range!(0 1 2 3);
if value[0] == b' ' {
return Err(
if value[1] == b' ' && value[2] == b' ' && value[3] == b' ' {
AxisTagValueError::EmptyTag
} else {
AxisTagValueError::InvalidSpacePadding
},
);
}
macro_rules! check_if_valid {
($e:expr ; $($f:expr)+) => {
if value[$e] == b' ' {
return if true $(&& value[$f] == b' ')+ {
Ok(Self(u32::from_be_bytes(value)))
} else {
Err(AxisTagValueError::InvalidSpacePadding)
};
}
};
}
check_if_valid!(1; 2 3);
check_if_valid!(2; 3);
Ok(Self(u32::from_be_bytes(value)))
}
pub const fn from_be_checked(value: u32) -> Result<Self, AxisTagValueError> {
Self::from_be_bytes_checked(value.to_be_bytes())
}
pub const fn from_be_bytes(value: [u8; 4]) -> Self {
Self::unwrap_result(Self::from_be_bytes_checked(value))
}
pub const fn from_be(value: u32) -> Self {
Self::unwrap_result(Self::from_be_checked(value))
}
const fn unwrap_result(result: Result<Self, AxisTagValueError>) -> Self {
match result {
Ok(t) => t,
Err(e) => panic!("{}", e.as_str()),
}
}
pub const fn to_u32(self) -> u32 {
self.0
}
pub const fn to_be_bytes(self) -> [u8; 4] {
self.0.to_be_bytes()
}
}
impl fmt::Display for AxisTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let bytes = self.to_be_bytes();
f.write_char(bytes[0] as char)?;
f.write_char(bytes[1] as char)?;
f.write_char(bytes[2] as char)?;
f.write_char(bytes[3] as char)
}
}
impl fmt::Debug for AxisTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AxisTag({} {:#x})", self, self.0)
}
}
#[derive(Clone, Copy, Debug)]
pub enum AxisTagValueError {
InvalidCharacter,
InvalidSpacePadding,
EmptyTag,
}
impl AxisTagValueError {
pub const fn as_str(&self) -> &'static str {
match self {
Self::InvalidCharacter => "each byte in an axis tag must be in the range 0x20 to 0x7E",
Self::InvalidSpacePadding => {
"a space character cannot be followed by a non-space character"
}
Self::EmptyTag => "a tag must have one to four non-space characters",
}
}
}
impl fmt::Display for AxisTagValueError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.write_str(self.as_str())
}
}
impl std::error::Error for AxisTagValueError {}
#[derive(Debug)]
pub struct Font {
ptr: NonNull<ffi::AFont>,
}
impl Font {
pub unsafe fn from_ptr(ptr: NonNull<ffi::AFont>) -> Self {
Self { ptr }
}
pub fn ptr(&self) -> NonNull<ffi::AFont> {
self.ptr
}
pub fn axis_count(&self) -> usize {
unsafe { ffi::AFont_getAxisCount(self.ptr.as_ptr()) }
}
pub fn axis_tag_at(&self, idx: usize) -> AxisTag {
AxisTag(unsafe { ffi::AFont_getAxisTag(self.ptr.as_ptr(), idx as u32) })
}
pub fn axis_value_at(&self, idx: usize) -> f32 {
unsafe { ffi::AFont_getAxisValue(self.ptr.as_ptr(), idx as u32) }
}
pub fn collection_index(&self) -> usize {
unsafe { ffi::AFont_getCollectionIndex(self.ptr.as_ptr()) }
}
pub fn path(&self) -> &Path {
let path = unsafe { CStr::from_ptr(ffi::AFont_getFontFilePath(self.ptr.as_ptr())) };
OsStr::from_bytes(path.to_bytes()).as_ref()
}
pub fn locale(&self) -> Option<&CStr> {
let ptr = unsafe { ffi::AFont_getLocale(self.ptr.as_ptr()) };
if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr) })
}
}
pub fn weight(&self) -> FontWeight {
FontWeight(unsafe { ffi::AFont_getWeight(self.ptr.as_ptr()) })
}
pub fn is_italic(&self) -> bool {
unsafe { ffi::AFont_isItalic(self.ptr.as_ptr()) }
}
}
impl Drop for Font {
fn drop(&mut self) {
unsafe { ffi::AFont_close(self.ptr.as_ptr()) }
}
}
#[repr(u32)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum FamilyVariant {
Compact = ffi::AFAMILY_VARIANT_COMPACT as _,
Default = ffi::AFAMILY_VARIANT_DEFAULT as _,
Elegant = ffi::AFAMILY_VARIANT_ELEGANT as _,
}
#[derive(Debug)]
pub struct FontMatcher {
ptr: NonNull<ffi::AFontMatcher>,
}
impl FontMatcher {
pub unsafe fn from_ptr(ptr: NonNull<ffi::AFontMatcher>) -> Self {
Self { ptr }
}
pub fn ptr(&self) -> NonNull<ffi::AFontMatcher> {
self.ptr
}
pub fn new() -> Self {
let ptr = NonNull::new(unsafe { ffi::AFontMatcher_create() })
.expect("AFontMatcher_create returned NULL");
unsafe { FontMatcher::from_ptr(ptr) }
}
pub fn match_font(
&mut self,
family_name: &CStr,
text: &[u16],
run_length_out: Option<&mut u32>,
) -> Font {
if text.is_empty() {
panic!("text is empty");
}
unsafe {
Font::from_ptr(
NonNull::new(ffi::AFontMatcher_match(
self.ptr.as_ptr(),
family_name.as_ptr(),
text.as_ptr(),
text.len() as _,
run_length_out.map_or(std::ptr::null_mut(), |u| u),
))
.expect("AFontMatcher_match returned NULL"),
)
}
}
pub fn set_family_variant(&mut self, family_variant: FamilyVariant) {
unsafe { ffi::AFontMatcher_setFamilyVariant(self.ptr.as_ptr(), family_variant as u32) }
}
pub fn set_locales(&mut self, language_tags: &CStr) {
unsafe { ffi::AFontMatcher_setLocales(self.ptr.as_ptr(), language_tags.as_ptr()) }
}
pub fn set_style(&mut self, weight: FontWeight, italic: bool) {
unsafe { ffi::AFontMatcher_setStyle(self.ptr.as_ptr(), weight.to_u16(), italic) }
}
}
impl Drop for FontMatcher {
fn drop(&mut self) {
unsafe { ffi::AFontMatcher_destroy(self.ptr.as_ptr()) }
}
}
#[derive(Debug)]
pub struct SystemFontIterator {
ptr: NonNull<ffi::ASystemFontIterator>,
}
impl SystemFontIterator {
pub unsafe fn from_ptr(ptr: NonNull<ffi::ASystemFontIterator>) -> Self {
Self { ptr }
}
pub fn ptr(&self) -> NonNull<ffi::ASystemFontIterator> {
self.ptr
}
pub fn new() -> Option<Self> {
NonNull::new(unsafe { ffi::ASystemFontIterator_open() })
.map(|p| unsafe { SystemFontIterator::from_ptr(p) })
}
}
impl Iterator for SystemFontIterator {
type Item = Font;
fn next(&mut self) -> Option<Self::Item> {
NonNull::new(unsafe { ffi::ASystemFontIterator_next(self.ptr.as_ptr()) })
.map(|p| unsafe { Font::from_ptr(p) })
}
}
impl Drop for SystemFontIterator {
fn drop(&mut self) {
unsafe { ffi::ASystemFontIterator_close(self.ptr.as_ptr()) }
}
}