bevy_text/
text.rs

1use crate::{Font, TextLayoutInfo, TextSpanAccess, TextSpanComponent};
2use bevy_asset::Handle;
3use bevy_color::Color;
4use bevy_derive::{Deref, DerefMut};
5use bevy_ecs::{prelude::*, reflect::ReflectComponent};
6use bevy_reflect::prelude::*;
7use bevy_utils::{default, once};
8use core::fmt::{Debug, Formatter};
9use core::str::from_utf8;
10use cosmic_text::{Buffer, Metrics};
11use serde::{Deserialize, Serialize};
12use smallvec::SmallVec;
13use tracing::warn;
14
15/// Wrapper for [`cosmic_text::Buffer`]
16#[derive(Deref, DerefMut, Debug, Clone)]
17pub struct CosmicBuffer(pub Buffer);
18
19impl Default for CosmicBuffer {
20    fn default() -> Self {
21        Self(Buffer::new_empty(Metrics::new(20.0, 20.0)))
22    }
23}
24
25/// A sub-entity of a [`ComputedTextBlock`].
26///
27/// Returned by [`ComputedTextBlock::entities`].
28#[derive(Debug, Copy, Clone, Reflect)]
29#[reflect(Debug, Clone)]
30pub struct TextEntity {
31    /// The entity.
32    pub entity: Entity,
33    /// Records the hierarchy depth of the entity within a `TextLayout`.
34    pub depth: usize,
35}
36
37/// Computed information for a text block.
38///
39/// See [`TextLayout`].
40///
41/// Automatically updated by 2d and UI text systems.
42#[derive(Component, Debug, Clone, Reflect)]
43#[reflect(Component, Debug, Default, Clone)]
44pub struct ComputedTextBlock {
45    /// Buffer for managing text layout and creating [`TextLayoutInfo`].
46    ///
47    /// This is private because buffer contents are always refreshed from ECS state when writing glyphs to
48    /// `TextLayoutInfo`. If you want to control the buffer contents manually or use the `cosmic-text`
49    /// editor, then you need to not use `TextLayout` and instead manually implement the conversion to
50    /// `TextLayoutInfo`.
51    #[reflect(ignore, clone)]
52    pub(crate) buffer: CosmicBuffer,
53    /// Entities for all text spans in the block, including the root-level text.
54    ///
55    /// The [`TextEntity::depth`] field can be used to reconstruct the hierarchy.
56    pub(crate) entities: SmallVec<[TextEntity; 1]>,
57    /// Flag set when any change has been made to this block that should cause it to be rerendered.
58    ///
59    /// Includes:
60    /// - [`TextLayout`] changes.
61    /// - [`TextFont`] or `Text2d`/`Text`/`TextSpan` changes anywhere in the block's entity hierarchy.
62    // TODO: This encompasses both structural changes like font size or justification and non-structural
63    // changes like text color and font smoothing. This field currently causes UI to 'remeasure' text, even if
64    // the actual changes are non-structural and can be handled by only rerendering and not remeasuring. A full
65    // solution would probably require splitting TextLayout and TextFont into structural/non-structural
66    // components for more granular change detection. A cost/benefit analysis is needed.
67    pub(crate) needs_rerender: bool,
68}
69
70impl ComputedTextBlock {
71    /// Accesses entities in this block.
72    ///
73    /// Can be used to look up [`TextFont`] components for glyphs in [`TextLayoutInfo`] using the `span_index`
74    /// stored there.
75    pub fn entities(&self) -> &[TextEntity] {
76        &self.entities
77    }
78
79    /// Indicates if the text needs to be refreshed in [`TextLayoutInfo`].
80    ///
81    /// Updated automatically by [`detect_text_needs_rerender`] and cleared
82    /// by [`TextPipeline`](crate::TextPipeline) methods.
83    pub fn needs_rerender(&self) -> bool {
84        self.needs_rerender
85    }
86    /// Accesses the underlying buffer which can be used for `cosmic-text` APIs such as accessing layout information
87    /// or calculating a cursor position.
88    ///
89    /// Mutable access is not offered because changes would be overwritten during the automated layout calculation.
90    /// If you want to control the buffer contents manually or use the `cosmic-text`
91    /// editor, then you need to not use `TextLayout` and instead manually implement the conversion to
92    /// `TextLayoutInfo`.
93    pub fn buffer(&self) -> &CosmicBuffer {
94        &self.buffer
95    }
96}
97
98impl Default for ComputedTextBlock {
99    fn default() -> Self {
100        Self {
101            buffer: CosmicBuffer::default(),
102            entities: SmallVec::default(),
103            needs_rerender: true,
104        }
105    }
106}
107
108/// Component with text format settings for a block of text.
109///
110/// A block of text is composed of text spans, which each have a separate string value and [`TextFont`]. Text
111/// spans associated with a text block are collected into [`ComputedTextBlock`] for layout, and then inserted
112/// to [`TextLayoutInfo`] for rendering.
113///
114/// See `Text2d` in `bevy_sprite` for the core component of 2d text, and `Text` in `bevy_ui` for UI text.
115#[derive(Component, Debug, Copy, Clone, Default, Reflect)]
116#[reflect(Component, Default, Debug, Clone)]
117#[require(ComputedTextBlock, TextLayoutInfo)]
118pub struct TextLayout {
119    /// The text's internal alignment.
120    /// Should not affect its position within a container.
121    pub justify: Justify,
122    /// How the text should linebreak when running out of the bounds determined by `max_size`.
123    pub linebreak: LineBreak,
124}
125
126impl TextLayout {
127    /// Makes a new [`TextLayout`].
128    pub const fn new(justify: Justify, linebreak: LineBreak) -> Self {
129        Self { justify, linebreak }
130    }
131
132    /// Makes a new [`TextLayout`] with the specified [`Justify`].
133    pub fn new_with_justify(justify: Justify) -> Self {
134        Self::default().with_justify(justify)
135    }
136
137    /// Makes a new [`TextLayout`] with the specified [`LineBreak`].
138    pub fn new_with_linebreak(linebreak: LineBreak) -> Self {
139        Self::default().with_linebreak(linebreak)
140    }
141
142    /// Makes a new [`TextLayout`] with soft wrapping disabled.
143    /// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.
144    pub fn new_with_no_wrap() -> Self {
145        Self::default().with_no_wrap()
146    }
147
148    /// Returns this [`TextLayout`] with the specified [`Justify`].
149    pub const fn with_justify(mut self, justify: Justify) -> Self {
150        self.justify = justify;
151        self
152    }
153
154    /// Returns this [`TextLayout`] with the specified [`LineBreak`].
155    pub const fn with_linebreak(mut self, linebreak: LineBreak) -> Self {
156        self.linebreak = linebreak;
157        self
158    }
159
160    /// Returns this [`TextLayout`] with soft wrapping disabled.
161    /// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.
162    pub const fn with_no_wrap(mut self) -> Self {
163        self.linebreak = LineBreak::NoWrap;
164        self
165    }
166}
167
168/// A span of text in a tree of spans.
169///
170/// A `TextSpan` is only valid when it exists as a child of a parent that has either `Text` or
171/// `Text2d`. The parent's `Text` / `Text2d` component contains the base text content. Any children
172/// with `TextSpan` extend this text by appending their content to the parent's text in sequence to
173/// form a [`ComputedTextBlock`]. The parent's [`TextLayout`] determines the layout of the block
174/// but each node has its own [`TextFont`] and [`TextColor`].
175#[derive(Component, Debug, Default, Clone, Deref, DerefMut, Reflect)]
176#[reflect(Component, Default, Debug, Clone)]
177#[require(TextFont, TextColor, LineHeight)]
178pub struct TextSpan(pub String);
179
180impl TextSpan {
181    /// Makes a new text span component.
182    pub fn new(text: impl Into<String>) -> Self {
183        Self(text.into())
184    }
185}
186
187impl TextSpanComponent for TextSpan {}
188
189impl TextSpanAccess for TextSpan {
190    fn read_span(&self) -> &str {
191        self.as_str()
192    }
193    fn write_span(&mut self) -> &mut String {
194        &mut *self
195    }
196}
197
198impl From<&str> for TextSpan {
199    fn from(value: &str) -> Self {
200        Self(String::from(value))
201    }
202}
203
204impl From<String> for TextSpan {
205    fn from(value: String) -> Self {
206        Self(value)
207    }
208}
209
210/// Describes the horizontal alignment of multiple lines of text relative to each other.
211///
212/// This only affects the internal positioning of the lines of text within a text entity and
213/// does not affect the text entity's position.
214///
215/// _Has no affect on a single line text entity_, unless used together with a
216/// [`TextBounds`](super::bounds::TextBounds) component with an explicit `width` value.
217#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
218#[reflect(Serialize, Deserialize, Clone, PartialEq, Hash)]
219#[doc(alias = "JustifyText")]
220pub enum Justify {
221    /// Leftmost character is immediately to the right of the render position.
222    /// Bounds start from the render position and advance rightwards.
223    #[default]
224    Left,
225    /// Leftmost & rightmost characters are equidistant to the render position.
226    /// Bounds start from the render position and advance equally left & right.
227    Center,
228    /// Rightmost character is immediately to the left of the render position.
229    /// Bounds start from the render position and advance leftwards.
230    Right,
231    /// Words are spaced so that leftmost & rightmost characters
232    /// align with their margins.
233    /// Bounds start from the render position and advance equally left & right.
234    Justified,
235}
236
237impl From<Justify> for cosmic_text::Align {
238    fn from(justify: Justify) -> Self {
239        match justify {
240            Justify::Left => cosmic_text::Align::Left,
241            Justify::Center => cosmic_text::Align::Center,
242            Justify::Right => cosmic_text::Align::Right,
243            Justify::Justified => cosmic_text::Align::Justified,
244        }
245    }
246}
247
248/// `TextFont` determines the style of a text span within a [`ComputedTextBlock`], specifically
249/// the font face, the font size, the line height, and the antialiasing method.
250#[derive(Component, Clone, Debug, Reflect, PartialEq)]
251#[reflect(Component, Default, Debug, Clone)]
252pub struct TextFont {
253    /// The specific font face to use, as a `Handle` to a [`Font`] asset.
254    ///
255    /// If the `font` is not specified, then
256    /// * if `default_font` feature is enabled (enabled by default in `bevy` crate),
257    ///   `FiraMono-subset.ttf` compiled into the library is used.
258    /// * otherwise no text will be rendered, unless a custom font is loaded into the default font
259    ///   handle.
260    pub font: Handle<Font>,
261    /// The vertical height of rasterized glyphs in the font atlas in pixels.
262    ///
263    /// This is multiplied by the window scale factor and `UiScale`, but not the text entity
264    /// transform or camera projection.
265    ///
266    /// A new font atlas is generated for every combination of font handle and scaled font size
267    /// which can have a strong performance impact.
268    pub font_size: f32,
269    /// How thick or bold the strokes of a font appear.
270    ///
271    /// Font weights can be any value between 1 and 1000, inclusive.
272    ///
273    /// Only supports variable weight fonts.
274    pub weight: FontWeight,
275    /// The antialiasing method to use when rendering text.
276    pub font_smoothing: FontSmoothing,
277    /// OpenType features for .otf fonts that support them.
278    pub font_features: FontFeatures,
279}
280
281impl TextFont {
282    /// Returns a new [`TextFont`] with the specified font size.
283    pub fn from_font_size(font_size: f32) -> Self {
284        Self::default().with_font_size(font_size)
285    }
286
287    /// Returns this [`TextFont`] with the specified font face handle.
288    pub fn with_font(mut self, font: Handle<Font>) -> Self {
289        self.font = font;
290        self
291    }
292
293    /// Returns this [`TextFont`] with the specified font size.
294    pub const fn with_font_size(mut self, font_size: f32) -> Self {
295        self.font_size = font_size;
296        self
297    }
298
299    /// Returns this [`TextFont`] with the specified [`FontSmoothing`].
300    pub const fn with_font_smoothing(mut self, font_smoothing: FontSmoothing) -> Self {
301        self.font_smoothing = font_smoothing;
302        self
303    }
304}
305
306impl From<Handle<Font>> for TextFont {
307    fn from(font: Handle<Font>) -> Self {
308        Self { font, ..default() }
309    }
310}
311
312impl Default for TextFont {
313    fn default() -> Self {
314        Self {
315            font: Default::default(),
316            font_size: 20.0,
317            weight: FontWeight::NORMAL,
318            font_features: FontFeatures::default(),
319            font_smoothing: Default::default(),
320        }
321    }
322}
323
324/// How thick or bold the strokes of a font appear.
325///
326/// Valid font weights range from 1 to 1000, inclusive.
327/// Weights above 1000 are clamped to 1000.
328/// A weight of 0 is treated as [`FontWeight::DEFAULT`].
329///
330/// `<https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-weight>`
331#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect)]
332pub struct FontWeight(pub u16);
333
334impl FontWeight {
335    /// Weight 100.
336    pub const THIN: FontWeight = FontWeight(100);
337
338    /// Weight 200.
339    pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
340
341    /// Weight 300.
342    pub const LIGHT: FontWeight = FontWeight(300);
343
344    /// Weight 400.
345    pub const NORMAL: FontWeight = FontWeight(400);
346
347    /// Weight 500.
348    pub const MEDIUM: FontWeight = FontWeight(500);
349
350    /// Weight 600.
351    pub const SEMIBOLD: FontWeight = FontWeight(600);
352
353    /// Weight 700.
354    pub const BOLD: FontWeight = FontWeight(700);
355
356    /// Weight 800
357    pub const EXTRA_BOLD: FontWeight = FontWeight(800);
358
359    /// Weight 900.
360    pub const BLACK: FontWeight = FontWeight(900);
361
362    /// Weight 950.
363    pub const EXTRA_BLACK: FontWeight = FontWeight(950);
364
365    /// The default font weight.
366    pub const DEFAULT: FontWeight = Self::NORMAL;
367
368    /// Clamp the weight value to between 1 and 1000.
369    /// Values of 0 are mapped to `Weight::DEFAULT`.
370    pub const fn clamp(mut self) -> Self {
371        if self.0 == 0 {
372            self = Self::DEFAULT;
373        } else if 1000 < self.0 {
374            self.0 = 1000;
375        }
376        Self(self.0)
377    }
378}
379
380impl Default for FontWeight {
381    fn default() -> Self {
382        Self::DEFAULT
383    }
384}
385
386impl From<FontWeight> for cosmic_text::Weight {
387    fn from(value: FontWeight) -> Self {
388        cosmic_text::Weight(value.clamp().0)
389    }
390}
391
392/// An OpenType font feature tag.
393#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect)]
394pub struct FontFeatureTag([u8; 4]);
395
396impl FontFeatureTag {
397    /// Replaces character combinations like fi, fl with ligatures.
398    pub const STANDARD_LIGATURES: FontFeatureTag = FontFeatureTag::new(b"liga");
399
400    /// Enables ligatures based on character context.
401    pub const CONTEXTUAL_LIGATURES: FontFeatureTag = FontFeatureTag::new(b"clig");
402
403    /// Enables optional ligatures for stylistic use (e.g., ct, st).
404    pub const DISCRETIONARY_LIGATURES: FontFeatureTag = FontFeatureTag::new(b"dlig");
405
406    /// Adjust glyph shapes based on surrounding letters.
407    pub const CONTEXTUAL_ALTERNATES: FontFeatureTag = FontFeatureTag::new(b"calt");
408
409    /// Use alternate glyph designs.
410    pub const STYLISTIC_ALTERNATES: FontFeatureTag = FontFeatureTag::new(b"salt");
411
412    /// Replaces lowercase letters with small caps.
413    pub const SMALL_CAPS: FontFeatureTag = FontFeatureTag::new(b"smcp");
414
415    /// Replaces uppercase letters with small caps.
416    pub const CAPS_TO_SMALL_CAPS: FontFeatureTag = FontFeatureTag::new(b"c2sc");
417
418    /// Replaces characters with swash versions (often decorative).
419    pub const SWASH: FontFeatureTag = FontFeatureTag::new(b"swsh");
420
421    /// Enables alternate glyphs for large sizes or titles.
422    pub const TITLING_ALTERNATES: FontFeatureTag = FontFeatureTag::new(b"titl");
423
424    /// Converts numbers like 1/2 into true fractions (½).
425    pub const FRACTIONS: FontFeatureTag = FontFeatureTag::new(b"frac");
426
427    /// Formats characters like 1st, 2nd properly.
428    pub const ORDINALS: FontFeatureTag = FontFeatureTag::new(b"ordn");
429
430    /// Uses a slashed version of zero (0) to differentiate from O.
431    pub const SLASHED_ZERO: FontFeatureTag = FontFeatureTag::new(b"ordn");
432
433    /// Replaces figures with superscript figures, e.g. for indicating footnotes.
434    pub const SUPERSCRIPT: FontFeatureTag = FontFeatureTag::new(b"sups");
435
436    /// Replaces figures with subscript figures.
437    pub const SUBSCRIPT: FontFeatureTag = FontFeatureTag::new(b"subs");
438
439    /// Changes numbers to "oldstyle" form, which fit better in the flow of sentences or other text.
440    pub const OLDSTYLE_FIGURES: FontFeatureTag = FontFeatureTag::new(b"onum");
441
442    /// Changes numbers to "lining" form, which are better suited for standalone numbers. When
443    /// enabled, the bottom of all numbers will be aligned with each other.
444    pub const LINING_FIGURES: FontFeatureTag = FontFeatureTag::new(b"lnum");
445
446    /// Changes numbers to be of proportional width. When enabled, numbers may have varying widths.
447    pub const PROPORTIONAL_FIGURES: FontFeatureTag = FontFeatureTag::new(b"pnum");
448
449    /// Changes numbers to be of uniform (tabular) width. When enabled, all numbers will have the
450    /// same width.
451    pub const TABULAR_FIGURES: FontFeatureTag = FontFeatureTag::new(b"tnum");
452
453    /// Varies the stroke thickness. Valid values are in the range of 1 to 1000, inclusive.
454    pub const WEIGHT: FontFeatureTag = FontFeatureTag::new(b"wght");
455
456    /// Varies the width of text from narrower to wider. Must be a value greater than 0. A value of
457    /// 100 is typically considered standard width.
458    pub const WIDTH: FontFeatureTag = FontFeatureTag::new(b"wdth");
459
460    /// Varies between upright and slanted text. Must be a value greater than -90 and less than +90.
461    /// A value of 0 is upright.
462    pub const SLANT: FontFeatureTag = FontFeatureTag::new(b"slnt");
463
464    /// Create a new [`FontFeatureTag`] from raw bytes.
465    pub const fn new(src: &[u8; 4]) -> Self {
466        Self(*src)
467    }
468}
469
470impl Debug for FontFeatureTag {
471    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
472        // OpenType tags are always ASCII, so this match will succeed for valid tags. This gives us
473        // human-readable debug output, e.g. FontFeatureTag("liga").
474        match from_utf8(&self.0) {
475            Ok(s) => write!(f, "FontFeatureTag(\"{}\")", s),
476            Err(_) => write!(f, "FontFeatureTag({:?})", self.0),
477        }
478    }
479}
480
481/// OpenType features for .otf fonts that support them.
482///
483/// Examples features include ligatures, small-caps, and fractional number display. For the complete
484/// list of OpenType features, see the spec at
485/// `<https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist>`.
486///
487/// # Usage:
488/// ```
489/// use bevy_text::{FontFeatureTag, FontFeatures};
490///
491/// // Create using the builder
492/// let font_features = FontFeatures::builder()
493///   .enable(FontFeatureTag::STANDARD_LIGATURES)
494///   .set(FontFeatureTag::WEIGHT, 300)
495///   .build();
496///
497/// // Create from a list
498/// let more_font_features: FontFeatures = [
499///   FontFeatureTag::STANDARD_LIGATURES,
500///   FontFeatureTag::OLDSTYLE_FIGURES,
501///   FontFeatureTag::TABULAR_FIGURES
502/// ].into();
503/// ```
504#[derive(Clone, Debug, Default, Reflect, PartialEq)]
505pub struct FontFeatures {
506    features: Vec<(FontFeatureTag, u32)>,
507}
508
509impl FontFeatures {
510    /// Create a new [`FontFeaturesBuilder`].
511    pub fn builder() -> FontFeaturesBuilder {
512        FontFeaturesBuilder::default()
513    }
514}
515
516/// A builder for [`FontFeatures`].
517#[derive(Clone, Default)]
518pub struct FontFeaturesBuilder {
519    features: Vec<(FontFeatureTag, u32)>,
520}
521
522impl FontFeaturesBuilder {
523    /// Enable an OpenType feature.
524    ///
525    /// Most OpenType features are on/off switches, so this is a convenience method that sets the
526    /// feature's value to "1" (enabled). For non-boolean features, see [`FontFeaturesBuilder::set`].
527    pub fn enable(self, feature_tag: FontFeatureTag) -> Self {
528        self.set(feature_tag, 1)
529    }
530
531    /// Set an OpenType feature to a specific value.
532    ///
533    /// For most features, the [`FontFeaturesBuilder::enable`] method should be used instead. A few
534    /// features, such as "wght", take numeric values, so this method may be used for these cases.
535    pub fn set(mut self, feature_tag: FontFeatureTag, value: u32) -> Self {
536        self.features.push((feature_tag, value));
537        self
538    }
539
540    /// Build a [`FontFeatures`] from the values set within this builder.
541    pub fn build(self) -> FontFeatures {
542        FontFeatures {
543            features: self.features,
544        }
545    }
546}
547
548/// Allow [`FontFeatures`] to be built from a list. This is suitable for the standard case when each
549/// listed feature is a boolean type. If any features require a numeric value (like "wght"), use
550/// [`FontFeaturesBuilder`] instead.
551impl<T> From<T> for FontFeatures
552where
553    T: IntoIterator<Item = FontFeatureTag>,
554{
555    fn from(value: T) -> Self {
556        FontFeatures {
557            features: value.into_iter().map(|x| (x, 1)).collect(),
558        }
559    }
560}
561
562impl From<&FontFeatures> for cosmic_text::FontFeatures {
563    fn from(font_features: &FontFeatures) -> Self {
564        cosmic_text::FontFeatures {
565            features: font_features
566                .features
567                .iter()
568                .map(|(tag, value)| cosmic_text::Feature {
569                    tag: cosmic_text::FeatureTag::new(&tag.0),
570                    value: *value,
571                })
572                .collect(),
573        }
574    }
575}
576
577/// Specifies the height of each line of text for `Text` and `Text2d`
578///
579/// Default is 1.2x the font size
580#[derive(Component, Debug, Clone, Copy, PartialEq, Reflect)]
581#[reflect(Component, Debug, Clone, PartialEq)]
582pub enum LineHeight {
583    /// Set line height to a specific number of pixels
584    Px(f32),
585    /// Set line height to a multiple of the font size
586    RelativeToFont(f32),
587}
588
589impl LineHeight {
590    pub(crate) fn eval(self, font_size: f32) -> f32 {
591        match self {
592            LineHeight::Px(px) => px,
593            LineHeight::RelativeToFont(scale) => scale * font_size,
594        }
595    }
596}
597
598impl Default for LineHeight {
599    fn default() -> Self {
600        LineHeight::RelativeToFont(1.2)
601    }
602}
603
604/// The color of the text for this section.
605#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)]
606#[reflect(Component, Default, Debug, PartialEq, Clone)]
607pub struct TextColor(pub Color);
608
609impl Default for TextColor {
610    fn default() -> Self {
611        Self::WHITE
612    }
613}
614
615impl<T: Into<Color>> From<T> for TextColor {
616    fn from(color: T) -> Self {
617        Self(color.into())
618    }
619}
620
621impl TextColor {
622    /// Black colored text
623    pub const BLACK: Self = TextColor(Color::BLACK);
624    /// White colored text
625    pub const WHITE: Self = TextColor(Color::WHITE);
626}
627
628/// The background color of the text for this section.
629#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)]
630#[reflect(Component, Default, Debug, PartialEq, Clone)]
631pub struct TextBackgroundColor(pub Color);
632
633impl Default for TextBackgroundColor {
634    fn default() -> Self {
635        Self(Color::BLACK)
636    }
637}
638
639impl<T: Into<Color>> From<T> for TextBackgroundColor {
640    fn from(color: T) -> Self {
641        Self(color.into())
642    }
643}
644
645impl TextBackgroundColor {
646    /// Black background
647    pub const BLACK: Self = TextBackgroundColor(Color::BLACK);
648    /// White background
649    pub const WHITE: Self = TextBackgroundColor(Color::WHITE);
650}
651
652/// Determines how lines will be broken when preventing text from running out of bounds.
653#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Reflect, Serialize, Deserialize)]
654#[reflect(Serialize, Deserialize, Clone, PartialEq, Hash, Default)]
655pub enum LineBreak {
656    /// Uses the [Unicode Line Breaking Algorithm](https://www.unicode.org/reports/tr14/).
657    /// Lines will be broken up at the nearest suitable word boundary, usually a space.
658    /// This behavior suits most cases, as it keeps words intact across linebreaks.
659    #[default]
660    WordBoundary,
661    /// Lines will be broken without discrimination on any character that would leave bounds.
662    /// This is closer to the behavior one might expect from text in a terminal.
663    /// However it may lead to words being broken up across linebreaks.
664    AnyCharacter,
665    /// Wraps at the word level, or fallback to character level if a word can’t fit on a line by itself
666    WordOrCharacter,
667    /// No soft wrapping, where text is automatically broken up into separate lines when it overflows a boundary, will ever occur.
668    /// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, is still enabled.
669    NoWrap,
670}
671
672/// A text entity with this component is drawn with strikethrough.
673#[derive(Component, Copy, Clone, Debug, Reflect, Default, Serialize, Deserialize)]
674#[reflect(Serialize, Deserialize, Clone, Default)]
675pub struct Strikethrough;
676
677/// Color for the text's strikethrough. If this component is not present, its `TextColor` will be used.
678#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)]
679#[reflect(Component, Default, Debug, PartialEq, Clone)]
680pub struct StrikethroughColor(pub Color);
681
682impl Default for StrikethroughColor {
683    fn default() -> Self {
684        Self(Color::WHITE)
685    }
686}
687
688impl<T: Into<Color>> From<T> for StrikethroughColor {
689    fn from(color: T) -> Self {
690        Self(color.into())
691    }
692}
693
694/// Add to a text entity to draw its text with underline.
695#[derive(Component, Copy, Clone, Debug, Reflect, Default, Serialize, Deserialize)]
696#[reflect(Serialize, Deserialize, Clone, Default)]
697pub struct Underline;
698
699/// Color for the text's underline. If this component is not present, its `TextColor` will be used.
700#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)]
701#[reflect(Component, Default, Debug, PartialEq, Clone)]
702pub struct UnderlineColor(pub Color);
703
704impl Default for UnderlineColor {
705    fn default() -> Self {
706        Self(Color::WHITE)
707    }
708}
709
710impl<T: Into<Color>> From<T> for UnderlineColor {
711    fn from(color: T) -> Self {
712        Self(color.into())
713    }
714}
715
716/// Determines which antialiasing method to use when rendering text. By default, text is
717/// rendered with grayscale antialiasing, but this can be changed to achieve a pixelated look.
718///
719/// **Note:** Subpixel antialiasing is not currently supported.
720#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Reflect, Serialize, Deserialize)]
721#[reflect(Serialize, Deserialize, Clone, PartialEq, Hash, Default)]
722#[doc(alias = "antialiasing")]
723#[doc(alias = "pixelated")]
724pub enum FontSmoothing {
725    /// No antialiasing. Useful for when you want to render text with a pixel art aesthetic.
726    ///
727    /// Combine this with `UiAntiAlias::Off` and `Msaa::Off` on your 2D camera for a fully pixelated look.
728    ///
729    /// **Note:** Due to limitations of the underlying text rendering library,
730    /// this may require specially-crafted pixel fonts to look good, especially at small sizes.
731    None,
732    /// The default grayscale antialiasing. Produces text that looks smooth,
733    /// even at small font sizes and low resolutions with modern vector fonts.
734    #[default]
735    AntiAliased,
736    // TODO: Add subpixel antialias support
737    // SubpixelAntiAliased,
738}
739
740/// System that detects changes to text blocks and sets `ComputedTextBlock::should_rerender`.
741///
742/// Generic over the root text component and text span component. For example, `Text2d`/[`TextSpan`] for
743/// 2d or `Text`/[`TextSpan`] for UI.
744pub fn detect_text_needs_rerender<Root: Component>(
745    changed_roots: Query<
746        Entity,
747        (
748            Or<(
749                Changed<Root>,
750                Changed<TextFont>,
751                Changed<TextLayout>,
752                Changed<LineHeight>,
753                Changed<Children>,
754            )>,
755            With<Root>,
756            With<TextFont>,
757            With<TextLayout>,
758        ),
759    >,
760    changed_spans: Query<
761        (Entity, Option<&ChildOf>, Has<TextLayout>),
762        (
763            Or<(
764                Changed<TextSpan>,
765                Changed<TextFont>,
766                Changed<LineHeight>,
767                Changed<Children>,
768                Changed<ChildOf>, // Included to detect broken text block hierarchies.
769                Added<TextLayout>,
770            )>,
771            With<TextSpan>,
772            With<TextFont>,
773        ),
774    >,
775    mut computed: Query<(
776        Option<&ChildOf>,
777        Option<&mut ComputedTextBlock>,
778        Has<TextSpan>,
779    )>,
780) {
781    // Root entity:
782    // - Root component changed.
783    // - TextFont on root changed.
784    // - TextLayout changed.
785    // - Root children changed (can include additions and removals).
786    for root in changed_roots.iter() {
787        let Ok((_, Some(mut computed), _)) = computed.get_mut(root) else {
788            once!(warn!("found entity {} with a root text component ({}) but no ComputedTextBlock; this warning only \
789                prints once", root, core::any::type_name::<Root>()));
790            continue;
791        };
792        computed.needs_rerender = true;
793    }
794
795    // Span entity:
796    // - Span component changed.
797    // - Span TextFont changed.
798    // - Span children changed (can include additions and removals).
799    for (entity, maybe_span_child_of, has_text_block) in changed_spans.iter() {
800        if has_text_block {
801            once!(warn!("found entity {} with a TextSpan that has a TextLayout, which should only be on root \
802                text entities (that have {}); this warning only prints once",
803                entity, core::any::type_name::<Root>()));
804        }
805
806        let Some(span_child_of) = maybe_span_child_of else {
807            once!(warn!(
808                "found entity {} with a TextSpan that has no parent; it should have an ancestor \
809                with a root text component ({}); this warning only prints once",
810                entity,
811                core::any::type_name::<Root>()
812            ));
813            continue;
814        };
815        let mut parent: Entity = span_child_of.parent();
816
817        // Search for the nearest ancestor with ComputedTextBlock.
818        // Note: We assume the perf cost from duplicate visits in the case that multiple spans in a block are visited
819        // is outweighed by the expense of tracking visited spans.
820        loop {
821            let Ok((maybe_child_of, maybe_computed, has_span)) = computed.get_mut(parent) else {
822                once!(warn!("found entity {} with a TextSpan that is part of a broken hierarchy with a ChildOf \
823                    component that points at non-existent entity {}; this warning only prints once",
824                    entity, parent));
825                break;
826            };
827            if let Some(mut computed) = maybe_computed {
828                computed.needs_rerender = true;
829                break;
830            }
831            if !has_span {
832                once!(warn!("found entity {} with a TextSpan that has an ancestor ({}) that does not have a text \
833                span component or a ComputedTextBlock component; this warning only prints once",
834                    entity, parent));
835                break;
836            }
837            let Some(next_child_of) = maybe_child_of else {
838                once!(warn!(
839                    "found entity {} with a TextSpan that has no ancestor with the root text \
840                    component ({}); this warning only prints once",
841                    entity,
842                    core::any::type_name::<Root>()
843                ));
844                break;
845            };
846            parent = next_child_of.parent();
847        }
848    }
849}
850
851#[derive(Component, Debug, Copy, Clone, Default, Reflect, PartialEq)]
852#[reflect(Component, Default, Debug, Clone, PartialEq)]
853/// Font hinting strategy.
854///
855/// The text bounds can underflow or overflow slightly with `FontHinting::Enabled`.
856///
857/// <https://docs.rs/cosmic-text/latest/cosmic_text/enum.Hinting.html>
858pub enum FontHinting {
859    #[default]
860    /// Glyphs will have subpixel coordinates.
861    Disabled,
862    /// Glyphs will be snapped to integral coordinates in the X-axis during layout.
863    ///
864    /// The text bounds can underflow or overflow slightly with this enabled.
865    Enabled,
866}
867
868impl From<FontHinting> for cosmic_text::Hinting {
869    fn from(value: FontHinting) -> Self {
870        match value {
871            FontHinting::Disabled => cosmic_text::Hinting::Disabled,
872            FontHinting::Enabled => cosmic_text::Hinting::Enabled,
873        }
874    }
875}