Skip to main content

iced_core/
text.rs

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