1use crate::text::Text;
20
21use cosmic_text as ct;
22use ct::{Buffer, LayoutRunIter};
23
24use piet::kurbo::{Point, Rect, Size, Vec2};
25use piet::TextStorage;
26
27use swash::scale::image::Image as SwashImage;
28use swash::scale::outline::Outline as SwashOutline;
29use swash::scale::{ScaleContext, StrikeWith};
30use swash::zeno;
31
32use std::cell::Cell;
33use std::cmp;
34use std::collections::hash_map::{Entry, HashMap};
35use std::fmt;
36use std::rc::Rc;
37
38#[derive(Clone)]
40pub struct TextLayout {
41 text_buffer: Rc<BufferWrapper>,
43}
44
45impl fmt::Debug for TextLayout {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 f.debug_struct("TextLayout")
48 .field("string", &self.text_buffer.string.as_str())
49 .field("glyph_size", &self.text_buffer.glyph_size)
50 .finish_non_exhaustive()
51 }
52}
53
54struct BufferWrapper {
55 string: Box<dyn TextStorage>,
57
58 glyph_size: i32,
60
61 buffer: Option<Buffer>,
63
64 run_metrics: Vec<piet::LineMetric>,
66
67 ink_rectangle: Rect,
69
70 logical_size: Cell<Option<Size>>,
72
73 handle: Text,
75}
76
77impl BufferWrapper {
78 fn buffer(&self) -> &Buffer {
79 self.buffer.as_ref().unwrap()
80 }
81}
82
83impl Drop for BufferWrapper {
84 fn drop(&mut self) {
85 let mut buffer = self.buffer.take().unwrap();
86 buffer.lines.clear();
87 let old_lines = self.handle.take_buffer();
88
89 if old_lines.capacity() > buffer.lines.capacity() {
91 self.handle.set_buffer(old_lines);
92 } else {
93 self.handle.set_buffer(buffer.lines);
94 }
95 }
96}
97
98impl TextLayout {
99 pub(crate) fn new(
101 text: Text,
102 buffer: Buffer,
103 string: Box<dyn TextStorage>,
104 glyph_size: i32,
105 font_system: &mut ct::FontSystem,
106 ) -> Self {
107 let span = trace_span!("TextLayout::new", string = %string.as_str());
108 let _guard = span.enter();
109
110 let run_metrics = buffer
112 .layout_runs()
113 .map(|run| RunMetrics::new(run, glyph_size as f64))
114 .map(|RunMetrics { line_metric }| line_metric)
115 .collect();
116
117 let mut ink_context = text.borrow_ink();
119 let mut missing_bbox_count = 0;
120
121 let bounding_boxes = buffer
122 .layout_runs()
123 .flat_map(|run| {
124 let run_y = run.line_y;
125 run.glyphs.iter().map(move |glyph| (glyph, run_y))
126 })
127 .filter_map(|(glyph, run_y)| {
128 let physical = glyph.physical((0., 0.), 1.);
129 let offset = Vec2::new(
130 physical.x as f64 + physical.cache_key.x_bin.as_float() as f64,
131 run_y as f64 + physical.y as f64 + physical.cache_key.y_bin.as_float() as f64,
132 );
133
134 match ink_context.bounding_box(&physical, font_system) {
136 Some(mut rect) => {
137 rect = rect + offset;
138 Some(rect)
139 }
140
141 None => {
142 missing_bbox_count += 1;
143 None
144 }
145 }
146 });
147 let ink_rectangle = bounding_rectangle(bounding_boxes);
148
149 if missing_bbox_count > 0 {
150 warn!("Missing {} bounding boxes", missing_bbox_count);
151 }
152
153 drop(ink_context);
154
155 Self {
156 text_buffer: Rc::new(BufferWrapper {
157 string,
158 glyph_size,
159 buffer: Some(buffer),
160 run_metrics,
161 handle: text,
162 ink_rectangle,
163 logical_size: Cell::new(None),
164 }),
165 }
166 }
167
168 pub fn buffer(&self) -> &Buffer {
170 self.text_buffer.buffer()
171 }
172
173 pub fn layout_runs(&self) -> LayoutRunIter<'_> {
175 self.buffer().layout_runs()
176 }
177}
178
179impl piet::TextLayout for TextLayout {
180 fn size(&self) -> Size {
181 if let Some(size) = self.text_buffer.logical_size.get() {
182 return size;
183 }
184
185 let mut size = Size::new(f64::MIN, f64::MIN);
186
187 for run in self.layout_runs() {
188 let max = |a: f32, b: f64| {
189 let a: f64 = a.into();
190 if a < b {
191 b
192 } else {
193 a
194 }
195 };
196
197 size.width = max(run.line_w, size.width);
198 size.height = max(run.line_y, size.height);
199 }
200
201 self.text_buffer.logical_size.set(Some(size));
202
203 size
204 }
205
206 fn trailing_whitespace_width(&self) -> f64 {
207 self.size().width
209 }
210
211 fn image_bounds(&self) -> Rect {
212 self.text_buffer.ink_rectangle
213 }
214
215 fn text(&self) -> &str {
216 &self.text_buffer.string
217 }
218
219 fn line_text(&self, line_number: usize) -> Option<&str> {
220 self.buffer()
221 .layout_runs()
222 .nth(line_number)
223 .map(|run| run.text)
224 }
225
226 fn line_metric(&self, line_number: usize) -> Option<piet::LineMetric> {
227 self.text_buffer.run_metrics.get(line_number).cloned()
228 }
229
230 fn line_count(&self) -> usize {
231 self.buffer().layout_runs().count()
232 }
233
234 fn hit_test_point(&self, point: Point) -> piet::HitTestPoint {
235 let mut htp = piet::HitTestPoint::default();
236 let (x, y) = point.into();
237
238 if let Some(cursor) = self.buffer().hit(x as f32, y as f32) {
239 htp.idx = cursor.index;
240 htp.is_inside = true;
241 return htp;
242 }
243
244 let mut ink_context = self.text_buffer.handle.borrow_ink();
245 let mut font_system_guard = match self.text_buffer.handle.borrow_font_system() {
246 Some(system) => system,
247 None => {
248 warn!("Tried to borrow font system to calculate better hit test point, but it was already borrowed.");
249 htp.idx = 0;
250 htp.is_inside = false;
251 return htp;
252 }
253 };
254 let font_system = &mut font_system_guard
255 .get()
256 .expect("For a TextLayout to exist, the font system must have already been initialized")
257 .system;
258
259 let mut closest_distance = f64::MAX;
261
262 for (glyph, physical_glyph) in self.layout_runs().flat_map(|run| {
263 let run_y = run.line_y;
264 run.glyphs
265 .iter()
266 .map(move |glyph| (glyph, glyph.physical((0., run_y), 1.)))
267 }) {
268 let bounding_box = match ink_context.bounding_box(&physical_glyph, font_system) {
269 Some(bbox) => bbox,
270 None => continue,
271 };
272
273 if bounding_box.contains(point) {
275 htp.idx = glyph.start;
276 htp.is_inside = false;
277 return htp;
278 }
279
280 let midpoint = bounding_box.center();
282 let distance = midpoint.distance(point);
283 if distance < closest_distance {
284 closest_distance = distance;
285 htp.idx = glyph.start;
286 }
287 }
288
289 htp.is_inside = false;
291 htp
292 }
293
294 fn hit_test_text_position(&self, idx: usize) -> piet::HitTestPosition {
295 let mut lines_and_glyphs = self.layout_runs().enumerate().flat_map(|(line, run)| {
297 run.glyphs.iter().map(move |glyph| {
298 (
299 line,
300 {
301 let physical = glyph.physical((0.0, 0.0), 1.0);
303 let x = physical.x as f64;
304 let y = run.line_y as f64
305 + physical.y as f64
306 + self.text_buffer.glyph_size as f64;
307
308 Point::new(x, y)
309 },
310 glyph.start..glyph.end,
311 )
312 })
313 });
314
315 let (line, point, _) = match lines_and_glyphs.find(|(_, _, range)| range.contains(&idx)) {
316 Some(x) => x,
317 None => {
318 return piet::HitTestPosition::default();
320 }
321 };
322
323 let mut htp = piet::HitTestPosition::default();
324 htp.point = point;
325 htp.line = line;
326 htp
327 }
328}
329
330fn bounding_rectangle(rects: impl IntoIterator<Item = Rect>) -> Rect {
331 let mut iter = rects.into_iter();
332 let mut sum_rect = match iter.next() {
333 Some(rect) => rect,
334 None => return Rect::ZERO,
335 };
336
337 for rect in iter {
338 if rect.x0 < sum_rect.x0 {
339 sum_rect.x0 = rect.x0;
340 }
341 if rect.y0 < sum_rect.y0 {
342 sum_rect.y0 = rect.y0;
343 }
344 if rect.x1 > sum_rect.x1 {
345 sum_rect.x1 = rect.x1;
346 }
347 if rect.y1 > sum_rect.y1 {
348 sum_rect.y1 = rect.y1;
349 }
350 }
351
352 sum_rect
353}
354
355struct RunMetrics {
357 line_metric: piet::LineMetric,
359}
360
361impl RunMetrics {
362 fn new(run: ct::LayoutRun<'_>, glyph_size: f64) -> RunMetrics {
363 let (start_offset, end_offset) = run.glyphs.iter().fold((0, 0), |(start, end), glyph| {
364 (cmp::min(start, glyph.start), cmp::max(end, glyph.end))
365 });
366
367 let y_offset = run.line_top.into();
368 let baseline = run.line_y as f64 - run.line_top as f64;
369
370 RunMetrics {
371 line_metric: piet::LineMetric {
372 start_offset,
373 end_offset,
374 trailing_whitespace: 0, y_offset,
376 height: glyph_size as _,
377 baseline,
378 },
379 }
380 }
381}
382
383pub(crate) struct InkRectangleState {
385 scaler: ScaleContext,
387
388 bbox_cache: HashMap<ct::CacheKey, Option<Rect>>,
390
391 swash_image: SwashImage,
393
394 swash_outline: SwashOutline,
396}
397
398impl InkRectangleState {
399 pub(crate) fn new() -> Self {
400 Self {
401 scaler: ScaleContext::new(),
402 bbox_cache: HashMap::new(),
403 swash_image: SwashImage::new(),
404 swash_outline: SwashOutline::new(),
405 }
406 }
407
408 fn bounding_box(
410 &mut self,
411 glyph: &ct::PhysicalGlyph,
412 system: &mut ct::FontSystem,
413 ) -> Option<Rect> {
414 let entry = match self.bbox_cache.entry(glyph.cache_key) {
416 Entry::Occupied(o) => return *o.into_mut(),
417 Entry::Vacant(v) => v,
418 };
419
420 let mut bbox = None;
421
422 if let Some(font) = system.get_font(glyph.cache_key.font_id) {
424 let mut scaler = self
426 .scaler
427 .builder(font.as_swash())
428 .size(f32::from_bits(glyph.cache_key.font_size_bits))
429 .build();
430
431 self.swash_outline.clear();
433 if scaler.scale_outline_into(glyph.cache_key.glyph_id, &mut self.swash_outline) {
434 bbox = Some(cvt_bounds(self.swash_outline.bounds()));
435 } else {
436 self.swash_image.clear();
438 if scaler.scale_bitmap_into(
439 glyph.cache_key.glyph_id,
440 StrikeWith::BestFit,
441 &mut self.swash_image,
442 ) {
443 bbox = Some(cvt_placement(self.swash_image.placement));
444 }
445 }
446 }
447
448 *entry.insert(bbox)
450 }
451}
452
453fn cvt_placement(placement: zeno::Placement) -> Rect {
454 Rect::new(
455 placement.left.into(),
456 -placement.top as f64,
457 placement.left as f64 + placement.width as f64,
458 -placement.top as f64 + placement.height as f64,
459 )
460}
461
462fn cvt_bounds(mut bounds: zeno::Bounds) -> Rect {
463 bounds.min.y *= -1.0;
464 bounds.max.y *= -1.0;
465 Rect::from_points(cvt_point(bounds.min), cvt_point(bounds.max))
466}
467
468fn cvt_point(point: zeno::Point) -> Point {
469 Point::new(point.x.into(), point.y.into())
470}