use crate::{Font, TextLayoutInfo, TextSpanAccess, TextSpanComponent};1use bevy_asset::Handle;2use bevy_color::Color;3use bevy_derive::{Deref, DerefMut};4use bevy_ecs::{prelude::*, reflect::ReflectComponent};5use bevy_math::Vec2;6use bevy_reflect::prelude::*;7use bevy_utils::{default, once};8use core::fmt::{Debug, Formatter};9use core::str::from_utf8;10use cosmic_text::{Buffer, Family, Metrics, Stretch};11use serde::{Deserialize, Serialize};12use smallvec::SmallVec;13use smol_str::SmolStr;14use tracing::warn;1516/// Wrapper for [`cosmic_text::Buffer`]17#[derive(Deref, DerefMut, Debug, Clone)]18pub struct CosmicBuffer(pub Buffer);1920impl Default for CosmicBuffer {21fn default() -> Self {22Self(Buffer::new_empty(Metrics::new(20.0, 20.0)))23}24}2526/// A sub-entity of a [`ComputedTextBlock`].27///28/// Returned by [`ComputedTextBlock::entities`].29#[derive(Debug, Copy, Clone, Reflect)]30#[reflect(Debug, Clone)]31pub struct TextEntity {32/// The entity.33pub entity: Entity,34/// Records the hierarchy depth of the entity within a `TextLayout`.35pub depth: usize,36/// Antialiasing method to use when rendering the text.37pub font_smoothing: FontSmoothing,38}3940/// Computed information for a text block.41///42/// See [`TextLayout`].43///44/// Automatically updated by 2d and UI text systems.45#[derive(Component, Debug, Clone, Reflect)]46#[reflect(Component, Debug, Default, Clone)]47pub struct ComputedTextBlock {48/// Buffer for managing text layout and creating [`TextLayoutInfo`].49///50/// This is private because buffer contents are always refreshed from ECS state when writing glyphs to51/// `TextLayoutInfo`. If you want to control the buffer contents manually or use the `cosmic-text`52/// editor, then you need to not use `TextLayout` and instead manually implement the conversion to53/// `TextLayoutInfo`.54#[reflect(ignore, clone)]55pub(crate) buffer: CosmicBuffer,56/// Entities for all text spans in the block, including the root-level text.57///58/// The [`TextEntity::depth`] field can be used to reconstruct the hierarchy.59pub(crate) entities: SmallVec<[TextEntity; 1]>,60/// Flag set when any change has been made to this block that should cause it to be rerendered.61///62/// Includes:63/// - [`TextLayout`] changes.64/// - [`TextFont`] or `Text2d`/`Text`/`TextSpan` changes anywhere in the block's entity hierarchy.65// TODO: This encompasses both structural changes like font size or justification and non-structural66// changes like text color and font smoothing. This field currently causes UI to 'remeasure' text, even if67// the actual changes are non-structural and can be handled by only rerendering and not remeasuring. A full68// solution would probably require splitting TextLayout and TextFont into structural/non-structural69// components for more granular change detection. A cost/benefit analysis is needed.70pub(crate) needs_rerender: bool,71// Flag set by `TextPipeline::update_buffer` if any text section in the block has a viewport font size value.72//73// Used by dependents to determine if they should update a text block on changes to74// the viewport size.75pub(crate) uses_viewport_sizes: bool,76// Flag set by `TextPipeline::update_buffer` if any text section in the block has a rem font size value.77//78// Used by dependents to determine if they should update a text block on changes to79// the rem size.80pub(crate) uses_rem_sizes: bool,81}8283impl ComputedTextBlock {84/// Accesses entities in this block.85///86/// Can be used to look up [`TextFont`] components for glyphs in [`TextLayoutInfo`] using the `span_index`87/// stored there.88pub fn entities(&self) -> &[TextEntity] {89&self.entities90}9192/// Indicates if the text needs to be refreshed in [`TextLayoutInfo`].93///94/// Updated automatically by [`detect_text_needs_rerender`] and cleared95/// by [`TextPipeline`](crate::TextPipeline) methods.96pub fn needs_rerender(97&self,98is_viewport_size_changed: bool,99is_rem_size_changed: bool,100) -> bool {101self.needs_rerender102|| (is_viewport_size_changed && self.uses_viewport_sizes)103|| (is_rem_size_changed && self.uses_rem_sizes)104}105/// Accesses the underlying buffer which can be used for `cosmic-text` APIs such as accessing layout information106/// or calculating a cursor position.107///108/// Mutable access is not offered because changes would be overwritten during the automated layout calculation.109/// If you want to control the buffer contents manually or use the `cosmic-text`110/// editor, then you need to not use `TextLayout` and instead manually implement the conversion to111/// `TextLayoutInfo`.112pub fn buffer(&self) -> &CosmicBuffer {113&self.buffer114}115}116117impl Default for ComputedTextBlock {118fn default() -> Self {119Self {120buffer: CosmicBuffer::default(),121entities: SmallVec::default(),122needs_rerender: true,123uses_rem_sizes: false,124uses_viewport_sizes: false,125}126}127}128129/// Component with text format settings for a block of text.130///131/// A block of text is composed of text spans, which each have a separate string value and [`TextFont`]. Text132/// spans associated with a text block are collected into [`ComputedTextBlock`] for layout, and then inserted133/// to [`TextLayoutInfo`] for rendering.134///135/// See `Text2d` in `bevy_sprite` for the core component of 2d text, and `Text` in `bevy_ui` for UI text.136#[derive(Component, Debug, Copy, Clone, Default, Reflect)]137#[reflect(Component, Default, Debug, Clone)]138#[require(ComputedTextBlock, TextLayoutInfo)]139pub struct TextLayout {140/// The text's internal alignment.141/// Should not affect its position within a container.142pub justify: Justify,143/// How the text should linebreak when running out of the bounds determined by `max_size`.144pub linebreak: LineBreak,145}146147impl TextLayout {148/// Makes a new [`TextLayout`].149pub const fn new(justify: Justify, linebreak: LineBreak) -> Self {150Self { justify, linebreak }151}152153/// Makes a new [`TextLayout`] with the specified [`Justify`].154pub fn new_with_justify(justify: Justify) -> Self {155Self::default().with_justify(justify)156}157158/// Makes a new [`TextLayout`] with the specified [`LineBreak`].159pub fn new_with_linebreak(linebreak: LineBreak) -> Self {160Self::default().with_linebreak(linebreak)161}162163/// Makes a new [`TextLayout`] with soft wrapping disabled.164/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.165pub fn new_with_no_wrap() -> Self {166Self::default().with_no_wrap()167}168169/// Returns this [`TextLayout`] with the specified [`Justify`].170pub const fn with_justify(mut self, justify: Justify) -> Self {171self.justify = justify;172self173}174175/// Returns this [`TextLayout`] with the specified [`LineBreak`].176pub const fn with_linebreak(mut self, linebreak: LineBreak) -> Self {177self.linebreak = linebreak;178self179}180181/// Returns this [`TextLayout`] with soft wrapping disabled.182/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.183pub const fn with_no_wrap(mut self) -> Self {184self.linebreak = LineBreak::NoWrap;185self186}187}188189/// A span of text in a tree of spans.190///191/// A `TextSpan` is only valid when it exists as a child of a parent that has either `Text` or192/// `Text2d`. The parent's `Text` / `Text2d` component contains the base text content. Any children193/// with `TextSpan` extend this text by appending their content to the parent's text in sequence to194/// form a [`ComputedTextBlock`]. The parent's [`TextLayout`] determines the layout of the block195/// but each node has its own [`TextFont`] and [`TextColor`].196#[derive(Component, Debug, Default, Clone, Deref, DerefMut, Reflect)]197#[reflect(Component, Default, Debug, Clone)]198#[require(TextFont, TextColor, LineHeight)]199pub struct TextSpan(pub String);200201impl TextSpan {202/// Makes a new text span component.203pub fn new(text: impl Into<String>) -> Self {204Self(text.into())205}206}207208impl TextSpanComponent for TextSpan {}209210impl TextSpanAccess for TextSpan {211fn read_span(&self) -> &str {212self.as_str()213}214fn write_span(&mut self) -> &mut String {215&mut *self216}217}218219impl From<&str> for TextSpan {220fn from(value: &str) -> Self {221Self(String::from(value))222}223}224225impl From<String> for TextSpan {226fn from(value: String) -> Self {227Self(value)228}229}230231/// Describes the horizontal alignment of multiple lines of text relative to each other.232///233/// This only affects the internal positioning of the lines of text within a text entity and234/// does not affect the text entity's position.235///236/// _Has no affect on a single line text entity_, unless used together with a237/// [`TextBounds`](super::bounds::TextBounds) component with an explicit `width` value.238#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]239#[reflect(Serialize, Deserialize, Clone, PartialEq, Hash)]240#[doc(alias = "JustifyText")]241pub enum Justify {242/// Leftmost character is immediately to the right of the render position.243/// Bounds start from the render position and advance rightwards.244#[default]245Left,246/// Leftmost & rightmost characters are equidistant to the render position.247/// Bounds start from the render position and advance equally left & right.248Center,249/// Rightmost character is immediately to the left of the render position.250/// Bounds start from the render position and advance leftwards.251Right,252/// Words are spaced so that leftmost & rightmost characters253/// align with their margins.254/// Bounds start from the render position and advance equally left & right.255Justified,256}257258impl From<Justify> for cosmic_text::Align {259fn from(justify: Justify) -> Self {260match justify {261Justify::Left => cosmic_text::Align::Left,262Justify::Center => cosmic_text::Align::Center,263Justify::Right => cosmic_text::Align::Right,264Justify::Justified => cosmic_text::Align::Justified,265}266}267}268269#[derive(Clone, Debug, Reflect, PartialEq)]270/// Determines how the font face for a text sections is selected.271///272/// A `FontSource` can be a handle to a font asset, a font family name,273/// or a generic font category that is resolved using Cosmic Text's font database.274///275/// The `CosmicFontSystem` resource can be used to change the font family276/// associated to a generic font variant:277/// ```278/// # use bevy_text::CosmicFontSystem;279/// # use bevy_text::FontSource;280/// let mut font_system = CosmicFontSystem::default();281/// let mut font_database = font_system.db_mut();282/// font_database.set_serif_family("Allegro");283/// font_database.set_sans_serif_family("Encode Sans");284/// font_database.set_cursive_family("Cedarville Cursive");285/// font_database.set_fantasy_family("Argusho");286/// font_database.set_monospace_family("Lucida Console");287///288/// // `CosmicFontSystem::get_family` can be used to look up the name289/// // of a `FontSource`'s associated family290/// let family_name = font_system.get_family(&FontSource::Serif).unwrap();291/// assert_eq!(family_name.as_str(), "Allegro");292/// ```293pub enum FontSource {294/// Use a specific font face referenced by a [`Font`] asset handle.295///296/// If the default font handle is used, then297/// * if `default_font` feature is enabled (enabled by default in `bevy` crate),298/// `FiraMono-subset.ttf` compiled into the library is used.299/// * otherwise no text will be rendered, unless a custom font is loaded into the default font300/// handle.301Handle(Handle<Font>),302/// Resolve the font by family name using the font database.303Family(SmolStr),304/// Fonts with serifs — small decorative strokes at the ends of letterforms.305///306/// Serif fonts are typically used for long passages of text and represent307/// a more traditional or formal typographic style.308Serif,309/// Fonts without serifs.310///311/// Sans-serif fonts generally have low stroke contrast and plain stroke312/// endings, making them common for UI text and on-screen reading.313SansSerif,314/// Fonts that use a cursive or handwritten style.315///316/// Glyphs often resemble connected or flowing pen or brush strokes rather317/// than printed letterforms.318Cursive,319/// Decorative or expressive fonts.320///321/// Fantasy fonts are primarily intended for display purposes and may322/// prioritize visual style over readability.323Fantasy,324/// Fonts in which all glyphs have the same fixed advance width.325///326/// Monospace fonts are commonly used for code, tabular data, and text327/// where vertical alignment is important.328Monospace,329}330331impl FontSource {332/// Returns this `FontSource` as a `fontdb` family, or `None`333/// if this source is a `Handle`.334pub(crate) fn as_family<'a>(&'a self) -> Option<Family<'a>> {335Some(match self {336FontSource::Family(family) => Family::Name(family.as_str()),337FontSource::Serif => Family::Serif,338FontSource::SansSerif => Family::SansSerif,339FontSource::Cursive => Family::Cursive,340FontSource::Fantasy => Family::Fantasy,341FontSource::Monospace => Family::Monospace,342_ => return None,343})344}345}346347impl Default for FontSource {348fn default() -> Self {349Self::Handle(Handle::default())350}351}352353impl From<Handle<Font>> for FontSource {354fn from(handle: Handle<Font>) -> Self {355Self::Handle(handle)356}357}358359impl From<&Handle<Font>> for FontSource {360fn from(handle: &Handle<Font>) -> Self {361Self::Handle(handle.clone())362}363}364365impl From<SmolStr> for FontSource {366fn from(family: SmolStr) -> Self {367FontSource::Family(family)368}369}370371impl From<&str> for FontSource {372fn from(family: &str) -> Self {373FontSource::Family(family.into())374}375}376377/// `TextFont` determines the style of a text span within a [`ComputedTextBlock`], specifically378/// the font face, the font size, the line height, and the antialiasing method.379#[derive(Component, Clone, Debug, Reflect, PartialEq)]380#[reflect(Component, Default, Debug, Clone)]381pub struct TextFont {382/// Specifies the font face used for this text section.383///384/// A `FontSource` can be a handle to a font asset, a font family name,385/// or a generic font category that is resolved using Cosmic Text's font database.386pub font: FontSource,387/// The vertical height of rasterized glyphs in the font atlas in pixels.388///389/// This is multiplied by the window scale factor and `UiScale`, but not the text entity's390/// transform or camera projection. Then, the scaled font size is rounded to the nearest pixel391/// to produce the final font size used during glyph layout.392///393/// A new font atlas is generated for every combination of font handle and scaled font size394/// which can have a strong performance impact.395pub font_size: FontSize,396/// How thick or bold the strokes of a font appear.397///398/// Font weights can be any value between 1 and 1000, inclusive.399///400/// Only supports variable weight fonts.401pub weight: FontWeight,402/// How condensed or expanded the glyphs appear horizontally.403pub width: FontWidth,404/// The slant style of a font face: normal, italic, or oblique.405pub style: FontStyle,406/// The antialiasing method to use when rendering text.407pub font_smoothing: FontSmoothing,408/// OpenType features for .otf fonts that support them.409pub font_features: FontFeatures,410}411412impl TextFont {413/// Returns a new [`TextFont`] with the specified font size.414pub fn from_font_size(font_size: impl Into<FontSize>) -> Self {415Self::default().with_font_size(font_size)416}417418/// Returns this [`TextFont`] with the specified font face handle.419pub fn with_font(mut self, font: Handle<Font>) -> Self {420self.font = FontSource::Handle(font);421self422}423424/// Returns this [`TextFont`] with the specified font family.425pub fn with_family(mut self, family: impl Into<SmolStr>) -> Self {426self.font = FontSource::Family(family.into());427self428}429430/// Returns this [`TextFont`] with the specified font size.431pub fn with_font_size(mut self, font_size: impl Into<FontSize>) -> Self {432self.font_size = font_size.into();433self434}435436/// Returns this [`TextFont`] with the specified [`FontSmoothing`].437pub const fn with_font_smoothing(mut self, font_smoothing: FontSmoothing) -> Self {438self.font_smoothing = font_smoothing;439self440}441}442443impl<T: Into<FontSource>> From<T> for TextFont {444fn from(source: T) -> Self {445Self {446font: source.into(),447..default()448}449}450}451452impl Default for TextFont {453fn default() -> Self {454Self {455font: Default::default(),456font_size: FontSize::from(20.),457style: FontStyle::Normal,458weight: FontWeight::NORMAL,459width: FontWidth::NORMAL,460font_features: FontFeatures::default(),461font_smoothing: Default::default(),462}463}464}465466/// The vertical height of rasterized glyphs in the font atlas in pixels.467///468/// This is multiplied by the scale factor, but not the text entity469/// transform or camera projection.470///471/// The viewport variants are not supported by `Text2d`.472///473/// A new font atlas is generated for every combination of font handle and scaled font size474/// which can have a strong performance impact.475#[derive(Component, Copy, Clone, Debug, Reflect)]476pub enum FontSize {477/// Font Size in logical pixels.478Px(f32),479/// Font size as a percentage of the viewport width.480Vw(f32),481/// Font size as a percentage of the viewport height.482Vh(f32),483/// Font size as a percentage of the smaller of the viewport width and height.484VMin(f32),485/// Font size as a percentage of the larger of the viewport width and height.486VMax(f32),487/// Font Size relative to the value of the `RemSize` resource.488Rem(f32),489}490491impl FontSize {492/// Evaluate the font size to a value in logical pixels493pub fn eval(494self,495// Viewport size in logical pixels496logical_viewport_size: Vec2,497// Base Rem size in logical pixels498rem_size: f32,499) -> f32 {500match self {501FontSize::Px(s) => s,502FontSize::Vw(s) => logical_viewport_size.x * s / 100.,503FontSize::Vh(s) => logical_viewport_size.y * s / 100.,504FontSize::VMin(s) => logical_viewport_size.min_element() * s / 100.,505FontSize::VMax(s) => logical_viewport_size.max_element() * s / 100.,506FontSize::Rem(s) => rem_size * s,507}508}509}510511impl PartialEq for FontSize {512fn eq(&self, other: &Self) -> bool {513match (*self, *other) {514(Self::Px(l), Self::Px(r))515| (Self::Vw(l), Self::Vw(r))516| (Self::Vh(l), Self::Vh(r))517| (Self::VMin(l), Self::VMin(r))518| (Self::VMax(l), Self::VMax(r))519| (Self::Rem(l), Self::Rem(r)) => l == r,520_ => false,521}522}523}524525impl core::ops::Mul<f32> for FontSize {526type Output = FontSize;527528fn mul(self, rhs: f32) -> Self::Output {529match self {530FontSize::Px(v) => FontSize::Px(v * rhs),531FontSize::Vw(v) => FontSize::Vw(v * rhs),532FontSize::Vh(v) => FontSize::Vh(v * rhs),533FontSize::VMin(v) => FontSize::VMin(v * rhs),534FontSize::VMax(v) => FontSize::VMax(v * rhs),535FontSize::Rem(v) => FontSize::Rem(v * rhs),536}537}538}539540impl core::ops::Mul<FontSize> for f32 {541type Output = FontSize;542543fn mul(self, rhs: FontSize) -> Self::Output {544rhs * self545}546}547548impl Default for FontSize {549fn default() -> Self {550Self::Px(20.)551}552}553554impl From<f32> for FontSize {555fn from(value: f32) -> Self {556Self::Px(value)557}558}559560/// Base value used to resolve `Rem` units for font sizes.561#[derive(Resource, Copy, Clone, Debug, PartialEq, Deref, DerefMut)]562pub struct RemSize(pub f32);563564impl Default for RemSize {565fn default() -> Self {566Self(20.)567}568}569570/// How thick or bold the strokes of a font appear.571///572/// Valid font weights range from 1 to 1000, inclusive.573/// Weights above 1000 are clamped to 1000.574/// A weight of 0 is treated as [`FontWeight::DEFAULT`].575///576/// Legacy names from when most fonts weren't variable fonts577/// are included as const values, but are misleading if578/// used in documentation and examples, as valid weights579/// for variable fonts are all of the numbers from 1-1000, and580/// not all fonts which are not variable fonts have those weights581/// supplied. If you use a custom font that supplies only specific582/// weights, that will be documented where you purchased the font.583///584/// `<https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-weight>`585#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect)]586pub struct FontWeight(pub u16);587588impl FontWeight {589/// Weight 100.590pub const THIN: FontWeight = FontWeight(100);591592/// Weight 200.593pub const EXTRA_LIGHT: FontWeight = FontWeight(200);594595/// Weight 300.596pub const LIGHT: FontWeight = FontWeight(300);597598/// Weight 400.599pub const NORMAL: FontWeight = FontWeight(400);600601/// Weight 500.602pub const MEDIUM: FontWeight = FontWeight(500);603604/// Weight 600.605pub const SEMIBOLD: FontWeight = FontWeight(600);606607/// Weight 700.608pub const BOLD: FontWeight = FontWeight(700);609610/// Weight 800611pub const EXTRA_BOLD: FontWeight = FontWeight(800);612613/// Weight 900.614pub const BLACK: FontWeight = FontWeight(900);615616/// Weight 950.617pub const EXTRA_BLACK: FontWeight = FontWeight(950);618619/// The default font weight.620pub const DEFAULT: FontWeight = Self::NORMAL;621622/// Clamp the weight value to between 1 and 1000.623/// Values of 0 are mapped to `Weight::DEFAULT`.624pub const fn clamp(mut self) -> Self {625if self.0 == 0 {626self = Self::DEFAULT;627} else if 1000 < self.0 {628self.0 = 1000;629}630Self(self.0)631}632}633634impl Default for FontWeight {635fn default() -> Self {636Self::DEFAULT637}638}639640impl From<FontWeight> for cosmic_text::Weight {641fn from(value: FontWeight) -> Self {642cosmic_text::Weight(value.clamp().0)643}644}645646/// `<https://docs.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass>`647#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Reflect)]648pub struct FontWidth(u16);649650impl FontWidth {651/// 50% of normal width.652pub const ULTRA_CONDENSED: Self = Self(1);653654/// 62.5% of normal width.655pub const EXTRA_CONDENSED: Self = Self(2);656657/// 75% of normal width.658pub const CONDENSED: Self = Self(3);659660/// 87.5% of normal width.661pub const SEMI_CONDENSED: Self = Self(4);662663/// 100% of normal width. This is the default.664pub const NORMAL: Self = Self(5);665666/// 112.5% of normal width.667pub const SEMI_EXPANDED: Self = Self(6);668669/// 125% of normal width.670pub const EXPANDED: Self = Self(7);671672/// 150% of normal width.673pub const EXTRA_EXPANDED: Self = Self(8);674675/// 200% of normal width.676pub const ULTRA_EXPANDED: Self = Self(9);677}678679impl Default for FontWidth {680fn default() -> Self {681Self::NORMAL682}683}684685impl From<FontWidth> for Stretch {686fn from(value: FontWidth) -> Self {687match value.0 {6881 => Stretch::UltraCondensed,6892 => Stretch::ExtraCondensed,6903 => Stretch::Condensed,6914 => Stretch::SemiCondensed,6926 => Stretch::SemiExpanded,6937 => Stretch::Expanded,6948 => Stretch::ExtraExpanded,6959 => Stretch::UltraExpanded,696_ => Stretch::Normal,697}698}699}700701/// The slant style of a font face: normal, italic, or oblique.702#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, Reflect)]703pub enum FontStyle {704/// A face that is neither italic nor obliqued.705#[default]706Normal,707/// A form that is generally cursive in nature.708Italic,709/// A typically sloped version of the regular face.710Oblique,711}712713impl From<FontStyle> for cosmic_text::Style {714fn from(value: FontStyle) -> Self {715match value {716FontStyle::Normal => cosmic_text::Style::Normal,717FontStyle::Italic => cosmic_text::Style::Italic,718FontStyle::Oblique => cosmic_text::Style::Oblique,719}720}721}722723/// An OpenType font feature tag.724#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect)]725pub struct FontFeatureTag([u8; 4]);726727impl FontFeatureTag {728/// Replaces character combinations like fi, fl with ligatures.729pub const STANDARD_LIGATURES: FontFeatureTag = FontFeatureTag::new(b"liga");730731/// Enables ligatures based on character context.732pub const CONTEXTUAL_LIGATURES: FontFeatureTag = FontFeatureTag::new(b"clig");733734/// Enables optional ligatures for stylistic use (e.g., ct, st).735pub const DISCRETIONARY_LIGATURES: FontFeatureTag = FontFeatureTag::new(b"dlig");736737/// Adjust glyph shapes based on surrounding letters.738pub const CONTEXTUAL_ALTERNATES: FontFeatureTag = FontFeatureTag::new(b"calt");739740/// Use alternate glyph designs.741pub const STYLISTIC_ALTERNATES: FontFeatureTag = FontFeatureTag::new(b"salt");742743/// Replaces lowercase letters with small caps.744pub const SMALL_CAPS: FontFeatureTag = FontFeatureTag::new(b"smcp");745746/// Replaces uppercase letters with small caps.747pub const CAPS_TO_SMALL_CAPS: FontFeatureTag = FontFeatureTag::new(b"c2sc");748749/// Replaces characters with swash versions (often decorative).750pub const SWASH: FontFeatureTag = FontFeatureTag::new(b"swsh");751752/// Enables alternate glyphs for large sizes or titles.753pub const TITLING_ALTERNATES: FontFeatureTag = FontFeatureTag::new(b"titl");754755/// Converts numbers like 1/2 into true fractions (½).756pub const FRACTIONS: FontFeatureTag = FontFeatureTag::new(b"frac");757758/// Formats characters like 1st, 2nd properly.759pub const ORDINALS: FontFeatureTag = FontFeatureTag::new(b"ordn");760761/// Uses a slashed version of zero (0) to differentiate from O.762pub const SLASHED_ZERO: FontFeatureTag = FontFeatureTag::new(b"ordn");763764/// Replaces figures with superscript figures, e.g. for indicating footnotes.765pub const SUPERSCRIPT: FontFeatureTag = FontFeatureTag::new(b"sups");766767/// Replaces figures with subscript figures.768pub const SUBSCRIPT: FontFeatureTag = FontFeatureTag::new(b"subs");769770/// Changes numbers to "oldstyle" form, which fit better in the flow of sentences or other text.771pub const OLDSTYLE_FIGURES: FontFeatureTag = FontFeatureTag::new(b"onum");772773/// Changes numbers to "lining" form, which are better suited for standalone numbers. When774/// enabled, the bottom of all numbers will be aligned with each other.775pub const LINING_FIGURES: FontFeatureTag = FontFeatureTag::new(b"lnum");776777/// Changes numbers to be of proportional width. When enabled, numbers may have varying widths.778pub const PROPORTIONAL_FIGURES: FontFeatureTag = FontFeatureTag::new(b"pnum");779780/// Changes numbers to be of uniform (tabular) width. When enabled, all numbers will have the781/// same width.782pub const TABULAR_FIGURES: FontFeatureTag = FontFeatureTag::new(b"tnum");783784/// Varies the stroke thickness. Valid values are in the range of 1 to 1000, inclusive.785pub const WEIGHT: FontFeatureTag = FontFeatureTag::new(b"wght");786787/// Varies the width of text from narrower to wider. Must be a value greater than 0. A value of788/// 100 is typically considered standard width.789pub const WIDTH: FontFeatureTag = FontFeatureTag::new(b"wdth");790791/// Varies between upright and slanted text. Must be a value greater than -90 and less than +90.792/// A value of 0 is upright.793pub const SLANT: FontFeatureTag = FontFeatureTag::new(b"slnt");794795/// Create a new [`FontFeatureTag`] from raw bytes.796pub const fn new(src: &[u8; 4]) -> Self {797Self(*src)798}799}800801impl Debug for FontFeatureTag {802fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {803// OpenType tags are always ASCII, so this match will succeed for valid tags. This gives us804// human-readable debug output, e.g. FontFeatureTag("liga").805match from_utf8(&self.0) {806Ok(s) => write!(f, "FontFeatureTag(\"{}\")", s),807Err(_) => write!(f, "FontFeatureTag({:?})", self.0),808}809}810}811812/// OpenType features for .otf fonts that support them.813///814/// Examples features include ligatures, small-caps, and fractional number display. For the complete815/// list of OpenType features, see the spec at816/// `<https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist>`.817///818/// # Usage:819/// ```820/// use bevy_text::{FontFeatureTag, FontFeatures};821///822/// // Create using the builder823/// let font_features = FontFeatures::builder()824/// .enable(FontFeatureTag::STANDARD_LIGATURES)825/// .set(FontFeatureTag::WEIGHT, 300)826/// .build();827///828/// // Create from a list829/// let more_font_features: FontFeatures = [830/// FontFeatureTag::STANDARD_LIGATURES,831/// FontFeatureTag::OLDSTYLE_FIGURES,832/// FontFeatureTag::TABULAR_FIGURES833/// ].into();834/// ```835#[derive(Clone, Debug, Default, Reflect, PartialEq)]836pub struct FontFeatures {837features: Vec<(FontFeatureTag, u32)>,838}839840impl FontFeatures {841/// Create a new [`FontFeaturesBuilder`].842pub fn builder() -> FontFeaturesBuilder {843FontFeaturesBuilder::default()844}845}846847/// A builder for [`FontFeatures`].848#[derive(Clone, Default)]849pub struct FontFeaturesBuilder {850features: Vec<(FontFeatureTag, u32)>,851}852853impl FontFeaturesBuilder {854/// Enable an OpenType feature.855///856/// Most OpenType features are on/off switches, so this is a convenience method that sets the857/// feature's value to "1" (enabled). For non-boolean features, see [`FontFeaturesBuilder::set`].858pub fn enable(self, feature_tag: FontFeatureTag) -> Self {859self.set(feature_tag, 1)860}861862/// Set an OpenType feature to a specific value.863///864/// For most features, the [`FontFeaturesBuilder::enable`] method should be used instead. A few865/// features, such as "wght", take numeric values, so this method may be used for these cases.866pub fn set(mut self, feature_tag: FontFeatureTag, value: u32) -> Self {867self.features.push((feature_tag, value));868self869}870871/// Build a [`FontFeatures`] from the values set within this builder.872pub fn build(self) -> FontFeatures {873FontFeatures {874features: self.features,875}876}877}878879/// Allow [`FontFeatures`] to be built from a list. This is suitable for the standard case when each880/// listed feature is a boolean type. If any features require a numeric value (like "wght"), use881/// [`FontFeaturesBuilder`] instead.882impl<T> From<T> for FontFeatures883where884T: IntoIterator<Item = FontFeatureTag>,885{886fn from(value: T) -> Self {887FontFeatures {888features: value.into_iter().map(|x| (x, 1)).collect(),889}890}891}892893impl From<&FontFeatures> for cosmic_text::FontFeatures {894fn from(font_features: &FontFeatures) -> Self {895cosmic_text::FontFeatures {896features: font_features897.features898.iter()899.map(|(tag, value)| cosmic_text::Feature {900tag: cosmic_text::FeatureTag::new(&tag.0),901value: *value,902})903.collect(),904}905}906}907908/// Specifies the height of each line of text for `Text` and `Text2d`909///910/// Default is 1.2x the font size911#[derive(Component, Debug, Clone, Copy, PartialEq, Reflect)]912#[reflect(Component, Debug, Clone, PartialEq)]913pub enum LineHeight {914/// Set line height to a specific number of pixels915Px(f32),916/// Set line height to a multiple of the font size917RelativeToFont(f32),918}919920impl Default for LineHeight {921fn default() -> Self {922LineHeight::RelativeToFont(1.2)923}924}925926/// The color of the text for this section.927#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)]928#[reflect(Component, Default, Debug, PartialEq, Clone)]929pub struct TextColor(pub Color);930931impl Default for TextColor {932fn default() -> Self {933Self::WHITE934}935}936937impl<T: Into<Color>> From<T> for TextColor {938fn from(color: T) -> Self {939Self(color.into())940}941}942943impl TextColor {944/// Black colored text945pub const BLACK: Self = TextColor(Color::BLACK);946/// White colored text947pub const WHITE: Self = TextColor(Color::WHITE);948}949950/// The background color of the text for this section.951#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)]952#[reflect(Component, Default, Debug, PartialEq, Clone)]953pub struct TextBackgroundColor(pub Color);954955impl Default for TextBackgroundColor {956fn default() -> Self {957Self(Color::BLACK)958}959}960961impl<T: Into<Color>> From<T> for TextBackgroundColor {962fn from(color: T) -> Self {963Self(color.into())964}965}966967impl TextBackgroundColor {968/// Black background969pub const BLACK: Self = TextBackgroundColor(Color::BLACK);970/// White background971pub const WHITE: Self = TextBackgroundColor(Color::WHITE);972}973974/// Determines how lines will be broken when preventing text from running out of bounds.975#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Reflect, Serialize, Deserialize)]976#[reflect(Serialize, Deserialize, Clone, PartialEq, Hash, Default)]977pub enum LineBreak {978/// Uses the [Unicode Line Breaking Algorithm](https://www.unicode.org/reports/tr14/).979/// Lines will be broken up at the nearest suitable word boundary, usually a space.980/// This behavior suits most cases, as it keeps words intact across linebreaks.981#[default]982WordBoundary,983/// Lines will be broken without discrimination on any character that would leave bounds.984/// This is closer to the behavior one might expect from text in a terminal.985/// However it may lead to words being broken up across linebreaks.986AnyCharacter,987/// Wraps at the word level, or fallback to character level if a word can’t fit on a line by itself988WordOrCharacter,989/// No soft wrapping, where text is automatically broken up into separate lines when it overflows a boundary, will ever occur.990/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, is still enabled.991NoWrap,992}993994/// A text entity with this component is drawn with strikethrough.995#[derive(Component, Copy, Clone, Debug, Reflect, Default, Serialize, Deserialize)]996#[reflect(Serialize, Deserialize, Clone, Default)]997pub struct Strikethrough;998999/// Color for the text's strikethrough. If this component is not present, its `TextColor` will be used.1000#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)]1001#[reflect(Component, Default, Debug, PartialEq, Clone)]1002pub struct StrikethroughColor(pub Color);10031004impl Default for StrikethroughColor {1005fn default() -> Self {1006Self(Color::WHITE)1007}1008}10091010impl<T: Into<Color>> From<T> for StrikethroughColor {1011fn from(color: T) -> Self {1012Self(color.into())1013}1014}10151016/// Add to a text entity to draw its text with underline.1017#[derive(Component, Copy, Clone, Debug, Reflect, Default, Serialize, Deserialize)]1018#[reflect(Serialize, Deserialize, Clone, Default)]1019pub struct Underline;10201021/// Color for the text's underline. If this component is not present, its `TextColor` will be used.1022#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)]1023#[reflect(Component, Default, Debug, PartialEq, Clone)]1024pub struct UnderlineColor(pub Color);10251026impl Default for UnderlineColor {1027fn default() -> Self {1028Self(Color::WHITE)1029}1030}10311032impl<T: Into<Color>> From<T> for UnderlineColor {1033fn from(color: T) -> Self {1034Self(color.into())1035}1036}10371038/// Determines which antialiasing method to use when rendering text. By default, text is1039/// rendered with grayscale antialiasing, but this can be changed to achieve a pixelated look.1040///1041/// **Note:** Subpixel antialiasing is not currently supported.1042#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Reflect, Serialize, Deserialize)]1043#[reflect(Serialize, Deserialize, Clone, PartialEq, Hash, Default)]1044#[doc(alias = "antialiasing")]1045#[doc(alias = "pixelated")]1046pub enum FontSmoothing {1047/// No antialiasing. Useful for when you want to render text with a pixel art aesthetic.1048///1049/// Combine this with `UiAntiAlias::Off` and `Msaa::Off` on your 2D camera for a fully pixelated look.1050///1051/// **Note:** Due to limitations of the underlying text rendering library,1052/// this may require specially-crafted pixel fonts to look good, especially at small sizes.1053None,1054/// The default grayscale antialiasing. Produces text that looks smooth,1055/// even at small font sizes and low resolutions with modern vector fonts.1056#[default]1057AntiAliased,1058// TODO: Add subpixel antialias support1059// SubpixelAntiAliased,1060}10611062/// System that detects changes to text blocks and sets `ComputedTextBlock::should_rerender`.1063///1064/// Generic over the root text component and text span component. For example, `Text2d`/[`TextSpan`] for1065/// 2d or `Text`/[`TextSpan`] for UI.1066pub fn detect_text_needs_rerender<Root: Component>(1067changed_roots: Query<1068Entity,1069(1070Or<(1071Changed<Root>,1072Changed<TextFont>,1073Changed<TextLayout>,1074Changed<LineHeight>,1075Changed<Children>,1076)>,1077With<Root>,1078With<TextFont>,1079With<TextLayout>,1080),1081>,1082changed_spans: Query<1083(Entity, Option<&ChildOf>, Has<TextLayout>),1084(1085Or<(1086Changed<TextSpan>,1087Changed<TextFont>,1088Changed<LineHeight>,1089Changed<Children>,1090Changed<ChildOf>, // Included to detect broken text block hierarchies.1091Added<TextLayout>,1092)>,1093With<TextSpan>,1094With<TextFont>,1095),1096>,1097mut computed: Query<(1098Option<&ChildOf>,1099Option<&mut ComputedTextBlock>,1100Has<TextSpan>,1101)>,1102) {1103// Root entity:1104// - Root component changed.1105// - TextFont on root changed.1106// - TextLayout changed.1107// - Root children changed (can include additions and removals).1108for root in changed_roots.iter() {1109let Ok((_, Some(mut computed), _)) = computed.get_mut(root) else {1110once!(warn!("found entity {} with a root text component ({}) but no ComputedTextBlock; this warning only \1111prints once", root, core::any::type_name::<Root>()));1112continue;1113};1114computed.needs_rerender = true;1115}11161117// Span entity:1118// - Span component changed.1119// - Span TextFont changed.1120// - Span children changed (can include additions and removals).1121for (entity, maybe_span_child_of, has_text_block) in changed_spans.iter() {1122if has_text_block {1123once!(warn!("found entity {} with a TextSpan that has a TextLayout, which should only be on root \1124text entities (that have {}); this warning only prints once",1125entity, core::any::type_name::<Root>()));1126}11271128let Some(span_child_of) = maybe_span_child_of else {1129once!(warn!(1130"found entity {} with a TextSpan that has no parent; it should have an ancestor \1131with a root text component ({}); this warning only prints once",1132entity,1133core::any::type_name::<Root>()1134));1135continue;1136};1137let mut parent: Entity = span_child_of.parent();11381139// Search for the nearest ancestor with ComputedTextBlock.1140// Note: We assume the perf cost from duplicate visits in the case that multiple spans in a block are visited1141// is outweighed by the expense of tracking visited spans.1142loop {1143let Ok((maybe_child_of, maybe_computed, has_span)) = computed.get_mut(parent) else {1144once!(warn!("found entity {} with a TextSpan that is part of a broken hierarchy with a ChildOf \1145component that points at non-existent entity {}; this warning only prints once",1146entity, parent));1147break;1148};1149if let Some(mut computed) = maybe_computed {1150computed.needs_rerender = true;1151break;1152}1153if !has_span {1154once!(warn!("found entity {} with a TextSpan that has an ancestor ({}) that does not have a text \1155span component or a ComputedTextBlock component; this warning only prints once",1156entity, parent));1157break;1158}1159let Some(next_child_of) = maybe_child_of else {1160once!(warn!(1161"found entity {} with a TextSpan that has no ancestor with the root text \1162component ({}); this warning only prints once",1163entity,1164core::any::type_name::<Root>()1165));1166break;1167};1168parent = next_child_of.parent();1169}1170}1171}11721173#[derive(Component, Debug, Copy, Clone, Default, Reflect, PartialEq)]1174#[reflect(Component, Default, Debug, Clone, PartialEq)]1175/// Font hinting strategy.1176///1177/// The text bounds can underflow or overflow slightly with `FontHinting::Enabled`.1178///1179/// <https://docs.rs/cosmic-text/latest/cosmic_text/enum.Hinting.html>1180pub enum FontHinting {1181#[default]1182/// Glyphs will have subpixel coordinates.1183Disabled,1184/// Glyphs will be snapped to integral coordinates in the X-axis during layout.1185///1186/// The text bounds can underflow or overflow slightly with this enabled.1187Enabled,1188}11891190impl From<FontHinting> for cosmic_text::Hinting {1191fn from(value: FontHinting) -> Self {1192match value {1193FontHinting::Disabled => cosmic_text::Hinting::Disabled,1194FontHinting::Enabled => cosmic_text::Hinting::Enabled,1195}1196}1197}119811991200