1use crate::{LayoutPoint, LayoutRect, LayoutSize, LayoutUnit};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6pub struct LineMetric {
7 pub start_index: usize,
9 pub end_index: usize,
11 pub baseline: LayoutUnit,
13 pub height: LayoutUnit,
15 pub width: LayoutUnit,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
20pub struct RichTextInlineBox {
21 pub id: u64,
22 pub x: LayoutUnit,
23 pub y: LayoutUnit,
24 pub width: LayoutUnit,
25 pub height: LayoutUnit,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
32pub struct ParagraphCluster {
33 pub start_index: usize,
34 pub end_index: usize,
35 pub line_index: usize,
36 pub rect: LayoutRect,
37 pub is_rtl: bool,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
42pub struct ParagraphGlyph {
43 pub id: u32,
44 pub style_index: usize,
45 pub cluster_index: usize,
46 pub position: LayoutPoint,
47 pub advance: LayoutUnit,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
52pub struct ParagraphSelectionBox {
53 pub start_index: usize,
54 pub end_index: usize,
55 pub line_index: usize,
56 pub rect: LayoutRect,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
61pub struct ParagraphCaretStop {
62 pub index: usize,
63 pub upstream: bool,
64 pub position: LayoutPoint,
65 pub height: LayoutUnit,
66}
67
68#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
75pub struct ResolvedParagraphLayout {
76 pub constraint_width: Option<LayoutUnit>,
77 pub size: LayoutSize,
78 pub lines: Vec<LineMetric>,
79 pub inline_boxes: Vec<RichTextInlineBox>,
80 pub clusters: Vec<ParagraphCluster>,
82 pub glyphs: Vec<ParagraphGlyph>,
84 pub caret_stops: Vec<ParagraphCaretStop>,
86 pub selection_boxes: Vec<ParagraphSelectionBox>,
88}
89
90impl ResolvedParagraphLayout {
91 pub fn empty(constraint_width: Option<LayoutUnit>) -> Self {
92 Self {
93 constraint_width,
94 size: LayoutSize::ZERO,
95 lines: Vec::new(),
96 inline_boxes: Vec::new(),
97 clusters: Vec::new(),
98 glyphs: Vec::new(),
99 caret_stops: Vec::new(),
100 selection_boxes: Vec::new(),
101 }
102 }
103
104 pub fn hit_test(&self, point: LayoutPoint) -> usize {
106 let Some(cluster) = self.clusters.iter().min_by(|left, right| {
107 distance_to_rect(point, left.rect).total_cmp(&distance_to_rect(point, right.rect))
108 }) else {
109 return 0;
110 };
111 let after_midpoint = point.x >= cluster.rect.x() + cluster.rect.width() * 0.5;
112 match (cluster.is_rtl, after_midpoint) {
113 (false, false) | (true, true) => cluster.start_index,
114 (false, true) | (true, false) => cluster.end_index,
115 }
116 }
117
118 pub fn caret(&self, index: usize, upstream: bool) -> Option<ParagraphCaretStop> {
120 self.caret_stops
121 .iter()
122 .find(|stop| stop.index == index && stop.upstream == upstream)
123 .copied()
124 .or_else(|| {
125 self.caret_stops
126 .iter()
127 .filter(|stop| stop.upstream == upstream)
128 .min_by_key(|stop| stop.index.abs_diff(index))
129 .copied()
130 })
131 }
132
133 pub fn selection_rects(&self, start: usize, end: usize) -> Vec<LayoutRect> {
136 let range = start.min(end)..start.max(end);
137 let mut selected = self
138 .selection_boxes
139 .iter()
140 .filter(|selection| {
141 selection.start_index < range.end && selection.end_index > range.start
142 })
143 .copied()
144 .collect::<Vec<_>>();
145 selected.sort_by(|left, right| {
146 left.line_index
147 .cmp(&right.line_index)
148 .then_with(|| left.rect.x().total_cmp(&right.rect.x()))
149 });
150 let mut rects: Vec<LayoutRect> = Vec::new();
151 for selection in selected {
152 if let Some(last) = rects.last_mut() {
153 let same_line = (last.y() - selection.rect.y()).abs() < 0.5
154 && (last.height() - selection.rect.height()).abs() < 0.5;
155 if same_line && selection.rect.x() <= last.right() + 0.5 {
156 let right = last.right().max(selection.rect.right());
157 last.size.width = right - last.x();
158 continue;
159 }
160 }
161 rects.push(selection.rect);
162 }
163 rects
164 }
165}
166
167fn distance_to_rect(point: LayoutPoint, rect: LayoutRect) -> LayoutUnit {
168 let dx = if point.x < rect.x() {
169 rect.x() - point.x
170 } else if point.x > rect.right() {
171 point.x - rect.right()
172 } else {
173 0.0
174 };
175 let dy = if point.y < rect.y() {
176 rect.y() - point.y
177 } else if point.y > rect.bottom() {
178 point.y - rect.bottom()
179 } else {
180 0.0
181 };
182 dx * dx + dy * dy
183}
184
185#[derive(Debug, Clone, PartialEq)]
187pub struct RichTextLayoutInfo {
188 pub width: LayoutUnit,
189 pub height: LayoutUnit,
190 pub inline_boxes: Vec<RichTextInlineBox>,
191}
192
193impl From<ResolvedParagraphLayout> for RichTextLayoutInfo {
194 fn from(layout: ResolvedParagraphLayout) -> Self {
195 Self {
196 width: layout.size.width,
197 height: layout.size.height,
198 inline_boxes: layout.inline_boxes,
199 }
200 }
201}