use std::ops::{Range, RangeBounds};
use crate::kurbo::{Point, Rect, Size};
use crate::{Color, Error, FontFamily, FontStyle, FontWeight};
pub trait Text: Clone {
type TextLayoutBuilder: TextLayoutBuilder<Out = Self::TextLayout>;
type TextLayout: TextLayout;
fn font_family(&mut self, family_name: &str) -> Option<FontFamily>;
fn load_font(&mut self, data: &[u8]) -> Result<FontFamily, Error>;
fn new_text_layout(&mut self, text: impl TextStorage) -> Self::TextLayoutBuilder;
}
pub trait TextStorage: 'static {
fn as_str(&self) -> &str;
}
impl std::ops::Deref for dyn TextStorage {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
pub enum TextAttribute {
FontFamily(FontFamily),
FontSize(f64),
Weight(FontWeight),
TextColor(crate::Color),
Style(FontStyle),
Underline(bool),
Strikethrough(bool),
}
pub trait TextLayoutBuilder: Sized {
type Out: TextLayout;
fn max_width(self, width: f64) -> Self;
fn alignment(self, alignment: TextAlignment) -> Self;
fn font(self, font: FontFamily, font_size: f64) -> Self {
self.default_attribute(TextAttribute::FontFamily(font))
.default_attribute(TextAttribute::FontSize(font_size))
}
fn text_color(self, color: Color) -> Self {
self.default_attribute(TextAttribute::TextColor(color))
}
fn default_attribute(self, attribute: impl Into<TextAttribute>) -> Self;
fn range_attribute(
self,
range: impl RangeBounds<usize>,
attribute: impl Into<TextAttribute>,
) -> Self;
fn build(self) -> Result<Self::Out, Error>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextAlignment {
Start,
End,
Center,
Justified,
}
pub trait TextLayout: Clone {
fn size(&self) -> Size;
fn trailing_whitespace_width(&self) -> f64;
fn image_bounds(&self) -> Rect;
fn text(&self) -> &str;
fn line_text(&self, line_number: usize) -> Option<&str>;
fn line_metric(&self, line_number: usize) -> Option<LineMetric>;
fn line_count(&self) -> usize;
fn hit_test_point(&self, point: Point) -> HitTestPoint;
fn hit_test_text_position(&self, idx: usize) -> HitTestPosition;
fn rects_for_range(&self, range: impl RangeBounds<usize>) -> Vec<Rect> {
let text_len = self.text().len();
let mut range = crate::util::resolve_range(range, text_len);
range.start = range.start.min(text_len);
range.end = range.end.min(text_len);
if range.start >= range.end {
return Vec::new();
}
let first_line = self.hit_test_text_position(range.start).line;
let last_line = self.hit_test_text_position(range.end).line;
let mut result = Vec::new();
for line in first_line..=last_line {
let metrics = self.line_metric(line).unwrap();
let y0 = metrics.y_offset;
let y1 = y0 + metrics.height;
let line_range_start = if line == first_line {
range.start
} else {
metrics.start_offset
};
let line_range_end = if line == last_line {
range.end
} else {
metrics.end_offset - metrics.trailing_whitespace
};
let start_point = self.hit_test_text_position(line_range_start);
let end_point = self.hit_test_text_position(line_range_end);
result.push(Rect::new(start_point.point.x, y0, end_point.point.x, y1));
}
result
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct LineMetric {
pub start_offset: usize,
pub end_offset: usize,
pub trailing_whitespace: usize,
pub baseline: f64,
pub height: f64,
pub y_offset: f64,
}
impl LineMetric {
#[inline]
pub fn range(&self) -> Range<usize> {
self.start_offset..self.end_offset
}
}
#[derive(Debug, Default, PartialEq)]
#[non_exhaustive]
pub struct HitTestPoint {
pub idx: usize,
pub is_inside: bool,
}
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct HitTestPosition {
pub point: Point,
pub line: usize,
}
impl HitTestPoint {
#[doc(hidden)]
pub fn new(idx: usize, is_inside: bool) -> HitTestPoint {
HitTestPoint { idx, is_inside }
}
}
impl HitTestPosition {
#[doc(hidden)]
pub fn new(point: Point, line: usize) -> HitTestPosition {
HitTestPosition { point, line }
}
}
impl From<FontFamily> for TextAttribute {
fn from(t: FontFamily) -> TextAttribute {
TextAttribute::FontFamily(t)
}
}
impl From<FontWeight> for TextAttribute {
fn from(src: FontWeight) -> TextAttribute {
TextAttribute::Weight(src)
}
}
impl From<FontStyle> for TextAttribute {
fn from(src: FontStyle) -> TextAttribute {
TextAttribute::Style(src)
}
}
impl Default for TextAlignment {
fn default() -> Self {
TextAlignment::Start
}
}
impl TextStorage for std::sync::Arc<str> {
fn as_str(&self) -> &str {
self
}
}
impl TextStorage for std::rc::Rc<str> {
fn as_str(&self) -> &str {
self
}
}
impl TextStorage for String {
fn as_str(&self) -> &str {
self.as_str()
}
}
impl TextStorage for std::sync::Arc<String> {
fn as_str(&self) -> &str {
self
}
}
impl TextStorage for std::rc::Rc<String> {
fn as_str(&self) -> &str {
self
}
}
impl TextStorage for &'static str {
fn as_str(&self) -> &str {
self
}
}