1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
use crate::{
InlineLayout, InlineLayoutRequest, LineLayout, LineLayoutCache, LineLayoutIndex, Pixels,
Result, ShapedLine, ShapedText, SharedString, TextLayoutOptions, TextRun, TextSystem,
};
use derive_more::Deref;
use std::{ops::Range, sync::Arc};
/// The text layout subsystem for one window, with a cache of shaped lines.
///
/// Get it with [`Window::text_system`](crate::Window::text_system). It derefs to the shared [`TextSystem`],
/// so you can call font methods on it directly.
///
/// Layout methods such as [`WindowTextSystem::shape_line`] and
/// [`WindowTextSystem::shape_text`] cache their results. A layout is reused when
/// the same text, font size, and shaping settings are requested again. The cache
/// keeps a layout only while the next frame uses it, so a layout that a frame
/// doesn't request is dropped at the end of that frame. Changing only colors or
/// decorations reuses the glyphs and repaints them.
#[derive(Deref)]
pub struct WindowTextSystem {
line_layout_cache: LineLayoutCache,
#[deref]
text_system: Arc<TextSystem>,
}
impl WindowTextSystem {
/// Creates a window text system with an empty layout cache on top of `text_system`.
///
/// GPUI creates one for each window. You don't need to call this yourself.
pub fn new(text_system: Arc<TextSystem>) -> Self {
Self {
line_layout_cache: LineLayoutCache::new(text_system.platform_text_system.clone()),
text_system,
}
}
/// Changes whenever fonts are added, which can change the layout of any text.
pub(crate) fn font_generation(&self) -> u64 {
self.text_system.platform_text_system.font_generation()
}
pub(crate) fn layout_index(&self) -> LineLayoutIndex {
self.line_layout_cache.layout_index()
}
pub(crate) fn reuse_layouts(&self, index: Range<LineLayoutIndex>) {
self.line_layout_cache.reuse_layouts(index)
}
pub(crate) fn truncate_layouts(&self, index: LineLayoutIndex) {
self.line_layout_cache.truncate_layouts(index)
}
/// Shapes one line of text, ready to paint.
///
/// Use this for a single line of text that you paint with [`ShapedLine::paint`].
/// For text that can wrap or has several paragraphs, use [`Self::shape_text`].
/// `runs` styles the text, and the run lengths must add up to `text.len()`.
/// Each run's [`TextRun::font`] selects the face used for its bytes.
///
/// The result is cached for the frame, as described on [`WindowTextSystem`].
///
/// # Panics
///
/// In debug builds, panics if `text` contains a `\n`. Release builds don't
/// check this, and the result for such text is not meaningful. Use
/// [`Self::shape_text`] for multiple lines.
///
/// # Examples
///
/// ```
/// # use gpui::{EmptyView, TestApp, TextRun, font, hsla, px};
/// # let mut app = TestApp::new();
/// # let mut window = app.open_window(|_window, _cx| EmptyView);
/// window.update(|_view, window, _cx| {
/// let run = TextRun {
/// len: "Hello".len(),
/// font: font("Lilex"),
/// color: hsla(0., 0., 0., 1.),
/// ..Default::default()
/// };
/// let line = window.text_system().shape_line("Hello".into(), px(16.), &[run]);
/// assert_eq!(line.len(), 5);
/// });
/// ```
pub fn shape_line(
&self,
text: SharedString,
font_size: Pixels,
runs: &[TextRun],
) -> ShapedLine {
debug_assert!(
text.find('\n').is_none(),
"text argument should not contain newlines"
);
let layout = self.layout_line(&text, font_size, runs);
ShapedLine { layout, text }
}
/// Shapes text that may span several lines, ready to paint.
///
/// This is the function to use for paragraphs. Each `\n` starts a new line.
/// When `wrap_width` is `Some`, lines wrap at word boundaries to fit within
/// that many logical pixels. A word wider than that is not split, so it
/// overflows. `None` disables soft wrapping, and only `\n` breaks lines.
/// With a wrap width, `line_clamp = Some(n)` limits soft wrapping: once the
/// budget is exhausted, the rest of each paragraph occupies an unwrapped line.
/// Hard paragraph breaks are preserved, so the total can exceed `n`.
/// `Some(0)` behaves like `Some(1)` and disables soft wrapping; it does not hide text.
///
/// `runs` styles the text. Each run gives the number of UTF-8 bytes it covers,
/// and the runs must cover the whole string and end on character boundaries.
/// The `Result` is currently always `Ok`.
///
/// Shaping is cached as described on [`WindowTextSystem`]. To lay out text
/// inside an element, use [`StyledText`](crate::StyledText) instead. See
/// [text](crate::_documentation::guides::text) for an overview.
///
/// # Examples
///
/// This example supplies the platform's real text system. [`TestApp::new`](crate::TestApp::new)
/// uses a simplified test shaper, which does not reproduce paragraph layout.
///
/// ```standalone_crate
/// # use gpui::{EmptyView, TestApp, TextRun, font, hsla, px};
/// let text_system = gpui_platform::current_platform(true).text_system();
/// let mut app = TestApp::with_text_system(text_system);
/// # let mut window = app.open_window(|_window, _cx| EmptyView);
/// window.update(|_view, window, _cx| {
/// let text = "first line\nsecond line";
/// let run = TextRun {
/// len: text.len(),
/// font: font(".SystemUIFont"),
/// color: hsla(0., 0., 0., 1.),
/// ..Default::default()
/// };
/// let shaped = window
/// .text_system()
/// .shape_text(text, px(16.), &[run.clone()], Some(px(200.)), None)
/// .unwrap();
/// assert_eq!(shaped.len(), text.len());
///
/// // A clamp cannot remove the hard break between these paragraphs.
/// let zero = window.text_system()
/// .shape_text(text, px(16.), &[run.clone()], Some(px(20.)), Some(0))
/// .unwrap();
/// let one = window.text_system()
/// .shape_text(text, px(16.), &[run], Some(px(20.)), Some(1))
/// .unwrap();
/// assert_eq!(zero.line_count(), 2);
/// assert_eq!(one.line_count(), 2);
/// });
/// ```
pub fn shape_text<S: AsRef<str> + Into<SharedString>>(
&self,
text: S,
font_size: Pixels,
runs: &[TextRun],
wrap_width: Option<Pixels>,
line_clamp: Option<usize>,
) -> Result<ShapedText> {
self.shape_text_with_options(
text,
font_size,
runs,
TextLayoutOptions {
wrap_width,
line_clamp,
..Default::default()
},
)
}
/// Shapes a text document with explicit paragraph direction, alignment, and bidi options.
///
/// This is the lower-level form of [`Self::shape_text`]. Use it when text
/// must align within a width different from its wrap width, or must follow
/// a specific paragraph direction. The `Result` is currently always `Ok`.
#[doc(hidden)]
pub fn shape_text_with_options<S: AsRef<str> + Into<SharedString>>(
&self,
text: S,
font_size: Pixels,
runs: &[TextRun],
options: TextLayoutOptions,
) -> Result<ShapedText> {
let text = text.into();
let layout = self
.line_layout_cache
.layout_text_with_options(&text, font_size, runs, options);
Ok(ShapedText { layout, text })
}
/// Lays out text and atomic boxes together, the way an inline element flows in a paragraph.
///
/// Each box is an element that sits between characters, such as an icon or
/// a badge. Text wraps around the boxes. The result is cached like other
/// layouts.
pub fn layout_inline(&self, request: InlineLayoutRequest<'_>) -> Arc<InlineLayout> {
self.line_layout_cache.layout_inline(request)
}
/// Lays out one line of text and returns the backend's [`LineLayout`].
///
/// Most code should call [`Self::shape_line`] instead, which returns a
/// [`ShapedLine`] that you can paint. Use this only when you need the raw
/// layout. `text` must not contain newlines, and nothing checks this here.
pub fn layout_line(&self, text: &str, font_size: Pixels, runs: &[TextRun]) -> Arc<LineLayout> {
self.line_layout_cache
.layout_line(&SharedString::new(text), font_size, runs)
}
pub(crate) fn finish_frame(&self) {
self.line_layout_cache.finish_frame()
}
}