layout/flow/inline/
shaping_queue.rs1use std::ops::Range;
6use std::sync::Arc;
7
8use fonts::{ShapedText, ShapedTextSlice, ShapedTextSliceType, ShapedTextSlicer, ShapingOptions};
9use icu_segmenter::LineBreakOptions;
10use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
11use style::computed_values::word_break::T as WordBreak;
12use style::properties::ComputedValues;
13use style::str::char_is_whitespace;
14use style::values::computed::OverflowWrap;
15use unicode_script::Script;
16
17use crate::ArcRefCell;
18use crate::flow::inline::line_breaker::LineBreaker;
19use crate::flow::inline::text_run::{FontAndScriptInfo, TextRun, TextRunItem, script_is_specific};
20
21pub(crate) struct ShapingQueueText {
25 info: FontAndScriptInfo,
26 byte_range: Range<usize>,
27 character_range: Range<usize>,
28 text_run: ArcRefCell<TextRun>,
29 index_in_text_run: usize,
30 old_shaped_text: Option<Arc<ShapedText>>,
31}
32
33pub(crate) enum ShapingQueueEntry {
35 PreservedTabOrNewline,
36 Text(ShapingQueueText),
37}
38
39impl ShapingQueueEntry {
40 pub(crate) fn new(
41 text_run: ArcRefCell<TextRun>,
42 text_run_item: &TextRunItem,
43 index_in_text_run: usize,
44 old_text_run_line_item: Option<TextRunItem>,
45 ) -> Self {
46 let text_segment = match text_run_item {
47 TextRunItem::LineBreak { .. } | TextRunItem::Tab { .. } => {
48 return Self::PreservedTabOrNewline;
49 },
50 TextRunItem::TextSegment(text_run_segment) => text_run_segment,
51 };
52
53 let old_shaped_text = old_text_run_line_item.and_then(|old_text_run_line_item| {
54 let TextRunItem::TextSegment(old_text_segment) = old_text_run_line_item else {
55 return None;
56 };
57 if !text_segment.is_compatible_with_old_shaping_result(&old_text_segment) {
58 return None;
59 }
60 old_text_segment.shaped_text
61 });
62
63 Self::Text(ShapingQueueText {
64 info: text_segment.info.clone(),
65 byte_range: text_segment.byte_range.clone(),
66 character_range: text_segment.character_range.clone(),
67 text_run,
68 index_in_text_run,
69 old_shaped_text,
70 })
71 }
72}
73
74struct BatchSlicer<'a> {
75 slicer: ShapedTextSlicer,
76 text: &'a str,
77 line_breaker: &'a mut LineBreaker,
78 character_offset_origin: usize,
79}
80
81impl BatchSlicer<'_> {
82 fn slice_shaped_text_at_line_break_opportunities(
83 &mut self,
84 segment: &ShapingQueueText,
85 parent_style: &ComputedValues,
86 ) -> (Vec<Arc<ShapedTextSlice>>, bool) {
87 let range = segment.byte_range.clone();
91 let linebreaks = self
92 .line_breaker
93 .advance_to_linebreaks_in_range(segment.byte_range.clone());
94 let linebreak_iter = linebreaks.iter().chain(std::iter::once(&range.end));
95
96 let mut break_at_start = false;
97
98 let text_style = parent_style.get_inherited_text();
99 let can_break_anywhere = text_style.word_break == WordBreak::BreakAll ||
100 text_style.overflow_wrap == OverflowWrap::Anywhere ||
101 text_style.overflow_wrap == OverflowWrap::BreakWord;
102
103 let mut last_slice = segment.byte_range.start..segment.byte_range.start;
104 let mut current_character_offset =
105 segment.character_range.start - self.character_offset_origin;
106
107 let mut runs = Vec::with_capacity(linebreaks.len());
108 let mut maybe_push_run = |run: Option<Arc<ShapedTextSlice>>| {
109 if let Some(run) = run {
110 runs.push(run);
111 }
112 };
113
114 for break_index in linebreak_iter {
115 if *break_index == segment.byte_range.start {
116 break_at_start = true;
117 continue;
118 }
119
120 let mut slice = last_slice.end..*break_index;
122 let word = &self.text[slice.clone()];
123
124 let mut whitespace = slice.end..slice.end;
126 let rev_char_indices = word.char_indices().rev().peekable();
127
128 let mut slice_type = ShapedTextSliceType::Word;
129 let mut ends_with_whitespace = false;
130 if let Some((first_white_space_index, first_white_space_character)) = rev_char_indices
131 .take_while(|&(_, character)| char_is_whitespace(character))
132 .last()
133 {
134 ends_with_whitespace = true;
135 whitespace.start = slice.start + first_white_space_index;
136
137 if text_style.white_space_collapse == WhiteSpaceCollapse::BreakSpaces &&
143 !can_break_anywhere
144 {
145 whitespace.start += first_white_space_character.len_utf8();
146 slice_type = ShapedTextSliceType::WordAndWhiteSpace;
147 }
148
149 slice.end = whitespace.start;
150 }
151
152 if !ends_with_whitespace &&
155 *break_index != segment.byte_range.end &&
156 text_style.word_break == WordBreak::KeepAll &&
157 !can_break_anywhere
158 {
159 continue;
160 }
161
162 last_slice = slice.start..*break_index;
164
165 if !slice.is_empty() {
167 current_character_offset += self.text[slice].chars().count();
168 maybe_push_run(
169 self.slicer
170 .slice_until_character_offset(current_character_offset, slice_type),
171 );
172 }
173
174 if whitespace.is_empty() {
175 continue;
176 }
177
178 if text_style.white_space_collapse == WhiteSpaceCollapse::BreakSpaces {
181 for _ in self.text[whitespace].chars() {
182 current_character_offset += 1;
183 maybe_push_run(self.slicer.slice_until_character_offset(
184 current_character_offset,
185 ShapedTextSliceType::WhiteSpace,
186 ));
187 }
188 continue;
189 }
190
191 current_character_offset += self.text[whitespace].chars().count();
192 maybe_push_run(self.slicer.slice_until_character_offset(
193 current_character_offset,
194 ShapedTextSliceType::WhiteSpace,
195 ));
196 }
197
198 (runs, break_at_start)
199 }
200}
201
202pub(crate) struct ShapingQueue<'a> {
217 queue: Vec<ShapingQueueText>,
219 text: &'a str,
221 line_breaker: LineBreaker,
223 byte_range: Range<usize>,
226 character_range: Range<usize>,
229 resolved_script: Option<Script>,
232}
233
234impl<'a> ShapingQueue<'a> {
235 pub(crate) fn new(text: &'a str, line_break_options: LineBreakOptions) -> Self {
236 Self {
237 queue: Default::default(),
238 text,
239 line_breaker: LineBreaker::new(text, line_break_options),
240 byte_range: Default::default(),
241 character_range: Default::default(),
242 resolved_script: None,
243 }
244 }
245
246 fn compatible_old_shaping_result(&self, character_count: usize) -> Option<Arc<ShapedText>> {
247 let old_shaped_text = self.queue.first()?.old_shaped_text.as_ref()?;
248 if old_shaped_text.character_count() != character_count {
249 return None;
250 }
251
252 if !self.queue.iter().all(|entry| {
253 entry
254 .old_shaped_text
255 .as_ref()
256 .is_some_and(|entry_old_shaped_text| {
257 Arc::ptr_eq(old_shaped_text, entry_old_shaped_text)
258 })
259 }) {
260 return None;
261 }
262 Some(old_shaped_text.clone())
263 }
264
265 fn shape_batch(&self) -> Option<Arc<ShapedText>> {
266 let first = self.queue.first()?;
267
268 let character_count = self.character_range.end - self.character_range.start;
269 if let Some(old_shaping_result) = self.compatible_old_shaping_result(character_count) {
270 return Some(old_shaping_result);
271 };
272
273 let mut options: ShapingOptions = (&first.info).into();
274 options.script = self.resolved_script.unwrap_or(first.info.script);
275
276 let font = &first.info.font_info.font;
277 Some(font.shape_text(&self.text[self.byte_range.clone()], &options))
278 }
279
280 pub(crate) fn flush(&mut self) {
284 let Some(shaped_text) = self.shape_batch() else {
285 return;
286 };
287
288 let mut slicer = BatchSlicer {
289 slicer: ShapedTextSlicer::new(shaped_text.clone()),
290 text: self.text,
291 line_breaker: &mut self.line_breaker,
292 character_offset_origin: self.character_range.start,
293 };
294
295 for entry in self.queue.drain(..) {
296 let mut text_run = entry.text_run.borrow_mut();
297 let style = text_run.inline_styles().style.borrow().clone();
298 let (runs, break_at_start) =
299 slicer.slice_shaped_text_at_line_break_opportunities(&entry, &style);
300
301 if let TextRunItem::TextSegment(text_segment) =
302 &mut text_run.items[entry.index_in_text_run]
303 {
304 text_segment.shaped_text = Some(shaped_text.clone());
305 text_segment.runs = runs;
306 text_segment.break_at_start = break_at_start;
307 }
308 }
309 }
310
311 fn compatible_with_batch(&self, text: &ShapingQueueText) -> bool {
312 let Some(last) = self.queue.last() else {
314 return true;
315 };
316
317 if last.character_range.end != text.character_range.start ||
320 last.byte_range.end != text.byte_range.start
321 {
322 return false;
323 }
324
325 if !Arc::ptr_eq(&last.info.font_info, &text.info.font_info) &&
328 *last.info.font_info != *text.info.font_info
329 {
330 return false;
331 }
332
333 !script_is_specific(text.info.script) ||
335 self.resolved_script
336 .is_none_or(|resolved_script| resolved_script == text.info.script)
337 }
338
339 fn push_text(&mut self, text: ShapingQueueText) {
340 if !self.compatible_with_batch(&text) {
341 self.flush();
342 }
343
344 if self.queue.is_empty() {
345 self.character_range = text.character_range.clone();
346 self.byte_range = text.byte_range.clone();
347 self.resolved_script = None;
348 } else {
349 self.character_range.end = text.character_range.end;
350 self.byte_range.end = text.byte_range.end;
351 }
352 if self.resolved_script.is_none() && script_is_specific(text.info.script) {
353 self.resolved_script = Some(text.info.script);
354 }
355
356 self.queue.push(text);
357 }
358
359 pub(crate) fn push(&mut self, entry: ShapingQueueEntry) {
362 match entry {
363 ShapingQueueEntry::PreservedTabOrNewline => self.flush(),
364 ShapingQueueEntry::Text(shaping_queue_text) => self.push_text(shaping_queue_text),
365 }
366 }
367}