use crate::shaping::{Direction, ShapedRun};
use std::ops::Range;
mod interaction;
mod shaped;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FontId(pub u64);
impl FontId {
#[must_use]
pub const fn new(id: u64) -> Self {
Self(id)
}
#[must_use]
pub fn from_font_data(data: &[u8]) -> Self {
let mut hash = 0xcbf29ce484222325u64;
for &b in data {
hash = (hash ^ u64::from(b)).wrapping_mul(0x100000001b3);
}
Self(hash)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum FontOrigin {
BundledFace,
SystemFallbackFace,
}
impl FontOrigin {
#[must_use]
pub const fn is_deterministic(self) -> bool {
matches!(self, Self::BundledFace)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CaretAffinity {
Leading,
Trailing,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CaretPosition {
pub byte_offset: usize,
pub utf16_offset: usize,
pub visual_x: f32,
pub affinity: CaretAffinity,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct HitTestResult {
pub cluster_index: usize,
pub caret: CaretPosition,
pub is_exact: bool,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SelectionRect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TextRunContext {
pub font_id: FontId,
pub font_size: f32,
pub script: [u8; 4],
pub language: [u8; 4],
pub direction: Direction,
pub font_origin: FontOrigin,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RunGlyph {
pub glyph_id: u16,
pub font_id: FontId,
pub cluster_index: usize,
pub x_advance: f32,
pub y_advance: f32,
pub x_offset: f32,
pub y_offset: f32,
}
#[derive(Clone, Debug, PartialEq)]
pub struct TextCluster {
pub cluster_index: usize,
pub byte_range: Range<usize>,
pub utf16_range: Range<usize>,
pub glyph_range: Range<usize>,
pub x_start: f32,
pub x_end: f32,
pub font_id: FontId,
}
impl TextCluster {
#[must_use]
pub fn advance(&self) -> f32 {
(self.x_end - self.x_start).abs()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct OwnedTextRun {
pub context: TextRunContext,
pub logical_text: String,
pub clusters: Vec<TextCluster>,
pub glyphs: Vec<RunGlyph>,
pub total_advance: f32,
}
impl OwnedTextRun {
pub fn from_shaped_run(
context: TextRunContext,
shaped: &ShapedRun,
scale: f32,
) -> Result<Self, String> {
shaped::build(context, shaped, scale)
}
#[must_use]
pub fn hit_test(&self, visual_x: f32) -> HitTestResult {
interaction::hit_test(self, visual_x)
}
#[must_use]
pub fn caret_at_byte(
&self,
byte_offset: usize,
affinity: CaretAffinity,
) -> Option<CaretPosition> {
interaction::caret_at_byte(self, byte_offset, affinity)
}
#[must_use]
pub fn caret_at_utf16(
&self,
utf16_offset: usize,
affinity: CaretAffinity,
) -> Option<CaretPosition> {
let byte_offset = utf16_to_byte(&self.logical_text, utf16_offset)?;
self.caret_at_byte(byte_offset, affinity)
}
#[must_use]
pub fn selection_rects(&self, range: Range<usize>, y: f32, height: f32) -> Vec<SelectionRect> {
interaction::selection_rects(self, range, y, height)
}
}
#[must_use]
pub fn byte_to_utf16(text: &str, byte_offset: usize) -> Option<usize> {
if byte_offset > text.len() || !text.is_char_boundary(byte_offset) {
return None;
}
let mut u16_count = 0;
for ch in text[..byte_offset].chars() {
u16_count += ch.len_utf16();
}
Some(u16_count)
}
#[must_use]
pub fn utf16_to_byte(text: &str, utf16_offset: usize) -> Option<usize> {
let mut cur_u16 = 0;
let mut cur_byte = 0;
for ch in text.chars() {
if cur_u16 == utf16_offset {
return Some(cur_byte);
}
if cur_u16 > utf16_offset {
return None;
}
cur_u16 += ch.len_utf16();
cur_byte += ch.len_utf8();
}
if cur_u16 == utf16_offset {
Some(cur_byte)
} else {
None
}
}