iced_core/
text.rs

1//! Draw and interact with text.
2pub 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::{Background, Border, Color, Padding, Pixels, Point, Rectangle, Size};
12
13use std::borrow::Cow;
14use std::hash::{Hash, Hasher};
15
16/// A paragraph.
17#[derive(Debug, Clone, Copy)]
18pub struct Text<Content = String, Font = crate::Font> {
19    /// The content of the paragraph.
20    pub content: Content,
21
22    /// The bounds of the paragraph.
23    pub bounds: Size,
24
25    /// The size of the [`Text`] in logical pixels.
26    pub size: Pixels,
27
28    /// The line height of the [`Text`].
29    pub line_height: LineHeight,
30
31    /// The font of the [`Text`].
32    pub font: Font,
33
34    /// The horizontal alignment of the [`Text`].
35    pub align_x: Alignment,
36
37    /// The vertical alignment of the [`Text`].
38    pub align_y: alignment::Vertical,
39
40    /// The [`Shaping`] strategy of the [`Text`].
41    pub shaping: Shaping,
42
43    /// The [`Wrapping`] strategy of the [`Text`].
44    pub wrapping: Wrapping,
45}
46
47impl<Content, Font> Text<Content, Font>
48where
49    Font: Copy,
50{
51    /// Returns a new [`Text`] replacing only the content with the
52    /// given value.
53    pub fn with_content<T>(&self, content: T) -> Text<T, Font> {
54        Text {
55            content,
56            bounds: self.bounds,
57            size: self.size,
58            line_height: self.line_height,
59            font: self.font,
60            align_x: self.align_x,
61            align_y: self.align_y,
62            shaping: self.shaping,
63            wrapping: self.wrapping,
64        }
65    }
66}
67
68impl<Content, Font> Text<Content, Font>
69where
70    Content: AsRef<str>,
71    Font: Copy,
72{
73    /// Returns a borrowed version of [`Text`].
74    pub fn as_ref(&self) -> Text<&str, Font> {
75        self.with_content(self.content.as_ref())
76    }
77}
78
79/// The alignment of some text.
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
81pub enum Alignment {
82    /// No specific alignment.
83    ///
84    /// Left-to-right text will be aligned to the left, while
85    /// right-to-left text will be aligned to the right.
86    #[default]
87    Default,
88    /// Align text to the left.
89    Left,
90    /// Center text.
91    Center,
92    /// Align text to the right.
93    Right,
94    /// Justify text.
95    Justified,
96}
97
98impl From<alignment::Horizontal> for Alignment {
99    fn from(alignment: alignment::Horizontal) -> Self {
100        match alignment {
101            alignment::Horizontal::Left => Self::Left,
102            alignment::Horizontal::Center => Self::Center,
103            alignment::Horizontal::Right => Self::Right,
104        }
105    }
106}
107
108impl From<crate::Alignment> for Alignment {
109    fn from(alignment: crate::Alignment) -> Self {
110        match alignment {
111            crate::Alignment::Start => Self::Left,
112            crate::Alignment::Center => Self::Center,
113            crate::Alignment::End => Self::Right,
114        }
115    }
116}
117
118impl From<Alignment> for alignment::Horizontal {
119    fn from(alignment: Alignment) -> Self {
120        match alignment {
121            Alignment::Default | Alignment::Left | Alignment::Justified => {
122                alignment::Horizontal::Left
123            }
124            Alignment::Center => alignment::Horizontal::Center,
125            Alignment::Right => alignment::Horizontal::Right,
126        }
127    }
128}
129
130/// The shaping strategy of some text.
131#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
132pub enum Shaping {
133    /// Auto-detect the best shaping strategy from the text.
134    ///
135    /// This strategy will use [`Basic`](Self::Basic) shaping if the
136    /// text consists of only ASCII characters; otherwise, it will
137    /// use [`Advanced`](Self::Advanced) shaping.
138    ///
139    /// This is the default, if neither the `basic-shaping` nor `advanced-shaping`
140    /// features are enabled.
141    Auto,
142    /// No shaping and no font fallback.
143    ///
144    /// This shaping strategy is very cheap, but it will not display complex
145    /// scripts properly nor try to find missing glyphs in your system fonts.
146    ///
147    /// You should use this strategy when you have complete control of the text
148    /// and the font you are displaying in your application.
149    ///
150    /// This will be the default if the `basic-shaping` feature is enabled and
151    /// the `advanced-shaping` feature is disabled.
152    Basic,
153    /// Advanced text shaping and font fallback.
154    ///
155    /// You will need to enable this flag if the text contains a complex
156    /// script, the font used needs it, and/or multiple fonts in your system
157    /// may be needed to display all of the glyphs.
158    ///
159    /// Advanced shaping is expensive! You should only enable it when necessary.
160    ///
161    /// This will be the default if the `advanced-shaping` feature is enabled.
162    Advanced,
163}
164
165impl Default for Shaping {
166    fn default() -> Self {
167        if cfg!(feature = "advanced-shaping") {
168            Self::Advanced
169        } else if cfg!(feature = "basic-shaping") {
170            Self::Basic
171        } else {
172            Self::Auto
173        }
174    }
175}
176
177/// The wrapping strategy of some text.
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
179pub enum Wrapping {
180    /// No wrapping.
181    None,
182    /// Wraps at the word level.
183    ///
184    /// This is the default.
185    #[default]
186    Word,
187    /// Wraps at the glyph level.
188    Glyph,
189    /// Wraps at the word level, or fallback to glyph level if a word can't fit on a line by itself.
190    WordOrGlyph,
191}
192
193/// The height of a line of text in a paragraph.
194#[derive(Debug, Clone, Copy, PartialEq)]
195pub enum LineHeight {
196    /// A factor of the size of the text.
197    Relative(f32),
198
199    /// An absolute height in logical pixels.
200    Absolute(Pixels),
201}
202
203impl LineHeight {
204    /// Returns the [`LineHeight`] in absolute logical pixels.
205    pub fn to_absolute(self, text_size: Pixels) -> Pixels {
206        match self {
207            Self::Relative(factor) => Pixels(factor * text_size.0),
208            Self::Absolute(pixels) => pixels,
209        }
210    }
211}
212
213impl Default for LineHeight {
214    fn default() -> Self {
215        Self::Relative(1.3)
216    }
217}
218
219impl From<f32> for LineHeight {
220    fn from(factor: f32) -> Self {
221        Self::Relative(factor)
222    }
223}
224
225impl From<Pixels> for LineHeight {
226    fn from(pixels: Pixels) -> Self {
227        Self::Absolute(pixels)
228    }
229}
230
231impl Hash for LineHeight {
232    fn hash<H: Hasher>(&self, state: &mut H) {
233        match self {
234            Self::Relative(factor) => {
235                state.write_u8(0);
236                factor.to_bits().hash(state);
237            }
238            Self::Absolute(pixels) => {
239                state.write_u8(1);
240                f32::from(*pixels).to_bits().hash(state);
241            }
242        }
243    }
244}
245
246/// The result of hit testing on text.
247#[derive(Debug, Clone, Copy, PartialEq)]
248pub enum Hit {
249    /// The point was within the bounds of the returned character index.
250    CharOffset(usize),
251}
252
253impl Hit {
254    /// Computes the cursor position of the [`Hit`] .
255    pub fn cursor(self) -> usize {
256        match self {
257            Self::CharOffset(i) => i,
258        }
259    }
260}
261
262/// The difference detected in some text.
263///
264/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some
265/// [`Text`].
266///
267/// [`compare`]: Paragraph::compare
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum Difference {
270    /// No difference.
271    ///
272    /// The text can be reused as it is!
273    None,
274
275    /// A bounds difference.
276    ///
277    /// This normally means a relayout is necessary, but the shape of the text can
278    /// be reused.
279    Bounds,
280
281    /// A shape difference.
282    ///
283    /// The contents, alignment, sizes, fonts, or any other essential attributes
284    /// of the shape of the text have changed. A complete reshape and relayout of
285    /// the text is necessary.
286    Shape,
287}
288
289/// A renderer capable of measuring and drawing [`Text`].
290pub trait Renderer: crate::Renderer {
291    /// The font type used.
292    type Font: Copy + PartialEq;
293
294    /// The [`Paragraph`] of this [`Renderer`].
295    type Paragraph: Paragraph<Font = Self::Font> + 'static;
296
297    /// The [`Editor`] of this [`Renderer`].
298    type Editor: Editor<Font = Self::Font> + 'static;
299
300    /// The icon font of the backend.
301    const ICON_FONT: Self::Font;
302
303    /// The `char` representing a ✔ icon in the [`ICON_FONT`].
304    ///
305    /// [`ICON_FONT`]: Self::ICON_FONT
306    const CHECKMARK_ICON: char;
307
308    /// The `char` representing a ▼ icon in the built-in [`ICON_FONT`].
309    ///
310    /// [`ICON_FONT`]: Self::ICON_FONT
311    const ARROW_DOWN_ICON: char;
312
313    /// The `char` representing a ^ icon in the built-in [`ICON_FONT`].
314    ///
315    /// [`ICON_FONT`]: Self::ICON_FONT
316    const SCROLL_UP_ICON: char;
317
318    /// The `char` representing a v icon in the built-in [`ICON_FONT`].
319    ///
320    /// [`ICON_FONT`]: Self::ICON_FONT
321    const SCROLL_DOWN_ICON: char;
322
323    /// The `char` representing a < icon in the built-in [`ICON_FONT`].
324    ///
325    /// [`ICON_FONT`]: Self::ICON_FONT
326    const SCROLL_LEFT_ICON: char;
327
328    /// The `char` representing a > icon in the built-in [`ICON_FONT`].
329    ///
330    /// [`ICON_FONT`]: Self::ICON_FONT
331    const SCROLL_RIGHT_ICON: char;
332
333    /// The 'char' representing the iced logo in the built-in ['ICON_FONT'].
334    ///
335    /// ['ICON_FONT']: Self::ICON_FONT
336    const ICED_LOGO: char;
337
338    /// Returns the default [`Self::Font`].
339    fn default_font(&self) -> Self::Font;
340
341    /// Returns the default size of [`Text`].
342    fn default_size(&self) -> Pixels;
343
344    /// Draws the given [`Paragraph`] at the given position and with the given
345    /// [`Color`].
346    fn fill_paragraph(
347        &mut self,
348        text: &Self::Paragraph,
349        position: Point,
350        color: Color,
351        clip_bounds: Rectangle,
352    );
353
354    /// Draws the given [`Editor`] at the given position and with the given
355    /// [`Color`].
356    fn fill_editor(
357        &mut self,
358        editor: &Self::Editor,
359        position: Point,
360        color: Color,
361        clip_bounds: Rectangle,
362    );
363
364    /// Draws the given [`Text`] at the given position and with the given
365    /// [`Color`].
366    fn fill_text(
367        &mut self,
368        text: Text<String, Self::Font>,
369        position: Point,
370        color: Color,
371        clip_bounds: Rectangle,
372    );
373}
374
375/// A span of text.
376#[derive(Debug, Clone)]
377pub struct Span<'a, Link = (), Font = crate::Font> {
378    /// The [`Fragment`] of text.
379    pub text: Fragment<'a>,
380    /// The size of the [`Span`] in [`Pixels`].
381    pub size: Option<Pixels>,
382    /// The [`LineHeight`] of the [`Span`].
383    pub line_height: Option<LineHeight>,
384    /// The font of the [`Span`].
385    pub font: Option<Font>,
386    /// The [`Color`] of the [`Span`].
387    pub color: Option<Color>,
388    /// The link of the [`Span`].
389    pub link: Option<Link>,
390    /// The [`Highlight`] of the [`Span`].
391    pub highlight: Option<Highlight>,
392    /// The [`Padding`] of the [`Span`].
393    ///
394    /// Currently, it only affects the bounds of the [`Highlight`].
395    pub padding: Padding,
396    /// Whether the [`Span`] should be underlined or not.
397    pub underline: bool,
398    /// Whether the [`Span`] should be struck through or not.
399    pub strikethrough: bool,
400}
401
402/// A text highlight.
403#[derive(Debug, Clone, Copy, PartialEq)]
404pub struct Highlight {
405    /// The [`Background`] of the highlight.
406    pub background: Background,
407    /// The [`Border`] of the highlight.
408    pub border: Border,
409}
410
411impl<'a, Link, Font> Span<'a, Link, Font> {
412    /// Creates a new [`Span`] of text with the given text fragment.
413    pub fn new(fragment: impl IntoFragment<'a>) -> Self {
414        Self {
415            text: fragment.into_fragment(),
416            ..Self::default()
417        }
418    }
419
420    /// Sets the size of the [`Span`].
421    pub fn size(mut self, size: impl Into<Pixels>) -> Self {
422        self.size = Some(size.into());
423        self
424    }
425
426    /// Sets the [`LineHeight`] of the [`Span`].
427    pub fn line_height(mut self, line_height: impl Into<LineHeight>) -> Self {
428        self.line_height = Some(line_height.into());
429        self
430    }
431
432    /// Sets the font of the [`Span`].
433    pub fn font(mut self, font: impl Into<Font>) -> Self {
434        self.font = Some(font.into());
435        self
436    }
437
438    /// Sets the font of the [`Span`], if any.
439    pub fn font_maybe(mut self, font: Option<impl Into<Font>>) -> Self {
440        self.font = font.map(Into::into);
441        self
442    }
443
444    /// Sets the [`Color`] of the [`Span`].
445    pub fn color(mut self, color: impl Into<Color>) -> Self {
446        self.color = Some(color.into());
447        self
448    }
449
450    /// Sets the [`Color`] of the [`Span`], if any.
451    pub fn color_maybe(mut self, color: Option<impl Into<Color>>) -> Self {
452        self.color = color.map(Into::into);
453        self
454    }
455
456    /// Sets the link of the [`Span`].
457    pub fn link(mut self, link: impl Into<Link>) -> Self {
458        self.link = Some(link.into());
459        self
460    }
461
462    /// Sets the link of the [`Span`], if any.
463    pub fn link_maybe(mut self, link: Option<impl Into<Link>>) -> Self {
464        self.link = link.map(Into::into);
465        self
466    }
467
468    /// Sets the [`Background`] of the [`Span`].
469    pub fn background(self, background: impl Into<Background>) -> Self {
470        self.background_maybe(Some(background))
471    }
472
473    /// Sets the [`Background`] of the [`Span`], if any.
474    pub fn background_maybe(mut self, background: Option<impl Into<Background>>) -> Self {
475        let Some(background) = background else {
476            return self;
477        };
478
479        match &mut self.highlight {
480            Some(highlight) => {
481                highlight.background = background.into();
482            }
483            None => {
484                self.highlight = Some(Highlight {
485                    background: background.into(),
486                    border: Border::default(),
487                });
488            }
489        }
490
491        self
492    }
493
494    /// Sets the [`Border`] of the [`Span`].
495    pub fn border(self, border: impl Into<Border>) -> Self {
496        self.border_maybe(Some(border))
497    }
498
499    /// Sets the [`Border`] of the [`Span`], if any.
500    pub fn border_maybe(mut self, border: Option<impl Into<Border>>) -> Self {
501        let Some(border) = border else {
502            return self;
503        };
504
505        match &mut self.highlight {
506            Some(highlight) => {
507                highlight.border = border.into();
508            }
509            None => {
510                self.highlight = Some(Highlight {
511                    border: border.into(),
512                    background: Background::Color(Color::TRANSPARENT),
513                });
514            }
515        }
516
517        self
518    }
519
520    /// Sets the [`Padding`] of the [`Span`].
521    ///
522    /// It only affects the [`background`] and [`border`] of the
523    /// [`Span`], currently.
524    ///
525    /// [`background`]: Self::background
526    /// [`border`]: Self::border
527    pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
528        self.padding = padding.into();
529        self
530    }
531
532    /// Sets whether the [`Span`] should be underlined or not.
533    pub fn underline(mut self, underline: bool) -> Self {
534        self.underline = underline;
535        self
536    }
537
538    /// Sets whether the [`Span`] should be struck through or not.
539    pub fn strikethrough(mut self, strikethrough: bool) -> Self {
540        self.strikethrough = strikethrough;
541        self
542    }
543
544    /// Turns the [`Span`] into a static one.
545    pub fn to_static(self) -> Span<'static, Link, Font> {
546        Span {
547            text: Cow::Owned(self.text.into_owned()),
548            size: self.size,
549            line_height: self.line_height,
550            font: self.font,
551            color: self.color,
552            link: self.link,
553            highlight: self.highlight,
554            padding: self.padding,
555            underline: self.underline,
556            strikethrough: self.strikethrough,
557        }
558    }
559}
560
561impl<Link, Font> Default for Span<'_, Link, Font> {
562    fn default() -> Self {
563        Self {
564            text: Cow::default(),
565            size: None,
566            line_height: None,
567            font: None,
568            color: None,
569            link: None,
570            highlight: None,
571            padding: Padding::default(),
572            underline: false,
573            strikethrough: false,
574        }
575    }
576}
577
578impl<'a, Link, Font> From<&'a str> for Span<'a, Link, Font> {
579    fn from(value: &'a str) -> Self {
580        Span::new(value)
581    }
582}
583
584impl<Link, Font: PartialEq> PartialEq for Span<'_, Link, Font> {
585    fn eq(&self, other: &Self) -> bool {
586        self.text == other.text
587            && self.size == other.size
588            && self.line_height == other.line_height
589            && self.font == other.font
590            && self.color == other.color
591    }
592}
593
594/// A fragment of [`Text`].
595///
596/// This is just an alias to a string that may be either
597/// borrowed or owned.
598pub type Fragment<'a> = Cow<'a, str>;
599
600/// A trait for converting a value to some text [`Fragment`].
601pub trait IntoFragment<'a> {
602    /// Converts the value to some text [`Fragment`].
603    fn into_fragment(self) -> Fragment<'a>;
604}
605
606impl<'a> IntoFragment<'a> for Fragment<'a> {
607    fn into_fragment(self) -> Fragment<'a> {
608        self
609    }
610}
611
612impl<'a> IntoFragment<'a> for &'a Fragment<'_> {
613    fn into_fragment(self) -> Fragment<'a> {
614        Fragment::Borrowed(self)
615    }
616}
617
618impl<'a> IntoFragment<'a> for &'a str {
619    fn into_fragment(self) -> Fragment<'a> {
620        Fragment::Borrowed(self)
621    }
622}
623
624impl<'a> IntoFragment<'a> for &'a String {
625    fn into_fragment(self) -> Fragment<'a> {
626        Fragment::Borrowed(self.as_str())
627    }
628}
629
630impl<'a> IntoFragment<'a> for String {
631    fn into_fragment(self) -> Fragment<'a> {
632        Fragment::Owned(self)
633    }
634}
635
636macro_rules! into_fragment {
637    ($type:ty) => {
638        impl<'a> IntoFragment<'a> for $type {
639            fn into_fragment(self) -> Fragment<'a> {
640                Fragment::Owned(self.to_string())
641            }
642        }
643
644        impl<'a> IntoFragment<'a> for &$type {
645            fn into_fragment(self) -> Fragment<'a> {
646                Fragment::Owned(self.to_string())
647            }
648        }
649    };
650}
651
652into_fragment!(char);
653into_fragment!(bool);
654
655into_fragment!(u8);
656into_fragment!(u16);
657into_fragment!(u32);
658into_fragment!(u64);
659into_fragment!(u128);
660into_fragment!(usize);
661
662into_fragment!(i8);
663into_fragment!(i16);
664into_fragment!(i32);
665into_fragment!(i64);
666into_fragment!(i128);
667into_fragment!(isize);
668
669into_fragment!(f32);
670into_fragment!(f64);