Skip to main content

iced_core/
text.rs

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