#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
feature = "fonts-cjk"
))]
#[derive(Clone, Copy)]
pub struct FaceBytes {
pub name: &'static str,
pub bytes: &'static [u8],
}
#[cfg(feature = "fonts-emoji-color")]
#[derive(Clone, Copy)]
pub struct ColorFaceBytes {
pub name: &'static str,
pub bytes: &'static [u8],
}
#[cfg(feature = "fonts-vector-latin")]
mod latin;
#[cfg(feature = "fonts-vector-latin")]
pub use latin::FONT as LATIN;
#[cfg(feature = "fonts-complex")]
mod arabic;
#[cfg(feature = "fonts-complex")]
pub use arabic::FONT as ARABIC;
#[cfg(feature = "fonts-cjk")]
#[cfg_attr(docsrs, doc(cfg(feature = "fonts-cjk")))]
mod cjk;
#[cfg(feature = "fonts-cjk")]
pub use cjk::FONT as CJK;
#[cfg(any(
feature = "fonts-cjk-shard-latin",
feature = "fonts-cjk-shard-symbols",
feature = "fonts-cjk-shard-kana",
feature = "fonts-cjk-shard-fullwidth",
feature = "fonts-cjk-shard-han"
))]
#[cfg_attr(
docsrs,
doc(cfg(any(
feature = "fonts-cjk-shard-latin",
feature = "fonts-cjk-shard-symbols",
feature = "fonts-cjk-shard-kana",
feature = "fonts-cjk-shard-fullwidth",
feature = "fonts-cjk-shard-han"
)))
)]
pub mod cjk_shards;
#[cfg(all(
feature = "runtime-fonts",
any(
feature = "fonts-cjk-shard-latin",
feature = "fonts-cjk-shard-symbols",
feature = "fonts-cjk-shard-kana",
feature = "fonts-cjk-shard-fullwidth",
feature = "fonts-cjk-shard-han"
)
))]
#[cfg_attr(
docsrs,
doc(cfg(all(
feature = "runtime-fonts",
any(
feature = "fonts-cjk-shard-latin",
feature = "fonts-cjk-shard-symbols",
feature = "fonts-cjk-shard-kana",
feature = "fonts-cjk-shard-fullwidth",
feature = "fonts-cjk-shard-han"
)
)))
)]
pub mod cjk_shard_loader;
#[cfg(feature = "fonts-emoji-color")]
mod emoji;
#[cfg(feature = "fonts-emoji-color")]
pub use emoji::FONT as EMOJI;
#[cfg(feature = "fonts-vector-latin")]
const LATIN_FACE: FaceBytes = FaceBytes { name: "Open Sans", bytes: LATIN };
#[cfg(feature = "fonts-complex")]
const ARABIC_FACE: FaceBytes = FaceBytes { name: "Noto Naskh Arabic", bytes: ARABIC };
#[cfg(feature = "fonts-cjk")]
const CJK_FACE: FaceBytes = FaceBytes { name: "Noto Sans SC", bytes: CJK };
#[cfg(feature = "fonts-emoji-color")]
const EMOJI_FACE: ColorFaceBytes = ColorFaceBytes { name: "Noto Color Emoji", bytes: EMOJI };
#[cfg(feature = "fonts-emoji-color")]
pub fn active_color_faces() -> &'static [ColorFaceBytes] {
static SLOTS: [ColorFaceBytes; 1] = [EMOJI_FACE];
&SLOTS
}
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
pub(crate) fn face_covers(face: &FaceBytes, ch: char) -> bool {
ttf_parser::Face::parse(face.bytes, 0).ok().and_then(|parsed| parsed.glyph_index(ch)).is_some()
}
const COVERAGE_CACHE_SLOTS: usize = 64;
#[derive(Clone, Copy)]
struct CoverageSlot {
codepoint: u32,
answer: Option<FaceBytes>,
}
impl CoverageSlot {
const EMPTY_CODEPOINT: u32 = u32::MAX;
const EMPTY: Self = Self { codepoint: Self::EMPTY_CODEPOINT, answer: None };
}
static COVERAGE_CACHE: crate::compat::Mutex<[CoverageSlot; COVERAGE_CACHE_SLOTS]> =
crate::compat::Mutex::new([CoverageSlot::EMPTY; COVERAGE_CACHE_SLOTS]);
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
struct ParsedFaceSlot {
bytes: *const u8,
face: Option<ttf_parser::Face<'static>>,
}
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
unsafe impl Send for ParsedFaceSlot {}
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
unsafe impl Sync for ParsedFaceSlot {}
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
const PARSED_FACE_SLOTS: usize = 4;
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
static PARSED_FACES: crate::compat::Mutex<[Option<ParsedFaceSlot>; PARSED_FACE_SLOTS]> =
crate::compat::Mutex::new([None, None, None, None]);
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
pub fn with_parsed_face<R>(
face: FaceBytes,
f: impl FnOnce(&ttf_parser::Face<'_>) -> R,
) -> Option<R> {
let key = face.bytes.as_ptr();
let mut cache = crate::compat::lock(&PARSED_FACES);
if let Some(index) = cache.iter().position(|slot| slot.as_ref().is_some_and(|s| s.bytes == key))
{
let slot = cache[index].as_ref()?;
return slot.face.as_ref().map(f);
}
let parsed = ttf_parser::Face::parse(face.bytes, 0).ok();
let answer = parsed.as_ref().map(f);
for index in (1..PARSED_FACE_SLOTS).rev() {
cache[index] = cache[index - 1].take();
}
cache[0] = Some(ParsedFaceSlot { bytes: key, face: parsed });
answer
}
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
pub fn clear_parsed_faces() {
let mut cache = crate::compat::lock(&PARSED_FACES);
*cache = [None, None, None, None];
}
#[cfg(feature = "text-shaping")]
struct ShaperFaceSlot {
bytes: *const u8,
face: Option<rustybuzz::Face<'static>>,
}
#[cfg(feature = "text-shaping")]
unsafe impl Send for ShaperFaceSlot {}
#[cfg(feature = "text-shaping")]
unsafe impl Sync for ShaperFaceSlot {}
#[cfg(feature = "text-shaping")]
static SHAPER_FACES: crate::compat::Mutex<[Option<ShaperFaceSlot>; PARSED_FACE_SLOTS]> =
crate::compat::Mutex::new([None, None, None, None]);
#[cfg(feature = "text-shaping")]
pub fn with_shaper_face<R>(
face: FaceBytes,
f: impl FnOnce(&rustybuzz::Face<'_>) -> R,
) -> Option<R> {
let key = face.bytes.as_ptr();
let mut cache = crate::compat::lock(&SHAPER_FACES);
if let Some(index) = cache.iter().position(|slot| slot.as_ref().is_some_and(|s| s.bytes == key))
{
let slot = cache[index].as_ref()?;
return slot.face.as_ref().map(f);
}
let parsed = rustybuzz::Face::from_slice(face.bytes, 0);
let answer = parsed.as_ref().map(f);
for index in (1..PARSED_FACE_SLOTS).rev() {
cache[index] = cache[index - 1].take();
}
cache[0] = Some(ShaperFaceSlot { bytes: key, face: parsed });
answer
}
#[cfg(feature = "text-shaping")]
pub fn clear_shaper_faces() {
let mut cache = crate::compat::lock(&SHAPER_FACES);
*cache = [None, None, None, None];
}
static FACE_GENERATION: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
static SLOT_GENERATIONS: crate::compat::Mutex<[u32; COVERAGE_CACHE_SLOTS]> =
crate::compat::Mutex::new([0u32; COVERAGE_CACHE_SLOTS]);
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
pub fn face_for_char(ch: char) -> Option<FaceBytes> {
let codepoint = ch as u32;
let slot = codepoint as usize % COVERAGE_CACHE_SLOTS;
let generation = FACE_GENERATION.load(core::sync::atomic::Ordering::Acquire);
{
let cache = crate::compat::lock(&COVERAGE_CACHE);
let entry = cache[slot];
if entry.codepoint == codepoint && slot_generation(slot) == generation {
return entry.answer;
}
}
let answer = resolve_face(ch);
{
let mut cache = crate::compat::lock(&COVERAGE_CACHE);
cache[slot] = CoverageSlot { codepoint, answer };
}
set_slot_generation(slot, generation);
answer
}
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
#[doc(hidden)]
pub fn face_for_char_uncached(ch: char) -> Option<FaceBytes> {
resolve_face(ch)
}
fn resolve_face(ch: char) -> Option<FaceBytes> {
#[cfg(feature = "runtime-fonts")]
{
let (runtime, len) = crate::render::text::runtime_fonts::active_runtime_faces();
for face in &runtime[..len] {
if face_covers(face, ch) {
return Some(*face);
}
}
}
active_faces().iter().copied().find(|face| face_covers(face, ch))
}
fn slot_generation(slot: usize) -> u32 {
crate::compat::lock(&SLOT_GENERATIONS)[slot]
}
fn set_slot_generation(slot: usize, generation: u32) {
crate::compat::lock(&SLOT_GENERATIONS)[slot] = generation;
}
#[cfg(feature = "runtime-fonts")]
pub(crate) fn invalidate_face_cache() {
FACE_GENERATION.fetch_add(1, core::sync::atomic::Ordering::AcqRel);
clear_parsed_faces();
clear_shaper_faces();
}
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
feature = "fonts-cjk"
))]
pub fn active_faces() -> &'static [FaceBytes] {
#[cfg(all(feature = "fonts-cjk", feature = "fonts-complex", feature = "fonts-vector-latin"))]
static SLOTS: [FaceBytes; 3] = [CJK_FACE, ARABIC_FACE, LATIN_FACE];
#[cfg(all(
feature = "fonts-cjk",
feature = "fonts-complex",
not(feature = "fonts-vector-latin")
))]
static SLOTS: [FaceBytes; 2] = [CJK_FACE, ARABIC_FACE];
#[cfg(all(
feature = "fonts-cjk",
not(feature = "fonts-complex"),
feature = "fonts-vector-latin"
))]
static SLOTS: [FaceBytes; 2] = [CJK_FACE, LATIN_FACE];
#[cfg(all(
feature = "fonts-cjk",
not(feature = "fonts-complex"),
not(feature = "fonts-vector-latin")
))]
static SLOTS: [FaceBytes; 1] = [CJK_FACE];
#[cfg(all(
not(feature = "fonts-cjk"),
feature = "fonts-complex",
feature = "fonts-vector-latin"
))]
static SLOTS: [FaceBytes; 2] = [ARABIC_FACE, LATIN_FACE];
#[cfg(all(
not(feature = "fonts-cjk"),
feature = "fonts-complex",
not(feature = "fonts-vector-latin")
))]
static SLOTS: [FaceBytes; 1] = [ARABIC_FACE];
#[cfg(all(
not(feature = "fonts-cjk"),
not(feature = "fonts-complex"),
feature = "fonts-vector-latin"
))]
static SLOTS: [FaceBytes; 1] = [LATIN_FACE];
#[cfg(not(any(
feature = "fonts-cjk",
feature = "fonts-complex",
feature = "fonts-vector-latin"
)))]
static SLOTS: [FaceBytes; 0] = [];
&SLOTS
}
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
pub fn outline_face_for(ch: char) -> Option<FaceBytes> {
face_for_char(ch)
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
#[test]
fn the_cached_and_uncached_lookups_agree() {
#[cfg(all(feature = "runtime-fonts", feature = "text-shaping"))]
let _guard = crate::render::text::runtime_fonts::test_lock();
let probes = ['A', 'z', '\u{0}', ' ', '\u{E000}', '\u{4E2D}', '\u{3042}', '\u{1F600}'];
for ch in probes {
let cached = face_for_char(ch);
let uncached = face_for_char_uncached(ch);
assert_eq!(
cached.map(|face| face.name),
uncached.map(|face| face.name),
"cache and un-cached lookup disagree for U+{:04X} ({ch:?})",
ch as u32
);
}
}
#[cfg(any(
feature = "text-shaping",
feature = "fonts-vector-latin",
feature = "fonts-complex",
cjk_outline_face
))]
#[test]
fn a_repeated_lookup_is_stable() {
#[cfg(all(feature = "runtime-fonts", feature = "text-shaping"))]
let _guard = crate::render::text::runtime_fonts::test_lock();
let first = face_for_char('A');
for _ in 0..100 {
assert_eq!(
face_for_char('A').map(|face| face.name),
first.map(|face| face.name),
"the cached answer must not change across repeated lookups"
);
}
}
}