use std::fmt;
pub use pdfrum_cmap::{CharCode, Cid};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Gid(pub u16);
impl From<pdfrum_type1::Gid> for Gid {
fn from(g: pdfrum_type1::Gid) -> Self {
Self(g.0)
}
}
impl From<Gid> for pdfrum_type1::Gid {
fn from(g: Gid) -> Self {
Self(g.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FontId(pub u64);
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct GlyphName(Box<[u8]>);
impl GlyphName {
#[must_use]
pub fn new(bytes: impl Into<Box<[u8]>>) -> Self {
Self(bytes.into())
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
#[must_use]
pub fn as_str(&self) -> Option<&str> {
std::str::from_utf8(&self.0).ok()
}
}
impl fmt::Debug for GlyphName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.as_str() {
Some(s) => write!(f, "GlyphName({s:?})"),
None => write!(f, "GlyphName({:?})", self.0),
}
}
}
impl From<&str> for GlyphName {
fn from(s: &str) -> Self {
Self::new(s.as_bytes().to_vec())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct FontFlags(u32);
impl FontFlags {
pub const FIXED_PITCH: Self = Self(1 << 0);
pub const SERIF: Self = Self(1 << 1);
pub const SYMBOLIC: Self = Self(1 << 2);
pub const SCRIPT: Self = Self(1 << 3);
pub const NON_SYMBOLIC: Self = Self(1 << 5);
pub const ITALIC: Self = Self(1 << 6);
pub const ALL_CAP: Self = Self(1 << 16);
pub const SMALL_CAP: Self = Self(1 << 17);
pub const FORCE_BOLD: Self = Self(1 << 18);
pub const USE_EXTERN_ATTR: Self = Self(1 << 19);
pub const NONE: Self = Self(0);
pub const DEFAULT: Self = Self::NON_SYMBOLIC;
#[must_use]
pub const fn bits(self) -> u32 {
self.0
}
#[must_use]
pub const fn from_bits(bits: u32) -> Self {
Self(bits)
}
#[must_use]
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
#[must_use]
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
#[must_use]
pub const fn with(self, other: Self) -> Self {
self.union(other)
}
#[must_use]
pub const fn without(self, other: Self) -> Self {
Self(self.0 & !other.0)
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn is_symbolic(self) -> bool {
self.contains(Self::SYMBOLIC)
}
#[must_use]
pub const fn is_non_symbolic(self) -> bool {
self.contains(Self::NON_SYMBOLIC)
}
#[must_use]
pub const fn is_italic(self) -> bool {
self.contains(Self::ITALIC)
}
#[must_use]
pub const fn is_fixed_pitch(self) -> bool {
self.contains(Self::FIXED_PITCH)
}
#[must_use]
pub const fn uses_extern_attr(self) -> bool {
self.contains(Self::USE_EXTERN_ATTR)
}
#[must_use]
pub const fn is_all_cap(self) -> bool {
self.contains(Self::ALL_CAP)
}
}
impl std::ops::BitOr for FontFlags {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
self.union(rhs)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flag_predicates_read_the_right_bits() {
let f = FontFlags::SYMBOLIC | FontFlags::ITALIC;
assert!(f.is_symbolic());
assert!(f.is_italic());
assert!(!f.is_non_symbolic());
assert!(!f.uses_extern_attr());
assert_eq!(FontFlags::DEFAULT.bits(), 32);
}
#[test]
fn with_and_without_are_inverses() {
let f = FontFlags::NONE.with(FontFlags::ALL_CAP);
assert!(f.is_all_cap());
assert!(!f.without(FontFlags::ALL_CAP).is_all_cap());
}
#[test]
fn unknown_bits_round_trip() {
let reserved = 1 << 30;
let f = FontFlags::from_bits(reserved | FontFlags::SERIF.bits());
assert_eq!(f.bits(), reserved | FontFlags::SERIF.bits());
assert!(f.contains(FontFlags::SERIF));
assert!(!f.contains(FontFlags::ITALIC));
}
#[test]
fn symbolic_and_non_symbolic_co_occur() {
let f = FontFlags::SYMBOLIC | FontFlags::NON_SYMBOLIC;
assert!(f.is_symbolic());
assert!(f.is_non_symbolic());
assert_eq!(f.bits(), (1 << 2) | (1 << 5));
}
#[test]
fn contains_holds_for_a_subset_and_the_empty_set() {
let f = FontFlags::SERIF | FontFlags::ITALIC | FontFlags::ALL_CAP;
assert!(f.contains(FontFlags::SERIF | FontFlags::ALL_CAP));
assert!(f.contains(FontFlags::NONE));
assert!(!f.contains(FontFlags::SERIF | FontFlags::SMALL_CAP));
assert!(FontFlags::NONE.is_empty());
assert!(!f.is_empty());
}
#[test]
fn glyph_names_keep_their_bytes() {
let n = GlyphName::from("quotesingle");
assert_eq!(n.as_bytes(), b"quotesingle");
assert_eq!(n.as_str(), Some("quotesingle"));
let raw = GlyphName::new(vec![0xff, 0xfe]);
assert_eq!(raw.as_str(), None);
}
#[test]
fn gid_round_trips_through_the_type1_newtype() {
let g = Gid(42);
assert_eq!(Gid::from(pdfrum_type1::Gid::from(g)), g);
}
}