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