Skip to main content

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