1use std::ops::Range;
6
7use icu_segmenter::WordSegmenter;
8use itertools::Either;
9use layout_api::SegmentGranularity;
10use script::layout_dom::ServoLayoutNode;
11use servo_base::text::{AssumeUnder4GB, Utf32CodeUnits, Utf32CodeUnitsOrNodeOffset};
12use style::dom::OpaqueNode;
13
14use crate::ArcRefCell;
15use crate::dom::{LayoutBox, NodeExt};
16use crate::flow::inline::InlineFormattingContext;
17use crate::flow::inline::text_run::TextRun;
18
19struct BoundaryIterator<'a, Boundaries> {
22 boundaries: Boundaries,
23 text: &'a str,
24 previous_boundary: usize,
25 previous_boundary_in_characters: Utf32CodeUnits,
26}
27
28impl<'a, Boundaries> BoundaryIterator<'a, Boundaries> {
29 fn new(text: &'a str, boundaries: Boundaries) -> Self {
30 Self {
31 boundaries,
32 text,
33 previous_boundary: 0,
34 previous_boundary_in_characters: Utf32CodeUnits(0),
35 }
36 }
37}
38
39impl<Boundaries: Iterator<Item = usize>> Iterator for BoundaryIterator<'_, Boundaries> {
40 type Item = Range<Utf32CodeUnits>;
41
42 fn next(&mut self) -> Option<Self::Item> {
43 let mut next_boundary = self.boundaries.next()?;
44 while next_boundary == self.previous_boundary {
45 next_boundary = self.boundaries.next()?;
46 }
47
48 let start = self.previous_boundary_in_characters;
49 let end = start +
50 Utf32CodeUnits::length_of(
51 AssumeUnder4GB,
52 &self.text[self.previous_boundary..next_boundary],
53 );
54 self.previous_boundary = next_boundary;
55 self.previous_boundary_in_characters = end;
56 Some(start..end)
57 }
58}
59
60pub(crate) struct PositionInInlineFormattingContext {
61 container: LayoutBox,
63 offset: Utf32CodeUnits,
65 text_run: ArcRefCell<TextRun>,
67}
68
69impl PositionInInlineFormattingContext {
70 pub(crate) fn from_dom_position_in_text_node(
75 node: &ServoLayoutNode,
76 offset: Utf32CodeUnitsOrNodeOffset,
77 ) -> Option<Self> {
78 let inner_layout_data = node.inner_layout_data();
79 let self_box = inner_layout_data.as_ref()?.self_box.borrow();
80 let layout_box = self_box.as_ref()?;
81
82 let LayoutBox::Text(text_run) = layout_box else {
83 return None;
84 };
85 let offset = text_run
86 .borrow()
87 .run_data
88 .map_dom_offset_to_transformed_ifc_offset(Utf32CodeUnits(offset.0));
89 let container = layout_box.layout_box_for_containing_inline_formatting_context()?;
90 Some(Self {
91 container,
92 offset,
93 text_run: text_run.clone(),
94 })
95 }
96
97 fn offsets(&self, granularity: SegmentGranularity) -> Range<Utf32CodeUnits> {
99 let text_run = self.text_run.borrow();
100 let run_data = &text_run.run_data;
101 let text = run_data
102 .text_content
103 .get()
104 .expect("There should always be text at this point");
105
106 let iterator = || match granularity {
107 SegmentGranularity::Word => Either::Left(BoundaryIterator::new(
108 text,
109 WordSegmenter::new_auto(Default::default()).segment_str(text),
110 )),
111 SegmentGranularity::HardLineBreak => Either::Right(BoundaryIterator::new(
112 text,
113 text.split_inclusive("\n").scan(0, |offset, chunk| {
114 *offset += chunk.len();
115 Some(*offset)
116 }),
117 )),
118 };
119
120 iterator()
121 .find(|range| range.contains(&self.offset))
122 .or_else(|| iterator().last())
123 .unwrap_or_else(|| run_data.character_range_in_ifc_text.clone())
124 }
125
126 pub(crate) fn segment(
129 &self,
130 granularity: SegmentGranularity,
131 ) -> Option<(
132 (OpaqueNode, Utf32CodeUnitsOrNodeOffset),
133 (OpaqueNode, Utf32CodeUnitsOrNodeOffset),
134 )> {
135 let range = self.offsets(granularity);
136 self.container
137 .with_inline_formatting_context(|inline_formatting_context| {
138 let start = inline_formatting_context
139 .find_dom_position_of_offset(range.start, SearchAffinity::RangeStart)?;
140 let end = inline_formatting_context
141 .find_dom_position_of_offset(range.end, SearchAffinity::RangeEnd)?;
142 Some((start, end))
143 })
144 }
145}
146
147enum SearchAffinity {
149 RangeStart,
152 RangeEnd,
155}
156
157impl InlineFormattingContext {
158 fn find_dom_position_of_offset(
161 &self,
162 offset: Utf32CodeUnits,
163 search_direction: SearchAffinity,
164 ) -> Option<(OpaqueNode, Utf32CodeUnitsOrNodeOffset)> {
165 let mut text_runs = self.non_pseudo_text_runs();
166 let text_run = match search_direction {
167 SearchAffinity::RangeStart => text_runs
168 .find(|text_run| offset < text_run.run_data.character_range_in_ifc_text.end)
169 .or_else(|| self.non_pseudo_text_runs().next_back()),
170 SearchAffinity::RangeEnd => text_runs
171 .rev()
172 .find(|text_run| offset > text_run.run_data.character_range_in_ifc_text.start)
173 .or_else(|| self.non_pseudo_text_runs().next()),
174 }?;
175
176 let mapped_offset = text_run
177 .run_data
178 .map_transformed_ifc_offset_to_dom_offset(offset);
179 Some((
180 text_run.base_fragment_info.tag?.node,
181 Utf32CodeUnitsOrNodeOffset(mapped_offset.0),
182 ))
183 }
184}