1use std::rc::Rc;
2
3use crate::media::text_formatting::{
4 DrawableTextRun, FormattingObjectPool, LogicalTextRunEnumerator, ShapedTextRun, TextFormatterImpl, TextCharacters, TextCollapsingProperties, TextFormatter, TextLine, TextRun,
5 TextRunProperties,
6};
7use crate::media::FlowDirection;
8use crate::utilities::MathUtilities;
9
10pub struct TextPathSegmentEllipsis {
18 width: f64,
19 symbol: Rc<dyn TextRun>,
20 flow_direction: FlowDirection,
21}
22
23#[derive(Clone, Copy)]
25struct Segment {
26 start: i32,
27 length: i32,
28 width: f64,
29 is_separator: bool,
30}
31
32impl TextPathSegmentEllipsis {
33 pub fn new(
41 ellipsis: &str,
42 width: f64,
43 text_run_properties: Rc<dyn TextRunProperties>,
44 flow_direction: FlowDirection,
45 ) -> Self {
46 Self { width, symbol: Rc::new(TextCharacters::from_str(ellipsis, text_run_properties)), flow_direction }
47 }
48
49 fn collapse_core(
52 &self,
53 text_line: &dyn TextLine,
54 logical_runs: &[Rc<dyn TextRun>],
55 shaped_symbol: Rc<ShapedTextRun>,
56 total_width: f64,
57 object_pool: &FormattingObjectPool,
58 ) -> Option<Vec<Rc<dyn TextRun>>> {
59 let mut run_start_chars = vec![0i32; logical_runs.len() + 1];
66
67 for (i, run) in logical_runs.iter().enumerate() {
68 run_start_chars[i + 1] = run_start_chars[i] + run.length();
69 }
70
71 let mut segments: Vec<Segment> = Vec::new();
73 let mut candidate_segment_indices: Vec<usize> = Vec::new();
74 let mut global_index = 0i32;
75 let mut current_seg_start = 0i32;
76 let mut in_separator = false;
77
78 for run in logical_runs {
79 if run.is::<ShapedTextRun>() {
80 for &ch in run.text_span() {
81 if Self::is_separator(ch) {
82 if !in_separator && global_index - current_seg_start > 0 {
84 let segment_width = Self::measure_segment_width(
85 logical_runs,
86 &run_start_chars,
87 current_seg_start,
88 global_index - current_seg_start,
89 );
90
91 segments.push(Segment {
92 start: current_seg_start,
93 length: global_index - current_seg_start,
94 width: segment_width,
95 is_separator: false,
96 });
97 }
98
99 let separator_width =
100 Self::measure_segment_width(logical_runs, &run_start_chars, global_index, 1);
101
102 segments.push(Segment { start: global_index, length: 1, width: separator_width, is_separator: true });
104
105 current_seg_start = global_index + 1;
107 in_separator = true;
108 } else if in_separator {
109 current_seg_start = global_index;
111 in_separator = false;
112 }
113
114 global_index += 1;
115 }
116 } else {
117 if in_separator {
119 current_seg_start = global_index;
120 in_separator = false;
121 }
122
123 global_index += run.length();
124 }
125 }
126
127 if global_index - current_seg_start > 0 {
129 let segment_width = Self::measure_segment_width(
130 logical_runs,
131 &run_start_chars,
132 current_seg_start,
133 global_index - current_seg_start,
134 );
135
136 segments.push(Segment {
137 start: current_seg_start,
138 length: global_index - current_seg_start,
139 width: segment_width,
140 is_separator: false,
141 });
142 }
143
144 if segments.is_empty() {
145 return None;
147 }
148
149 let mut prefix = vec![0f64; segments.len() + 1];
150
151 for (i, segment) in segments.iter().enumerate() {
153 if !segment.is_separator {
154 candidate_segment_indices.push(i);
155 }
156
157 prefix[i + 1] = prefix[i] + segment.width;
158 }
159
160 let mid_char = global_index / 2;
162
163 let mut center_candidate_idx = 0i32;
165 let mut best_dist = i64::MAX;
166
167 for (i, &candidate_segment_index) in candidate_segment_indices.iter().enumerate() {
168 let segment = segments[candidate_segment_index];
169 let seg_center = segment.start + segment.length / 2;
170 let dist = (seg_center - mid_char).abs() as i64;
171
172 if dist < best_dist {
173 best_dist = dist;
174 center_candidate_idx = i as i32;
175 }
176 }
177
178 let candidate_count = candidate_segment_indices.len() as i32;
180
181 if candidate_count > 0 {
182 let mut window_starts: Vec<i32> = Vec::new();
183
184 for window_size in 1..=candidate_count {
185 let half = (window_size - 1) / 2;
188 let start = center_candidate_idx - half;
189 window_starts.clear();
191
192 let min_start = 0.max(center_candidate_idx - (window_size - 1));
194 let max_start = (candidate_count - window_size).min(center_candidate_idx + (window_size - 1));
195
196 let mut s = start;
198 while s >= min_start {
199 window_starts.push(s);
200 s -= 1;
201 }
202
203 let mut s = start + 1;
205 while s <= max_start {
206 window_starts.push(s);
207 s += 1;
208 }
209
210 for &window_start in &window_starts {
211 if window_start < 0 || window_start + window_size > candidate_count {
212 continue;
213 }
214
215 let left_cand = window_start;
216 let right_cand = window_start + window_size - 1;
217
218 let seg_start_index = candidate_segment_indices[left_cand as usize];
220 let seg_end_index = candidate_segment_indices[right_cand as usize];
221
222 let left_remaining = segments[seg_start_index].start;
224 let right_remaining = global_index - (segments[seg_end_index].start + segments[seg_end_index].length);
225
226 if left_remaining <= 0 || right_remaining <= 0 {
227 continue;
228 }
229
230 let trimmed_width = prefix[seg_end_index + 1] - prefix[seg_start_index];
231
232 if MathUtilities::less_than_or_close(
233 total_width - trimmed_width + shaped_symbol.size().width,
234 self.width,
235 ) {
236 let remove_start = segments[seg_start_index].start;
238 let remove_length =
239 (segments[seg_end_index].start + segments[seg_end_index].length) - remove_start;
240
241 let (first, remainder) =
242 TextFormatterImpl::split_text_runs(logical_runs, remove_start, object_pool).deconstruct();
243
244 let Some(remainder) = remainder else {
245 object_pool.text_run_lists.return_optional(first);
247
248 return None;
249 };
250
251 let (middle, last) =
252 TextFormatterImpl::split_text_runs(&remainder, remove_length, object_pool).deconstruct();
253
254 let mut result: Vec<Rc<dyn TextRun>> = Vec::with_capacity(
257 first.as_ref().map_or(0, |runs| runs.len()) + 1 + last.as_ref().map_or(0, |runs| runs.len()),
258 );
259
260 if let Some(first) = &first {
261 result.extend(first.iter().cloned());
262 }
263
264 result.push(shaped_symbol);
265
266 if let Some(last) = &last {
267 result.extend(last.iter().cloned());
268 }
269
270 object_pool.text_run_lists.return_optional(first);
272 object_pool.text_run_lists.return_list(remainder);
273 object_pool.text_run_lists.return_optional(middle);
274 object_pool.text_run_lists.return_optional(last);
275
276 return Some(result);
277 }
278 }
279 }
280 }
281
282 let mut current_length = 0;
284 let mut remaining_width = text_line.width_including_trailing_whitespace();
285
286 for (segment_index, segment) in segments.iter().enumerate() {
287 if segment_index < segments.len() - 1
288 && MathUtilities::greater_than(remaining_width - segment.width, self.width)
289 {
290 remaining_width -= segment.width;
291 current_length += segment.length;
292
293 continue;
294 }
295
296 let (first, second): (Option<Vec<Rc<dyn TextRun>>>, Option<Vec<Rc<dyn TextRun>>>) =
298 TextFormatterImpl::split_text_runs(logical_runs, current_length, object_pool).deconstruct();
299
300 let mut trimmed_run: Option<Rc<dyn TextRun>> = None;
301 let mut remaining_run_count = 0;
302
303 if let Some(second) = &second {
304 if !second.is_empty() {
305 remaining_run_count = second.len() - 1;
306
307 let run = &second[0];
308
309 if let Some(shaped_run) = run.downcast_ref::<ShapedTextRun>() {
310 let measure_width = self.width - shaped_symbol.size().width;
311
312 if let Some((length, _)) = shaped_run.try_measure_characters_backwards(measure_width) {
313 let split_at = shaped_run.length() - length;
314
315 if split_at > 0 {
316 trimmed_run = shaped_run.split(split_at).second.map(|run| run as Rc<dyn TextRun>);
317 } else if length > 0 {
318 trimmed_run = Some(run.clone());
321 }
322 }
324 }
325 }
326 }
327
328 let run_count = usize::from(trimmed_run.is_some()) + 1 + remaining_run_count;
329
330 let mut result: Vec<Rc<dyn TextRun>> = Vec::with_capacity(run_count);
331
332 result.push(shaped_symbol);
334
335 if let Some(trimmed_run) = trimmed_run {
337 result.push(trimmed_run);
338 }
339
340 if let Some(second) = &second {
342 result.extend(second.iter().skip(1).cloned());
343 }
344
345 object_pool.text_run_lists.return_optional(first);
347 object_pool.text_run_lists.return_optional(second);
348
349 return Some(result);
350 }
351
352 None
354 }
355
356 fn is_separator(ch: u16) -> bool {
360 ch == b'/' as u16 || ch == b'\\' as u16
361 }
362
363 fn measure_segment_width(
364 runs: &[Rc<dyn TextRun>],
365 run_start_chars: &[i32],
366 segment_start: i32,
367 segment_length: i32,
368 ) -> f64 {
369 if segment_length <= 0 {
370 return 0.0;
371 }
372
373 let segment_end = segment_start + segment_length;
374
375 let mut i = Self::find_first_overlapping_run(run_start_chars, segment_start);
378
379 let mut width = 0.0;
380
381 while i < runs.len() {
382 let run_start = run_start_chars[i];
383
384 if run_start >= segment_end {
385 break;
386 }
387
388 let run = &runs[i];
389 let run_end = run_start + run.length();
390
391 let overlap_start = segment_start.max(run_start);
392 let overlap_end = segment_end.min(run_end);
393
394 if overlap_end > overlap_start {
395 if let Some(shaped) = run.downcast_ref::<ShapedTextRun>() {
396 width += shaped.shaped_buffer().get_char_range_width(overlap_start - run_start, overlap_end - run_start);
398 } else if let Some(drawable) = run.as_drawable() {
399 if overlap_end - overlap_start >= run.length() {
401 width += drawable.size().width;
402 }
403 }
404 }
405
406 i += 1;
407 }
408
409 width
410 }
411
412 fn find_first_overlapping_run(run_start_chars: &[i32], char_index: i32) -> usize {
413 if char_index <= 0 {
414 return 0;
415 }
416
417 if run_start_chars.len() < 2 {
419 return 0;
420 }
421
422 let mut lo = 0usize;
423 let mut hi = run_start_chars.len() - 2;
424
425 while lo < hi {
426 let mid = (lo + hi + 1) >> 1;
427
428 if run_start_chars[mid] <= char_index {
429 lo = mid;
430 } else {
431 hi = mid - 1;
432 }
433 }
434
435 lo
436 }
437}
438
439impl TextCollapsingProperties for TextPathSegmentEllipsis {
440 fn width(&self) -> f64 {
441 self.width
442 }
443
444 fn symbol(&self) -> &Rc<dyn TextRun> {
445 &self.symbol
446 }
447
448 fn flow_direction(&self) -> FlowDirection {
449 self.flow_direction
450 }
451
452 fn collapse(&self, text_line: &dyn TextLine) -> Option<Vec<Rc<dyn TextRun>>> {
453 if text_line.text_runs().is_empty() {
454 return None;
455 }
456
457 let object_pool = FormattingObjectPool::instance();
458
459 let shaped_symbol = <dyn TextFormatter>::create_symbol(&*self.symbol, self.flow_direction);
460
461 if MathUtilities::less_than(self.width, shaped_symbol.size().width) {
462 return None;
464 }
465
466 let total_width = text_line.width();
467
468 if MathUtilities::less_than_or_close(total_width, self.width) {
469 return None;
471 }
472
473 let mut logical_runs = object_pool.text_run_lists.rent();
475
476 {
477 let mut enumerator = LogicalTextRunEnumerator::new(text_line);
478
479 while let Some(run) = enumerator.move_next() {
480 logical_runs.push(run);
481 }
482 }
483
484 let result = self.collapse_core(text_line, &logical_runs, shaped_symbol, total_width, object_pool);
485
486 object_pool.text_run_lists.return_list(logical_runs);
487
488 result
489 }
490}