Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_text/src/text.rs
9334 views
1
use crate::{Font, TextLayoutInfo, TextSpanAccess, TextSpanComponent};
2
use bevy_asset::Handle;
3
use bevy_color::Color;
4
use bevy_derive::{Deref, DerefMut};
5
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
6
use bevy_math::Vec2;
7
use bevy_reflect::prelude::*;
8
use bevy_utils::{default, once};
9
use core::fmt::{Debug, Formatter};
10
use core::str::from_utf8;
11
use cosmic_text::{Buffer, Family, Metrics, Stretch};
12
use serde::{Deserialize, Serialize};
13
use smallvec::SmallVec;
14
use smol_str::SmolStr;
15
use tracing::warn;
16
17
/// Wrapper for [`cosmic_text::Buffer`]
18
#[derive(Deref, DerefMut, Debug, Clone)]
19
pub struct CosmicBuffer(pub Buffer);
20
21
impl Default for CosmicBuffer {
22
fn default() -> Self {
23
Self(Buffer::new_empty(Metrics::new(20.0, 20.0)))
24
}
25
}
26
27
/// A sub-entity of a [`ComputedTextBlock`].
28
///
29
/// Returned by [`ComputedTextBlock::entities`].
30
#[derive(Debug, Copy, Clone, Reflect)]
31
#[reflect(Debug, Clone)]
32
pub struct TextEntity {
33
/// The entity.
34
pub entity: Entity,
35
/// Records the hierarchy depth of the entity within a `TextLayout`.
36
pub depth: usize,
37
/// Antialiasing method to use when rendering the text.
38
pub font_smoothing: FontSmoothing,
39
}
40
41
/// Computed information for a text block.
42
///
43
/// See [`TextLayout`].
44
///
45
/// Automatically updated by 2d and UI text systems.
46
#[derive(Component, Debug, Clone, Reflect)]
47
#[reflect(Component, Debug, Default, Clone)]
48
pub struct ComputedTextBlock {
49
/// Buffer for managing text layout and creating [`TextLayoutInfo`].
50
///
51
/// This is private because buffer contents are always refreshed from ECS state when writing glyphs to
52
/// `TextLayoutInfo`. If you want to control the buffer contents manually or use the `cosmic-text`
53
/// editor, then you need to not use `TextLayout` and instead manually implement the conversion to
54
/// `TextLayoutInfo`.
55
#[reflect(ignore, clone)]
56
pub(crate) buffer: CosmicBuffer,
57
/// Entities for all text spans in the block, including the root-level text.
58
///
59
/// The [`TextEntity::depth`] field can be used to reconstruct the hierarchy.
60
pub(crate) entities: SmallVec<[TextEntity; 1]>,
61
/// Flag set when any change has been made to this block that should cause it to be rerendered.
62
///
63
/// Includes:
64
/// - [`TextLayout`] changes.
65
/// - [`TextFont`] or `Text2d`/`Text`/`TextSpan` changes anywhere in the block's entity hierarchy.
66
// TODO: This encompasses both structural changes like font size or justification and non-structural
67
// changes like text color and font smoothing. This field currently causes UI to 'remeasure' text, even if
68
// the actual changes are non-structural and can be handled by only rerendering and not remeasuring. A full
69
// solution would probably require splitting TextLayout and TextFont into structural/non-structural
70
// components for more granular change detection. A cost/benefit analysis is needed.
71
pub(crate) needs_rerender: bool,
72
// Flag set by `TextPipeline::update_buffer` if any text section in the block has a viewport font size value.
73
//
74
// Used by dependents to determine if they should update a text block on changes to
75
// the viewport size.
76
pub(crate) uses_viewport_sizes: bool,
77
// Flag set by `TextPipeline::update_buffer` if any text section in the block has a rem font size value.
78
//
79
// Used by dependents to determine if they should update a text block on changes to
80
// the rem size.
81
pub(crate) uses_rem_sizes: bool,
82
}
83
84
impl ComputedTextBlock {
85
/// Accesses entities in this block.
86
///
87
/// Can be used to look up [`TextFont`] components for glyphs in [`TextLayoutInfo`] using the `span_index`
88
/// stored there.
89
pub fn entities(&self) -> &[TextEntity] {
90
&self.entities
91
}
92
93
/// Indicates if the text needs to be refreshed in [`TextLayoutInfo`].
94
///
95
/// Updated automatically by [`detect_text_needs_rerender`] and cleared
96
/// by [`TextPipeline`](crate::TextPipeline) methods.
97
pub fn needs_rerender(
98
&self,
99
is_viewport_size_changed: bool,
100
is_rem_size_changed: bool,
101
) -> bool {
102
self.needs_rerender
103
|| (is_viewport_size_changed && self.uses_viewport_sizes)
104
|| (is_rem_size_changed && self.uses_rem_sizes)
105
}
106
/// Accesses the underlying buffer which can be used for `cosmic-text` APIs such as accessing layout information
107
/// or calculating a cursor position.
108
///
109
/// Mutable access is not offered because changes would be overwritten during the automated layout calculation.
110
/// If you want to control the buffer contents manually or use the `cosmic-text`
111
/// editor, then you need to not use `TextLayout` and instead manually implement the conversion to
112
/// `TextLayoutInfo`.
113
pub fn buffer(&self) -> &CosmicBuffer {
114
&self.buffer
115
}
116
}
117
118
impl Default for ComputedTextBlock {
119
fn default() -> Self {
120
Self {
121
buffer: CosmicBuffer::default(),
122
entities: SmallVec::default(),
123
needs_rerender: true,
124
uses_rem_sizes: false,
125
uses_viewport_sizes: false,
126
}
127
}
128
}
129
130
/// Component with text format settings for a block of text.
131
///
132
/// A block of text is composed of text spans, which each have a separate string value and [`TextFont`]. Text
133
/// spans associated with a text block are collected into [`ComputedTextBlock`] for layout, and then inserted
134
/// to [`TextLayoutInfo`] for rendering.
135
///
136
/// See `Text2d` in `bevy_sprite` for the core component of 2d text, and `Text` in `bevy_ui` for UI text.
137
#[derive(Component, Debug, Copy, Clone, Default, Reflect)]
138
#[reflect(Component, Default, Debug, Clone)]
139
#[require(ComputedTextBlock, TextLayoutInfo)]
140
pub struct TextLayout {
141
/// The text's internal alignment.
142
/// Should not affect its position within a container.
143
pub justify: Justify,
144
/// How the text should linebreak when running out of the bounds determined by `max_size`.
145
pub linebreak: LineBreak,
146
}
147
148
impl TextLayout {
149
/// Makes a new [`TextLayout`].
150
pub const fn new(justify: Justify, linebreak: LineBreak) -> Self {
151
Self { justify, linebreak }
152
}
153
154
/// Makes a new [`TextLayout`] with the specified [`Justify`].
155
pub fn new_with_justify(justify: Justify) -> Self {
156
Self::default().with_justify(justify)
157
}
158
159
/// Makes a new [`TextLayout`] with the specified [`LineBreak`].
160
pub fn new_with_linebreak(linebreak: LineBreak) -> Self {
161
Self::default().with_linebreak(linebreak)
162
}
163
164
/// Makes a new [`TextLayout`] with soft wrapping disabled.
165
/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.
166
pub fn new_with_no_wrap() -> Self {
167
Self::default().with_no_wrap()
168
}
169
170
/// Returns this [`TextLayout`] with the specified [`Justify`].
171
pub const fn with_justify(mut self, justify: Justify) -> Self {
172
self.justify = justify;
173
self
174
}
175
176
/// Returns this [`TextLayout`] with the specified [`LineBreak`].
177
pub const fn with_linebreak(mut self, linebreak: LineBreak) -> Self {
178
self.linebreak = linebreak;
179
self
180
}
181
182
/// Returns this [`TextLayout`] with soft wrapping disabled.
183
/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, will still occur.
184
pub const fn with_no_wrap(mut self) -> Self {
185
self.linebreak = LineBreak::NoWrap;
186
self
187
}
188
}
189
190
/// A span of text in a tree of spans.
191
///
192
/// A `TextSpan` is only valid when it exists as a child of a parent that has either `Text` or
193
/// `Text2d`. The parent's `Text` / `Text2d` component contains the base text content. Any children
194
/// with `TextSpan` extend this text by appending their content to the parent's text in sequence to
195
/// form a [`ComputedTextBlock`]. The parent's [`TextLayout`] determines the layout of the block
196
/// but each node has its own [`TextFont`] and [`TextColor`].
197
#[derive(Component, Debug, Default, Clone, Deref, DerefMut, Reflect)]
198
#[reflect(Component, Default, Debug, Clone)]
199
#[require(TextFont, TextColor, LineHeight)]
200
pub struct TextSpan(pub String);
201
202
impl TextSpan {
203
/// Makes a new text span component.
204
pub fn new(text: impl Into<String>) -> Self {
205
Self(text.into())
206
}
207
}
208
209
impl TextSpanComponent for TextSpan {}
210
211
impl TextSpanAccess for TextSpan {
212
fn read_span(&self) -> &str {
213
self.as_str()
214
}
215
fn write_span(&mut self) -> &mut String {
216
&mut *self
217
}
218
}
219
220
impl From<&str> for TextSpan {
221
fn from(value: &str) -> Self {
222
Self(String::from(value))
223
}
224
}
225
226
impl From<String> for TextSpan {
227
fn from(value: String) -> Self {
228
Self(value)
229
}
230
}
231
232
/// Describes the horizontal alignment of multiple lines of text relative to each other.
233
///
234
/// This only affects the internal positioning of the lines of text within a text entity and
235
/// does not affect the text entity's position.
236
///
237
/// _Has no affect on a single line text entity_, unless used together with a
238
/// [`TextBounds`](super::bounds::TextBounds) component with an explicit `width` value.
239
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
240
#[reflect(Serialize, Deserialize, Clone, PartialEq, Hash)]
241
#[doc(alias = "JustifyText")]
242
pub enum Justify {
243
/// Leftmost character is immediately to the right of the render position.
244
/// Bounds start from the render position and advance rightwards.
245
#[default]
246
Left,
247
/// Leftmost & rightmost characters are equidistant to the render position.
248
/// Bounds start from the render position and advance equally left & right.
249
Center,
250
/// Rightmost character is immediately to the left of the render position.
251
/// Bounds start from the render position and advance leftwards.
252
Right,
253
/// Words are spaced so that leftmost & rightmost characters
254
/// align with their margins.
255
/// Bounds start from the render position and advance equally left & right.
256
Justified,
257
}
258
259
impl From<Justify> for cosmic_text::Align {
260
fn from(justify: Justify) -> Self {
261
match justify {
262
Justify::Left => cosmic_text::Align::Left,
263
Justify::Center => cosmic_text::Align::Center,
264
Justify::Right => cosmic_text::Align::Right,
265
Justify::Justified => cosmic_text::Align::Justified,
266
}
267
}
268
}
269
270
#[derive(Clone, Debug, Reflect, PartialEq)]
271
/// Determines how the font face for a text sections is selected.
272
///
273
/// A `FontSource` can be a handle to a font asset, a font family name,
274
/// or a generic font category that is resolved using Cosmic Text's font database.
275
///
276
/// The `CosmicFontSystem` resource can be used to change the font family
277
/// associated to a generic font variant:
278
/// ```
279
/// # use bevy_text::CosmicFontSystem;
280
/// # use bevy_text::FontSource;
281
/// let mut font_system = CosmicFontSystem::default();
282
/// let mut font_database = font_system.db_mut();
283
/// font_database.set_serif_family("Allegro");
284
/// font_database.set_sans_serif_family("Encode Sans");
285
/// font_database.set_cursive_family("Cedarville Cursive");
286
/// font_database.set_fantasy_family("Argusho");
287
/// font_database.set_monospace_family("Lucida Console");
288
///
289
/// // `CosmicFontSystem::get_family` can be used to look up the name
290
/// // of a `FontSource`'s associated family
291
/// let family_name = font_system.get_family(&FontSource::Serif).unwrap();
292
/// assert_eq!(family_name.as_str(), "Allegro");
293
/// ```
294
pub enum FontSource {
295
/// Use a specific font face referenced by a [`Font`] asset handle.
296
///
297
/// If the default font handle is used, then
298
/// * if `default_font` feature is enabled (enabled by default in `bevy` crate),
299
/// `FiraMono-subset.ttf` compiled into the library is used.
300
/// * otherwise no text will be rendered, unless a custom font is loaded into the default font
301
/// handle.
302
Handle(Handle<Font>),
303
/// Resolve the font by family name using the font database.
304
Family(SmolStr),
305
/// Fonts with serifs — small decorative strokes at the ends of letterforms.
306
///
307
/// Serif fonts are typically used for long passages of text and represent
308
/// a more traditional or formal typographic style.
309
Serif,
310
/// Fonts without serifs.
311
///
312
/// Sans-serif fonts generally have low stroke contrast and plain stroke
313
/// endings, making them common for UI text and on-screen reading.
314
SansSerif,
315
/// Fonts that use a cursive or handwritten style.
316
///
317
/// Glyphs often resemble connected or flowing pen or brush strokes rather
318
/// than printed letterforms.
319
Cursive,
320
/// Decorative or expressive fonts.
321
///
322
/// Fantasy fonts are primarily intended for display purposes and may
323
/// prioritize visual style over readability.
324
Fantasy,
325
/// Fonts in which all glyphs have the same fixed advance width.
326
///
327
/// Monospace fonts are commonly used for code, tabular data, and text
328
/// where vertical alignment is important.
329
Monospace,
330
}
331
332
impl FontSource {
333
/// Returns this `FontSource` as a `fontdb` family, or `None`
334
/// if this source is a `Handle`.
335
pub(crate) fn as_family<'a>(&'a self) -> Option<Family<'a>> {
336
Some(match self {
337
FontSource::Family(family) => Family::Name(family.as_str()),
338
FontSource::Serif => Family::Serif,
339
FontSource::SansSerif => Family::SansSerif,
340
FontSource::Cursive => Family::Cursive,
341
FontSource::Fantasy => Family::Fantasy,
342
FontSource::Monospace => Family::Monospace,
343
_ => return None,
344
})
345
}
346
}
347
348
impl Default for FontSource {
349
fn default() -> Self {
350
Self::Handle(Handle::default())
351
}
352
}
353
354
impl From<Handle<Font>> for FontSource {
355
fn from(handle: Handle<Font>) -> Self {
356
Self::Handle(handle)
357
}
358
}
359
360
impl From<&Handle<Font>> for FontSource {
361
fn from(handle: &Handle<Font>) -> Self {
362
Self::Handle(handle.clone())
363
}
364
}
365
366
impl From<SmolStr> for FontSource {
367
fn from(family: SmolStr) -> Self {
368
FontSource::Family(family)
369
}
370
}
371
372
impl From<&str> for FontSource {
373
fn from(family: &str) -> Self {
374
FontSource::Family(family.into())
375
}
376
}
377
378
/// `TextFont` determines the style of a text span within a [`ComputedTextBlock`], specifically
379
/// the font face, the font size, the line height, and the antialiasing method.
380
#[derive(Component, Clone, Debug, Reflect, PartialEq)]
381
#[reflect(Component, Default, Debug, Clone)]
382
pub struct TextFont {
383
/// Specifies the font face used for this text section.
384
///
385
/// A `FontSource` can be a handle to a font asset, a font family name,
386
/// or a generic font category that is resolved using Cosmic Text's font database.
387
pub font: FontSource,
388
/// The vertical height of rasterized glyphs in the font atlas in pixels.
389
///
390
/// This is multiplied by the window scale factor and `UiScale`, but not the text entity's
391
/// transform or camera projection. Then, the scaled font size is rounded to the nearest pixel
392
/// to produce the final font size used during glyph layout.
393
///
394
/// A new font atlas is generated for every combination of font handle and scaled font size
395
/// which can have a strong performance impact.
396
pub font_size: FontSize,
397
/// How thick or bold the strokes of a font appear.
398
///
399
/// Font weights can be any value between 1 and 1000, inclusive.
400
///
401
/// Only supports variable weight fonts.
402
pub weight: FontWeight,
403
/// How condensed or expanded the glyphs appear horizontally.
404
pub width: FontWidth,
405
/// The slant style of a font face: normal, italic, or oblique.
406
pub style: FontStyle,
407
/// The antialiasing method to use when rendering text.
408
pub font_smoothing: FontSmoothing,
409
/// OpenType features for .otf fonts that support them.
410
pub font_features: FontFeatures,
411
}
412
413
impl TextFont {
414
/// Returns a new [`TextFont`] with the specified font size.
415
pub fn from_font_size(font_size: impl Into<FontSize>) -> Self {
416
Self::default().with_font_size(font_size)
417
}
418
419
/// Returns this [`TextFont`] with the specified font face handle.
420
pub fn with_font(mut self, font: Handle<Font>) -> Self {
421
self.font = FontSource::Handle(font);
422
self
423
}
424
425
/// Returns this [`TextFont`] with the specified font family.
426
pub fn with_family(mut self, family: impl Into<SmolStr>) -> Self {
427
self.font = FontSource::Family(family.into());
428
self
429
}
430
431
/// Returns this [`TextFont`] with the specified font size.
432
pub fn with_font_size(mut self, font_size: impl Into<FontSize>) -> Self {
433
self.font_size = font_size.into();
434
self
435
}
436
437
/// Returns this [`TextFont`] with the specified [`FontSmoothing`].
438
pub const fn with_font_smoothing(mut self, font_smoothing: FontSmoothing) -> Self {
439
self.font_smoothing = font_smoothing;
440
self
441
}
442
}
443
444
impl<T: Into<FontSource>> From<T> for TextFont {
445
fn from(source: T) -> Self {
446
Self {
447
font: source.into(),
448
..default()
449
}
450
}
451
}
452
453
impl Default for TextFont {
454
fn default() -> Self {
455
Self {
456
font: Default::default(),
457
font_size: FontSize::from(20.),
458
style: FontStyle::Normal,
459
weight: FontWeight::NORMAL,
460
width: FontWidth::NORMAL,
461
font_features: FontFeatures::default(),
462
font_smoothing: Default::default(),
463
}
464
}
465
}
466
467
/// The vertical height of rasterized glyphs in the font atlas in pixels.
468
///
469
/// This is multiplied by the scale factor, but not the text entity
470
/// transform or camera projection.
471
///
472
/// The viewport variants are not supported by `Text2d`.
473
///
474
/// A new font atlas is generated for every combination of font handle and scaled font size
475
/// which can have a strong performance impact.
476
#[derive(Component, Copy, Clone, Debug, Reflect)]
477
pub enum FontSize {
478
/// Font Size in logical pixels.
479
Px(f32),
480
/// Font size as a percentage of the viewport width.
481
Vw(f32),
482
/// Font size as a percentage of the viewport height.
483
Vh(f32),
484
/// Font size as a percentage of the smaller of the viewport width and height.
485
VMin(f32),
486
/// Font size as a percentage of the larger of the viewport width and height.
487
VMax(f32),
488
/// Font Size relative to the value of the `RemSize` resource.
489
Rem(f32),
490
}
491
492
impl FontSize {
493
/// Evaluate the font size to a value in logical pixels
494
pub fn eval(
495
self,
496
// Viewport size in logical pixels
497
logical_viewport_size: Vec2,
498
// Base Rem size in logical pixels
499
rem_size: f32,
500
) -> f32 {
501
match self {
502
FontSize::Px(s) => s,
503
FontSize::Vw(s) => logical_viewport_size.x * s / 100.,
504
FontSize::Vh(s) => logical_viewport_size.y * s / 100.,
505
FontSize::VMin(s) => logical_viewport_size.min_element() * s / 100.,
506
FontSize::VMax(s) => logical_viewport_size.max_element() * s / 100.,
507
FontSize::Rem(s) => rem_size * s,
508
}
509
}
510
}
511
512
impl PartialEq for FontSize {
513
fn eq(&self, other: &Self) -> bool {
514
match (*self, *other) {
515
(Self::Px(l), Self::Px(r))
516
| (Self::Vw(l), Self::Vw(r))
517
| (Self::Vh(l), Self::Vh(r))
518
| (Self::VMin(l), Self::VMin(r))
519
| (Self::VMax(l), Self::VMax(r))
520
| (Self::Rem(l), Self::Rem(r)) => l == r,
521
_ => false,
522
}
523
}
524
}
525
526
impl core::ops::Mul<f32> for FontSize {
527
type Output = FontSize;
528
529
fn mul(self, rhs: f32) -> Self::Output {
530
match self {
531
FontSize::Px(v) => FontSize::Px(v * rhs),
532
FontSize::Vw(v) => FontSize::Vw(v * rhs),
533
FontSize::Vh(v) => FontSize::Vh(v * rhs),
534
FontSize::VMin(v) => FontSize::VMin(v * rhs),
535
FontSize::VMax(v) => FontSize::VMax(v * rhs),
536
FontSize::Rem(v) => FontSize::Rem(v * rhs),
537
}
538
}
539
}
540
541
impl core::ops::Mul<FontSize> for f32 {
542
type Output = FontSize;
543
544
fn mul(self, rhs: FontSize) -> Self::Output {
545
rhs * self
546
}
547
}
548
549
impl Default for FontSize {
550
fn default() -> Self {
551
Self::Px(20.)
552
}
553
}
554
555
impl From<f32> for FontSize {
556
fn from(value: f32) -> Self {
557
Self::Px(value)
558
}
559
}
560
561
/// Base value used to resolve `Rem` units for font sizes.
562
#[derive(Resource, Copy, Clone, Debug, PartialEq, Deref, DerefMut)]
563
pub struct RemSize(pub f32);
564
565
impl Default for RemSize {
566
fn default() -> Self {
567
Self(20.)
568
}
569
}
570
571
/// How thick or bold the strokes of a font appear.
572
///
573
/// Valid font weights range from 1 to 1000, inclusive.
574
/// Weights above 1000 are clamped to 1000.
575
/// A weight of 0 is treated as [`FontWeight::DEFAULT`].
576
///
577
/// Legacy names from when most fonts weren't variable fonts
578
/// are included as const values, but are misleading if
579
/// used in documentation and examples, as valid weights
580
/// for variable fonts are all of the numbers from 1-1000, and
581
/// not all fonts which are not variable fonts have those weights
582
/// supplied. If you use a custom font that supplies only specific
583
/// weights, that will be documented where you purchased the font.
584
///
585
/// `<https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-weight>`
586
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect)]
587
pub struct FontWeight(pub u16);
588
589
impl FontWeight {
590
/// Weight 100.
591
pub const THIN: FontWeight = FontWeight(100);
592
593
/// Weight 200.
594
pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
595
596
/// Weight 300.
597
pub const LIGHT: FontWeight = FontWeight(300);
598
599
/// Weight 400.
600
pub const NORMAL: FontWeight = FontWeight(400);
601
602
/// Weight 500.
603
pub const MEDIUM: FontWeight = FontWeight(500);
604
605
/// Weight 600.
606
pub const SEMIBOLD: FontWeight = FontWeight(600);
607
608
/// Weight 700.
609
pub const BOLD: FontWeight = FontWeight(700);
610
611
/// Weight 800
612
pub const EXTRA_BOLD: FontWeight = FontWeight(800);
613
614
/// Weight 900.
615
pub const BLACK: FontWeight = FontWeight(900);
616
617
/// Weight 950.
618
pub const EXTRA_BLACK: FontWeight = FontWeight(950);
619
620
/// The default font weight.
621
pub const DEFAULT: FontWeight = Self::NORMAL;
622
623
/// Clamp the weight value to between 1 and 1000.
624
/// Values of 0 are mapped to `Weight::DEFAULT`.
625
pub const fn clamp(mut self) -> Self {
626
if self.0 == 0 {
627
self = Self::DEFAULT;
628
} else if 1000 < self.0 {
629
self.0 = 1000;
630
}
631
Self(self.0)
632
}
633
}
634
635
impl Default for FontWeight {
636
fn default() -> Self {
637
Self::DEFAULT
638
}
639
}
640
641
impl From<FontWeight> for cosmic_text::Weight {
642
fn from(value: FontWeight) -> Self {
643
cosmic_text::Weight(value.clamp().0)
644
}
645
}
646
647
/// `<https://docs.microsoft.com/en-us/typography/opentype/spec/os2#uswidthclass>`
648
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Debug, Hash, Reflect)]
649
pub struct FontWidth(u16);
650
651
impl FontWidth {
652
/// 50% of normal width.
653
pub const ULTRA_CONDENSED: Self = Self(1);
654
655
/// 62.5% of normal width.
656
pub const EXTRA_CONDENSED: Self = Self(2);
657
658
/// 75% of normal width.
659
pub const CONDENSED: Self = Self(3);
660
661
/// 87.5% of normal width.
662
pub const SEMI_CONDENSED: Self = Self(4);
663
664
/// 100% of normal width. This is the default.
665
pub const NORMAL: Self = Self(5);
666
667
/// 112.5% of normal width.
668
pub const SEMI_EXPANDED: Self = Self(6);
669
670
/// 125% of normal width.
671
pub const EXPANDED: Self = Self(7);
672
673
/// 150% of normal width.
674
pub const EXTRA_EXPANDED: Self = Self(8);
675
676
/// 200% of normal width.
677
pub const ULTRA_EXPANDED: Self = Self(9);
678
}
679
680
impl Default for FontWidth {
681
fn default() -> Self {
682
Self::NORMAL
683
}
684
}
685
686
impl From<FontWidth> for Stretch {
687
fn from(value: FontWidth) -> Self {
688
match value.0 {
689
1 => Stretch::UltraCondensed,
690
2 => Stretch::ExtraCondensed,
691
3 => Stretch::Condensed,
692
4 => Stretch::SemiCondensed,
693
6 => Stretch::SemiExpanded,
694
7 => Stretch::Expanded,
695
8 => Stretch::ExtraExpanded,
696
9 => Stretch::UltraExpanded,
697
_ => Stretch::Normal,
698
}
699
}
700
}
701
702
/// The slant style of a font face: normal, italic, or oblique.
703
#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, Hash, Reflect)]
704
pub enum FontStyle {
705
/// A face that is neither italic nor obliqued.
706
#[default]
707
Normal,
708
/// A form that is generally cursive in nature.
709
Italic,
710
/// A typically sloped version of the regular face.
711
Oblique,
712
}
713
714
impl From<FontStyle> for cosmic_text::Style {
715
fn from(value: FontStyle) -> Self {
716
match value {
717
FontStyle::Normal => cosmic_text::Style::Normal,
718
FontStyle::Italic => cosmic_text::Style::Italic,
719
FontStyle::Oblique => cosmic_text::Style::Oblique,
720
}
721
}
722
}
723
724
/// An OpenType font feature tag.
725
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect)]
726
pub struct FontFeatureTag([u8; 4]);
727
728
impl FontFeatureTag {
729
/// Replaces character combinations like fi, fl with ligatures.
730
pub const STANDARD_LIGATURES: FontFeatureTag = FontFeatureTag::new(b"liga");
731
732
/// Enables ligatures based on character context.
733
pub const CONTEXTUAL_LIGATURES: FontFeatureTag = FontFeatureTag::new(b"clig");
734
735
/// Enables optional ligatures for stylistic use (e.g., ct, st).
736
pub const DISCRETIONARY_LIGATURES: FontFeatureTag = FontFeatureTag::new(b"dlig");
737
738
/// Adjust glyph shapes based on surrounding letters.
739
pub const CONTEXTUAL_ALTERNATES: FontFeatureTag = FontFeatureTag::new(b"calt");
740
741
/// Use alternate glyph designs.
742
pub const STYLISTIC_ALTERNATES: FontFeatureTag = FontFeatureTag::new(b"salt");
743
744
/// Replaces lowercase letters with small caps.
745
pub const SMALL_CAPS: FontFeatureTag = FontFeatureTag::new(b"smcp");
746
747
/// Replaces uppercase letters with small caps.
748
pub const CAPS_TO_SMALL_CAPS: FontFeatureTag = FontFeatureTag::new(b"c2sc");
749
750
/// Replaces characters with swash versions (often decorative).
751
pub const SWASH: FontFeatureTag = FontFeatureTag::new(b"swsh");
752
753
/// Enables alternate glyphs for large sizes or titles.
754
pub const TITLING_ALTERNATES: FontFeatureTag = FontFeatureTag::new(b"titl");
755
756
/// Converts numbers like 1/2 into true fractions (½).
757
pub const FRACTIONS: FontFeatureTag = FontFeatureTag::new(b"frac");
758
759
/// Formats characters like 1st, 2nd properly.
760
pub const ORDINALS: FontFeatureTag = FontFeatureTag::new(b"ordn");
761
762
/// Uses a slashed version of zero (0) to differentiate from O.
763
pub const SLASHED_ZERO: FontFeatureTag = FontFeatureTag::new(b"ordn");
764
765
/// Replaces figures with superscript figures, e.g. for indicating footnotes.
766
pub const SUPERSCRIPT: FontFeatureTag = FontFeatureTag::new(b"sups");
767
768
/// Replaces figures with subscript figures.
769
pub const SUBSCRIPT: FontFeatureTag = FontFeatureTag::new(b"subs");
770
771
/// Changes numbers to "oldstyle" form, which fit better in the flow of sentences or other text.
772
pub const OLDSTYLE_FIGURES: FontFeatureTag = FontFeatureTag::new(b"onum");
773
774
/// Changes numbers to "lining" form, which are better suited for standalone numbers. When
775
/// enabled, the bottom of all numbers will be aligned with each other.
776
pub const LINING_FIGURES: FontFeatureTag = FontFeatureTag::new(b"lnum");
777
778
/// Changes numbers to be of proportional width. When enabled, numbers may have varying widths.
779
pub const PROPORTIONAL_FIGURES: FontFeatureTag = FontFeatureTag::new(b"pnum");
780
781
/// Changes numbers to be of uniform (tabular) width. When enabled, all numbers will have the
782
/// same width.
783
pub const TABULAR_FIGURES: FontFeatureTag = FontFeatureTag::new(b"tnum");
784
785
/// Varies the stroke thickness. Valid values are in the range of 1 to 1000, inclusive.
786
pub const WEIGHT: FontFeatureTag = FontFeatureTag::new(b"wght");
787
788
/// Varies the width of text from narrower to wider. Must be a value greater than 0. A value of
789
/// 100 is typically considered standard width.
790
pub const WIDTH: FontFeatureTag = FontFeatureTag::new(b"wdth");
791
792
/// Varies between upright and slanted text. Must be a value greater than -90 and less than +90.
793
/// A value of 0 is upright.
794
pub const SLANT: FontFeatureTag = FontFeatureTag::new(b"slnt");
795
796
/// Create a new [`FontFeatureTag`] from raw bytes.
797
pub const fn new(src: &[u8; 4]) -> Self {
798
Self(*src)
799
}
800
}
801
802
impl Debug for FontFeatureTag {
803
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
804
// OpenType tags are always ASCII, so this match will succeed for valid tags. This gives us
805
// human-readable debug output, e.g. FontFeatureTag("liga").
806
match from_utf8(&self.0) {
807
Ok(s) => write!(f, "FontFeatureTag(\"{}\")", s),
808
Err(_) => write!(f, "FontFeatureTag({:?})", self.0),
809
}
810
}
811
}
812
813
/// OpenType features for .otf fonts that support them.
814
///
815
/// Examples features include ligatures, small-caps, and fractional number display. For the complete
816
/// list of OpenType features, see the spec at
817
/// `<https://learn.microsoft.com/en-us/typography/opentype/spec/featurelist>`.
818
///
819
/// # Usage:
820
/// ```
821
/// use bevy_text::{FontFeatureTag, FontFeatures};
822
///
823
/// // Create using the builder
824
/// let font_features = FontFeatures::builder()
825
/// .enable(FontFeatureTag::STANDARD_LIGATURES)
826
/// .set(FontFeatureTag::WEIGHT, 300)
827
/// .build();
828
///
829
/// // Create from a list
830
/// let more_font_features: FontFeatures = [
831
/// FontFeatureTag::STANDARD_LIGATURES,
832
/// FontFeatureTag::OLDSTYLE_FIGURES,
833
/// FontFeatureTag::TABULAR_FIGURES
834
/// ].into();
835
/// ```
836
#[derive(Clone, Debug, Default, Reflect, PartialEq)]
837
pub struct FontFeatures {
838
features: Vec<(FontFeatureTag, u32)>,
839
}
840
841
impl FontFeatures {
842
/// Create a new [`FontFeaturesBuilder`].
843
pub fn builder() -> FontFeaturesBuilder {
844
FontFeaturesBuilder::default()
845
}
846
}
847
848
/// A builder for [`FontFeatures`].
849
#[derive(Clone, Default)]
850
pub struct FontFeaturesBuilder {
851
features: Vec<(FontFeatureTag, u32)>,
852
}
853
854
impl FontFeaturesBuilder {
855
/// Enable an OpenType feature.
856
///
857
/// Most OpenType features are on/off switches, so this is a convenience method that sets the
858
/// feature's value to "1" (enabled). For non-boolean features, see [`FontFeaturesBuilder::set`].
859
pub fn enable(self, feature_tag: FontFeatureTag) -> Self {
860
self.set(feature_tag, 1)
861
}
862
863
/// Set an OpenType feature to a specific value.
864
///
865
/// For most features, the [`FontFeaturesBuilder::enable`] method should be used instead. A few
866
/// features, such as "wght", take numeric values, so this method may be used for these cases.
867
pub fn set(mut self, feature_tag: FontFeatureTag, value: u32) -> Self {
868
self.features.push((feature_tag, value));
869
self
870
}
871
872
/// Build a [`FontFeatures`] from the values set within this builder.
873
pub fn build(self) -> FontFeatures {
874
FontFeatures {
875
features: self.features,
876
}
877
}
878
}
879
880
/// Allow [`FontFeatures`] to be built from a list. This is suitable for the standard case when each
881
/// listed feature is a boolean type. If any features require a numeric value (like "wght"), use
882
/// [`FontFeaturesBuilder`] instead.
883
impl<T> From<T> for FontFeatures
884
where
885
T: IntoIterator<Item = FontFeatureTag>,
886
{
887
fn from(value: T) -> Self {
888
FontFeatures {
889
features: value.into_iter().map(|x| (x, 1)).collect(),
890
}
891
}
892
}
893
894
impl From<&FontFeatures> for cosmic_text::FontFeatures {
895
fn from(font_features: &FontFeatures) -> Self {
896
cosmic_text::FontFeatures {
897
features: font_features
898
.features
899
.iter()
900
.map(|(tag, value)| cosmic_text::Feature {
901
tag: cosmic_text::FeatureTag::new(&tag.0),
902
value: *value,
903
})
904
.collect(),
905
}
906
}
907
}
908
909
/// Specifies the height of each line of text for `Text` and `Text2d`
910
///
911
/// Default is 1.2x the font size
912
#[derive(Component, Debug, Clone, Copy, PartialEq, Reflect)]
913
#[reflect(Component, Debug, Clone, PartialEq)]
914
pub enum LineHeight {
915
/// Set line height to a specific number of pixels
916
Px(f32),
917
/// Set line height to a multiple of the font size
918
RelativeToFont(f32),
919
}
920
921
impl Default for LineHeight {
922
fn default() -> Self {
923
LineHeight::RelativeToFont(1.2)
924
}
925
}
926
927
/// The color of the text for this section.
928
#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)]
929
#[reflect(Component, Default, Debug, PartialEq, Clone)]
930
pub struct TextColor(pub Color);
931
932
impl Default for TextColor {
933
fn default() -> Self {
934
Self::WHITE
935
}
936
}
937
938
impl<T: Into<Color>> From<T> for TextColor {
939
fn from(color: T) -> Self {
940
Self(color.into())
941
}
942
}
943
944
impl TextColor {
945
/// Black colored text
946
pub const BLACK: Self = TextColor(Color::BLACK);
947
/// White colored text
948
pub const WHITE: Self = TextColor(Color::WHITE);
949
}
950
951
/// The background color of the text for this section.
952
#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)]
953
#[reflect(Component, Default, Debug, PartialEq, Clone)]
954
pub struct TextBackgroundColor(pub Color);
955
956
impl Default for TextBackgroundColor {
957
fn default() -> Self {
958
Self(Color::BLACK)
959
}
960
}
961
962
impl<T: Into<Color>> From<T> for TextBackgroundColor {
963
fn from(color: T) -> Self {
964
Self(color.into())
965
}
966
}
967
968
impl TextBackgroundColor {
969
/// Black background
970
pub const BLACK: Self = TextBackgroundColor(Color::BLACK);
971
/// White background
972
pub const WHITE: Self = TextBackgroundColor(Color::WHITE);
973
}
974
975
/// Determines how lines will be broken when preventing text from running out of bounds.
976
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Reflect, Serialize, Deserialize)]
977
#[reflect(Serialize, Deserialize, Clone, PartialEq, Hash, Default)]
978
pub enum LineBreak {
979
/// Uses the [Unicode Line Breaking Algorithm](https://www.unicode.org/reports/tr14/).
980
/// Lines will be broken up at the nearest suitable word boundary, usually a space.
981
/// This behavior suits most cases, as it keeps words intact across linebreaks.
982
#[default]
983
WordBoundary,
984
/// Lines will be broken without discrimination on any character that would leave bounds.
985
/// This is closer to the behavior one might expect from text in a terminal.
986
/// However it may lead to words being broken up across linebreaks.
987
AnyCharacter,
988
/// Wraps at the word level, or fallback to character level if a word can’t fit on a line by itself
989
WordOrCharacter,
990
/// No soft wrapping, where text is automatically broken up into separate lines when it overflows a boundary, will ever occur.
991
/// Hard wrapping, where text contains an explicit linebreak such as the escape sequence `\n`, is still enabled.
992
NoWrap,
993
}
994
995
/// A text entity with this component is drawn with strikethrough.
996
#[derive(Component, Copy, Clone, Debug, Reflect, Default, Serialize, Deserialize)]
997
#[reflect(Serialize, Deserialize, Clone, Default)]
998
pub struct Strikethrough;
999
1000
/// Color for the text's strikethrough. If this component is not present, its `TextColor` will be used.
1001
#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)]
1002
#[reflect(Component, Default, Debug, PartialEq, Clone)]
1003
pub struct StrikethroughColor(pub Color);
1004
1005
impl Default for StrikethroughColor {
1006
fn default() -> Self {
1007
Self(Color::WHITE)
1008
}
1009
}
1010
1011
impl<T: Into<Color>> From<T> for StrikethroughColor {
1012
fn from(color: T) -> Self {
1013
Self(color.into())
1014
}
1015
}
1016
1017
/// Add to a text entity to draw its text with underline.
1018
#[derive(Component, Copy, Clone, Debug, Reflect, Default, Serialize, Deserialize)]
1019
#[reflect(Serialize, Deserialize, Clone, Default)]
1020
pub struct Underline;
1021
1022
/// Color for the text's underline. If this component is not present, its `TextColor` will be used.
1023
#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect, PartialEq)]
1024
#[reflect(Component, Default, Debug, PartialEq, Clone)]
1025
pub struct UnderlineColor(pub Color);
1026
1027
impl Default for UnderlineColor {
1028
fn default() -> Self {
1029
Self(Color::WHITE)
1030
}
1031
}
1032
1033
impl<T: Into<Color>> From<T> for UnderlineColor {
1034
fn from(color: T) -> Self {
1035
Self(color.into())
1036
}
1037
}
1038
1039
/// Determines which antialiasing method to use when rendering text. By default, text is
1040
/// rendered with grayscale antialiasing, but this can be changed to achieve a pixelated look.
1041
///
1042
/// **Note:** Subpixel antialiasing is not currently supported.
1043
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Reflect, Serialize, Deserialize)]
1044
#[reflect(Serialize, Deserialize, Clone, PartialEq, Hash, Default)]
1045
#[doc(alias = "antialiasing")]
1046
#[doc(alias = "pixelated")]
1047
pub enum FontSmoothing {
1048
/// No antialiasing. Useful for when you want to render text with a pixel art aesthetic.
1049
///
1050
/// Combine this with `UiAntiAlias::Off` and `Msaa::Off` on your 2D camera for a fully pixelated look.
1051
///
1052
/// **Note:** Due to limitations of the underlying text rendering library,
1053
/// this may require specially-crafted pixel fonts to look good, especially at small sizes.
1054
None,
1055
/// The default grayscale antialiasing. Produces text that looks smooth,
1056
/// even at small font sizes and low resolutions with modern vector fonts.
1057
#[default]
1058
AntiAliased,
1059
// TODO: Add subpixel antialias support
1060
// SubpixelAntiAliased,
1061
}
1062
1063
/// System that detects changes to text blocks and sets `ComputedTextBlock::should_rerender`.
1064
///
1065
/// Generic over the root text component and text span component. For example, `Text2d`/[`TextSpan`] for
1066
/// 2d or `Text`/[`TextSpan`] for UI.
1067
pub fn detect_text_needs_rerender<Root: Component>(
1068
changed_roots: Query<
1069
Entity,
1070
(
1071
Or<(
1072
Changed<Root>,
1073
Changed<TextFont>,
1074
Changed<TextLayout>,
1075
Changed<LineHeight>,
1076
Changed<Children>,
1077
)>,
1078
With<Root>,
1079
With<TextFont>,
1080
With<TextLayout>,
1081
),
1082
>,
1083
changed_spans: Query<
1084
(Entity, Option<&ChildOf>, Has<TextLayout>),
1085
(
1086
Or<(
1087
Changed<TextSpan>,
1088
Changed<TextFont>,
1089
Changed<LineHeight>,
1090
Changed<Children>,
1091
Changed<ChildOf>, // Included to detect broken text block hierarchies.
1092
Added<TextLayout>,
1093
)>,
1094
With<TextSpan>,
1095
With<TextFont>,
1096
),
1097
>,
1098
mut computed: Query<(
1099
Option<&ChildOf>,
1100
Option<&mut ComputedTextBlock>,
1101
Has<TextSpan>,
1102
)>,
1103
) {
1104
// Root entity:
1105
// - Root component changed.
1106
// - TextFont on root changed.
1107
// - TextLayout changed.
1108
// - Root children changed (can include additions and removals).
1109
for root in changed_roots.iter() {
1110
let Ok((_, Some(mut computed), _)) = computed.get_mut(root) else {
1111
once!(warn!("found entity {} with a root text component ({}) but no ComputedTextBlock; this warning only \
1112
prints once", root, core::any::type_name::<Root>()));
1113
continue;
1114
};
1115
computed.needs_rerender = true;
1116
}
1117
1118
// Span entity:
1119
// - Span component changed.
1120
// - Span TextFont changed.
1121
// - Span children changed (can include additions and removals).
1122
for (entity, maybe_span_child_of, has_text_block) in changed_spans.iter() {
1123
if has_text_block {
1124
once!(warn!("found entity {} with a TextSpan that has a TextLayout, which should only be on root \
1125
text entities (that have {}); this warning only prints once",
1126
entity, core::any::type_name::<Root>()));
1127
}
1128
1129
let Some(span_child_of) = maybe_span_child_of else {
1130
once!(warn!(
1131
"found entity {} with a TextSpan that has no parent; it should have an ancestor \
1132
with a root text component ({}); this warning only prints once",
1133
entity,
1134
core::any::type_name::<Root>()
1135
));
1136
continue;
1137
};
1138
let mut parent: Entity = span_child_of.parent();
1139
1140
// Search for the nearest ancestor with ComputedTextBlock.
1141
// Note: We assume the perf cost from duplicate visits in the case that multiple spans in a block are visited
1142
// is outweighed by the expense of tracking visited spans.
1143
loop {
1144
let Ok((maybe_child_of, maybe_computed, has_span)) = computed.get_mut(parent) else {
1145
once!(warn!("found entity {} with a TextSpan that is part of a broken hierarchy with a ChildOf \
1146
component that points at non-existent entity {}; this warning only prints once",
1147
entity, parent));
1148
break;
1149
};
1150
if let Some(mut computed) = maybe_computed {
1151
computed.needs_rerender = true;
1152
break;
1153
}
1154
if !has_span {
1155
once!(warn!("found entity {} with a TextSpan that has an ancestor ({}) that does not have a text \
1156
span component or a ComputedTextBlock component; this warning only prints once",
1157
entity, parent));
1158
break;
1159
}
1160
let Some(next_child_of) = maybe_child_of else {
1161
once!(warn!(
1162
"found entity {} with a TextSpan that has no ancestor with the root text \
1163
component ({}); this warning only prints once",
1164
entity,
1165
core::any::type_name::<Root>()
1166
));
1167
break;
1168
};
1169
parent = next_child_of.parent();
1170
}
1171
}
1172
}
1173
1174
#[derive(Component, Debug, Copy, Clone, Default, Reflect, PartialEq)]
1175
#[reflect(Component, Default, Debug, Clone, PartialEq)]
1176
/// Font hinting strategy.
1177
///
1178
/// The text bounds can underflow or overflow slightly with `FontHinting::Enabled`.
1179
///
1180
/// <https://docs.rs/cosmic-text/latest/cosmic_text/enum.Hinting.html>
1181
pub enum FontHinting {
1182
#[default]
1183
/// Glyphs will have subpixel coordinates.
1184
Disabled,
1185
/// Glyphs will be snapped to integral coordinates in the X-axis during layout.
1186
///
1187
/// The text bounds can underflow or overflow slightly with this enabled.
1188
Enabled,
1189
}
1190
1191
impl From<FontHinting> for cosmic_text::Hinting {
1192
fn from(value: FontHinting) -> Self {
1193
match value {
1194
FontHinting::Disabled => cosmic_text::Hinting::Disabled,
1195
FontHinting::Enabled => cosmic_text::Hinting::Enabled,
1196
}
1197
}
1198
}
1199
1200