1pub mod editor;
3pub mod highlighter;
4pub mod paragraph;
5
6pub use editor::Editor;
7pub use highlighter::Highlighter;
8pub use paragraph::Paragraph;
9
10use crate::alignment;
11use crate::{
12    Background, Border, Color, Padding, Pixels, Point, Rectangle, Size,
13};
14
15use std::borrow::Cow;
16use std::hash::{Hash, Hasher};
17
18#[derive(Debug, Clone, Copy)]
20pub struct Text<Content = String, Font = crate::Font> {
21    pub content: Content,
23
24    pub bounds: Size,
26
27    pub size: Pixels,
29
30    pub line_height: LineHeight,
32
33    pub font: Font,
35
36    pub align_x: Alignment,
38
39    pub align_y: alignment::Vertical,
41
42    pub shaping: Shaping,
44
45    pub wrapping: Wrapping,
47}
48
49impl<Content, Font> Text<Content, Font>
50where
51    Font: Copy,
52{
53    pub fn with_content<T>(&self, content: T) -> Text<T, Font> {
56        Text {
57            content,
58            bounds: self.bounds,
59            size: self.size,
60            line_height: self.line_height,
61            font: self.font,
62            align_x: self.align_x,
63            align_y: self.align_y,
64            shaping: self.shaping,
65            wrapping: self.wrapping,
66        }
67    }
68}
69
70impl<Content, Font> Text<Content, Font>
71where
72    Content: AsRef<str>,
73    Font: Copy,
74{
75    pub fn as_ref(&self) -> Text<&str, Font> {
77        self.with_content(self.content.as_ref())
78    }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
83pub enum Alignment {
84    #[default]
89    Default,
90    Left,
92    Center,
94    Right,
96    Justified,
98}
99
100impl From<alignment::Horizontal> for Alignment {
101    fn from(alignment: alignment::Horizontal) -> Self {
102        match alignment {
103            alignment::Horizontal::Left => Self::Left,
104            alignment::Horizontal::Center => Self::Center,
105            alignment::Horizontal::Right => Self::Right,
106        }
107    }
108}
109
110impl From<crate::Alignment> for Alignment {
111    fn from(alignment: crate::Alignment) -> Self {
112        match alignment {
113            crate::Alignment::Start => Self::Left,
114            crate::Alignment::Center => Self::Center,
115            crate::Alignment::End => Self::Right,
116        }
117    }
118}
119
120impl From<Alignment> for alignment::Horizontal {
121    fn from(alignment: Alignment) -> Self {
122        match alignment {
123            Alignment::Default | Alignment::Left | Alignment::Justified => {
124                alignment::Horizontal::Left
125            }
126            Alignment::Center => alignment::Horizontal::Center,
127            Alignment::Right => alignment::Horizontal::Right,
128        }
129    }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
134pub enum Shaping {
135    Auto,
144    Basic,
155    Advanced,
165}
166
167impl Default for Shaping {
168    fn default() -> Self {
169        if cfg!(feature = "advanced-shaping") {
170            Self::Advanced
171        } else if cfg!(feature = "basic-shaping") {
172            Self::Basic
173        } else {
174            Self::Auto
175        }
176    }
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
181pub enum Wrapping {
182    None,
184    #[default]
188    Word,
189    Glyph,
191    WordOrGlyph,
193}
194
195#[derive(Debug, Clone, Copy, PartialEq)]
197pub enum LineHeight {
198    Relative(f32),
200
201    Absolute(Pixels),
203}
204
205impl LineHeight {
206    pub fn to_absolute(self, text_size: Pixels) -> Pixels {
208        match self {
209            Self::Relative(factor) => Pixels(factor * text_size.0),
210            Self::Absolute(pixels) => pixels,
211        }
212    }
213}
214
215impl Default for LineHeight {
216    fn default() -> Self {
217        Self::Relative(1.3)
218    }
219}
220
221impl From<f32> for LineHeight {
222    fn from(factor: f32) -> Self {
223        Self::Relative(factor)
224    }
225}
226
227impl From<Pixels> for LineHeight {
228    fn from(pixels: Pixels) -> Self {
229        Self::Absolute(pixels)
230    }
231}
232
233impl Hash for LineHeight {
234    fn hash<H: Hasher>(&self, state: &mut H) {
235        match self {
236            Self::Relative(factor) => {
237                state.write_u8(0);
238                factor.to_bits().hash(state);
239            }
240            Self::Absolute(pixels) => {
241                state.write_u8(1);
242                f32::from(*pixels).to_bits().hash(state);
243            }
244        }
245    }
246}
247
248#[derive(Debug, Clone, Copy, PartialEq)]
250pub enum Hit {
251    CharOffset(usize),
253}
254
255impl Hit {
256    pub fn cursor(self) -> usize {
258        match self {
259            Self::CharOffset(i) => i,
260        }
261    }
262}
263
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub enum Difference {
272    None,
276
277    Bounds,
282
283    Shape,
289}
290
291pub trait Renderer: crate::Renderer {
293    type Font: Copy + PartialEq;
295
296    type Paragraph: Paragraph<Font = Self::Font> + 'static;
298
299    type Editor: Editor<Font = Self::Font> + 'static;
301
302    const ICON_FONT: Self::Font;
304
305    const CHECKMARK_ICON: char;
309
310    const ARROW_DOWN_ICON: char;
314
315    fn default_font(&self) -> Self::Font;
317
318    fn default_size(&self) -> Pixels;
320
321    fn fill_paragraph(
324        &mut self,
325        text: &Self::Paragraph,
326        position: Point,
327        color: Color,
328        clip_bounds: Rectangle,
329    );
330
331    fn fill_editor(
334        &mut self,
335        editor: &Self::Editor,
336        position: Point,
337        color: Color,
338        clip_bounds: Rectangle,
339    );
340
341    fn fill_text(
344        &mut self,
345        text: Text<String, Self::Font>,
346        position: Point,
347        color: Color,
348        clip_bounds: Rectangle,
349    );
350}
351
352#[derive(Debug, Clone)]
354pub struct Span<'a, Link = (), Font = crate::Font> {
355    pub text: Fragment<'a>,
357    pub size: Option<Pixels>,
359    pub line_height: Option<LineHeight>,
361    pub font: Option<Font>,
363    pub color: Option<Color>,
365    pub link: Option<Link>,
367    pub highlight: Option<Highlight>,
369    pub padding: Padding,
373    pub underline: bool,
375    pub strikethrough: bool,
377}
378
379#[derive(Debug, Clone, Copy, PartialEq)]
381pub struct Highlight {
382    pub background: Background,
384    pub border: Border,
386}
387
388impl<'a, Link, Font> Span<'a, Link, Font> {
389    pub fn new(fragment: impl IntoFragment<'a>) -> Self {
391        Self {
392            text: fragment.into_fragment(),
393            ..Self::default()
394        }
395    }
396
397    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
399        self.size = Some(size.into());
400        self
401    }
402
403    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
405        self.line_height = Some(line_height.into());
406        self
407    }
408
409    pub fn font(mut self, font: impl Into<Font>) -> Self {
411        self.font = Some(font.into());
412        self
413    }
414
415    pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
417        self.font = font.map(Into::into);
418        self
419    }
420
421    pub fn color(mut self, color: impl Into<Color>) -> Self {
423        self.color = Some(color.into());
424        self
425    }
426
427    pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
429        self.color = color.map(Into::into);
430        self
431    }
432
433    pub fn link(mut self, link: impl Into<Link>) -> Self {
435        self.link = Some(link.into());
436        self
437    }
438
439    pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
441        self.link = link.map(Into::into);
442        self
443    }
444
445    pub fn background(self, background: impl Into<Background>) -> Self {
447        self.background_maybe(Some(background))
448    }
449
450    pub fn background_maybe(
452        mut self,
453        background: Option<impl Into<Background>>,
454    ) -> Self {
455        let Some(background) = background else {
456            return self;
457        };
458
459        match &mut self.highlight {
460            Some(highlight) => {
461                highlight.background = background.into();
462            }
463            None => {
464                self.highlight = Some(Highlight {
465                    background: background.into(),
466                    border: Border::default(),
467                });
468            }
469        }
470
471        self
472    }
473
474    pub fn border(self, border: impl Into<Border>) -> Self {
476        self.border_maybe(Some(border))
477    }
478
479    pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
481        let Some(border) = border else {
482            return self;
483        };
484
485        match &mut self.highlight {
486            Some(highlight) => {
487                highlight.border = border.into();
488            }
489            None => {
490                self.highlight = Some(Highlight {
491                    border: border.into(),
492                    background: Background::Color(Color::TRANSPARENT),
493                });
494            }
495        }
496
497        self
498    }
499
500    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
508        self.padding = padding.into();
509        self
510    }
511
512    pub fn underline(mut self, underline: bool) -> Self {
514        self.underline = underline;
515        self
516    }
517
518    pub fn strikethrough(mut self, strikethrough: bool) -> Self {
520        self.strikethrough = strikethrough;
521        self
522    }
523
524    pub fn to_static(self) -> Span<'static, Link, Font> {
526        Span {
527            text: Cow::Owned(self.text.into_owned()),
528            size: self.size,
529            line_height: self.line_height,
530            font: self.font,
531            color: self.color,
532            link: self.link,
533            highlight: self.highlight,
534            padding: self.padding,
535            underline: self.underline,
536            strikethrough: self.strikethrough,
537        }
538    }
539}
540
541impl<Link, Font> Default for Span<'_, Link, Font> {
542    fn default() -> Self {
543        Self {
544            text: Cow::default(),
545            size: None,
546            line_height: None,
547            font: None,
548            color: None,
549            link: None,
550            highlight: None,
551            padding: Padding::default(),
552            underline: false,
553            strikethrough: false,
554        }
555    }
556}
557
558impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
559    fn from(value: &'a str) -> Self {
560        Span::new(value)
561    }
562}
563
564impl<Link, Font: PartialEq> PartialEq for Span<'_, Link, Font> {
565    fn eq(&self, other: &Self) -> bool {
566        self.text == other.text
567            && self.size == other.size
568            && self.line_height == other.line_height
569            && self.font == other.font
570            && self.color == other.color
571    }
572}
573
574pub type Fragment<'a> = Cow<'a, str>;
579
580pub trait IntoFragment<'a> {
582    fn into_fragment(self) -> Fragment<'a>;
584}
585
586impl<'a> IntoFragment<'a> for Fragment<'a> {
587    fn into_fragment(self) -> Fragment<'a> {
588        self
589    }
590}
591
592impl<'a> IntoFragment<'a> for &'a Fragment<'_> {
593    fn into_fragment(self) -> Fragment<'a> {
594        Fragment::Borrowed(self)
595    }
596}
597
598impl<'a> IntoFragment<'a> for &'a str {
599    fn into_fragment(self) -> Fragment<'a> {
600        Fragment::Borrowed(self)
601    }
602}
603
604impl<'a> IntoFragment<'a> for &'a String {
605    fn into_fragment(self) -> Fragment<'a> {
606        Fragment::Borrowed(self.as_str())
607    }
608}
609
610impl<'a> IntoFragment<'a> for String {
611    fn into_fragment(self) -> Fragment<'a> {
612        Fragment::Owned(self)
613    }
614}
615
616macro_rules! into_fragment {
617    ($type:ty) => {
618        impl<'a> IntoFragment<'a> for $type {
619            fn into_fragment(self) -> Fragment<'a> {
620                Fragment::Owned(self.to_string())
621            }
622        }
623
624        impl<'a> IntoFragment<'a> for &$type {
625            fn into_fragment(self) -> Fragment<'a> {
626                Fragment::Owned(self.to_string())
627            }
628        }
629    };
630}
631
632into_fragment!(char);
633into_fragment!(bool);
634
635into_fragment!(u8);
636into_fragment!(u16);
637into_fragment!(u32);
638into_fragment!(u64);
639into_fragment!(u128);
640into_fragment!(usize);
641
642into_fragment!(i8);
643into_fragment!(i16);
644into_fragment!(i32);
645into_fragment!(i64);
646into_fragment!(i128);
647into_fragment!(isize);
648
649into_fragment!(f32);
650into_fragment!(f64);