1use std::{ffi, fmt, ops::Range};
2
3use skia_bindings as sb;
4
5use super::{
6 LineMetrics, PositionWithAffinity, RectHeightStyle, RectWidthStyle, TextBox, TextDirection,
7 TextIndex, TextRange,
8};
9use crate::{
10 interop::{Sink, VecSink},
11 prelude::*,
12 scalar, Canvas, Font, Path, Point, Rect, Size, TextBlob, Unichar,
13};
14
15pub type Paragraph = RefHandle<sb::skia_textlayout_Paragraph>;
16// <https://github.com/rust-skia/rust-skia/issues/537>
17// unsafe_send_sync!(Paragraph);
18
19impl NativeDrop for sb::skia_textlayout_Paragraph {
20 fn drop(&mut self) {
21 unsafe { sb::C_Paragraph_delete(self) }
22 }
23}
24
25impl fmt::Debug for Paragraph {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 f&mut DebugStruct<'_, '_>.debug_struct("Paragraph")
28 .field("max_width", &self.max_width())
29 .field("height", &self.height())
30 .field("min_intrinsic_width", &self.min_intrinsic_width())
31 .field("max_intrinsic_width", &self.max_intrinsic_width())
32 .field("alphabetic_baseline", &self.alphabetic_baseline())
33 .field("ideographic_baseline", &self.ideographic_baseline())
34 .field("longest_line", &self.longest_line())
35 .field("did_exceed_max_lines", &self.did_exceed_max_lines())
36 .field(name:"line_number", &self.line_number())
37 .finish()
38 }
39}
40
41impl Paragraph {
42 pub fn max_width(&self) -> scalar {
43 self.native().fWidth
44 }
45
46 pub fn height(&self) -> scalar {
47 self.native().fHeight
48 }
49
50 pub fn min_intrinsic_width(&self) -> scalar {
51 self.native().fMinIntrinsicWidth
52 }
53
54 pub fn max_intrinsic_width(&self) -> scalar {
55 self.native().fMaxIntrinsicWidth
56 }
57
58 pub fn alphabetic_baseline(&self) -> scalar {
59 self.native().fAlphabeticBaseline
60 }
61
62 pub fn ideographic_baseline(&self) -> scalar {
63 self.native().fIdeographicBaseline
64 }
65
66 pub fn longest_line(&self) -> scalar {
67 self.native().fLongestLine
68 }
69
70 pub fn did_exceed_max_lines(&self) -> bool {
71 self.native().fExceededMaxLines
72 }
73
74 pub fn layout(&mut self, width: scalar) {
75 unsafe { sb::C_Paragraph_layout(self.native_mut(), width) }
76 }
77
78 pub fn paint(&self, canvas: &Canvas, p: impl Into<Point>) {
79 let p = p.into();
80 unsafe { sb::C_Paragraph_paint(self.native_mut_force(), canvas.native_mut(), p.x, p.y) }
81 }
82
83 /// Returns a vector of bounding boxes that enclose all text between
84 /// start and end glyph indexes, including start and excluding end
85 pub fn get_rects_for_range(
86 &self,
87 range: Range<usize>,
88 rect_height_style: RectHeightStyle,
89 rect_width_style: RectWidthStyle,
90 ) -> Vec<TextBox> {
91 let mut result: Vec<TextBox> = Vec::new();
92
93 let mut set_tb = |tbs: &[sb::skia_textlayout_TextBox]| {
94 result = tbs.iter().map(TextBox::from_native_ref).cloned().collect();
95 };
96
97 unsafe {
98 sb::C_Paragraph_getRectsForRange(
99 self.native_mut_force(),
100 range.start.try_into().unwrap(),
101 range.end.try_into().unwrap(),
102 rect_height_style.into_native(),
103 rect_width_style.into_native(),
104 VecSink::new(&mut set_tb).native_mut(),
105 );
106 }
107 result
108 }
109
110 pub fn get_rects_for_placeholders(&self) -> Vec<TextBox> {
111 let mut result = Vec::new();
112
113 let mut set_tb = |tbs: &[sb::skia_textlayout_TextBox]| {
114 result = tbs.iter().map(TextBox::from_native_ref).cloned().collect();
115 };
116
117 unsafe {
118 sb::C_Paragraph_getRectsForPlaceholders(
119 self.native_mut_force(),
120 VecSink::new(&mut set_tb).native_mut(),
121 )
122 }
123 result
124 }
125
126 /// Returns the index of the glyph that corresponds to the provided coordinate,
127 /// with the top left corner as the origin, and +y direction as down
128 pub fn get_glyph_position_at_coordinate(&self, p: impl Into<Point>) -> PositionWithAffinity {
129 let p = p.into();
130 let mut r = Default::default();
131 unsafe {
132 sb::C_Paragraph_getGlyphPositionAtCoordinate(self.native_mut_force(), p.x, p.y, &mut r)
133 }
134 r
135 }
136
137 /// Finds the first and last glyphs that define a word containing
138 /// the glyph at index offset
139 pub fn get_word_boundary(&self, offset: u32) -> Range<usize> {
140 let mut range: [usize; 2] = Default::default();
141 unsafe {
142 sb::C_Paragraph_getWordBoundary(self.native_mut_force(), offset, range.as_mut_ptr())
143 }
144 range[0]..range[1]
145 }
146
147 pub fn get_line_metrics(&self) -> Vec<LineMetrics> {
148 let mut result: Vec<LineMetrics> = Vec::new();
149 let mut set_lm = |lms: &[sb::skia_textlayout_LineMetrics]| {
150 result = lms.iter().map(LineMetrics::from_native_ref).collect();
151 };
152
153 unsafe {
154 sb::C_Paragraph_getLineMetrics(
155 self.native_mut_force(),
156 VecSink::new(&mut set_lm).native_mut(),
157 )
158 }
159
160 result
161 }
162
163 pub fn line_number(&self) -> usize {
164 unsafe { sb::C_Paragraph_lineNumber(self.native_mut_force()) }
165 }
166
167 pub fn mark_dirty(&mut self) {
168 unsafe { sb::C_Paragraph_markDirty(self.native_mut()) }
169 }
170
171 /// This function will return the number of unresolved glyphs or
172 /// `None` if not applicable (has not been shaped yet - valid case)
173 pub fn unresolved_glyphs(&mut self) -> Option<usize> {
174 unsafe { sb::C_Paragraph_unresolvedGlyphs(self.native_mut()) }
175 .try_into()
176 .ok()
177 }
178
179 pub fn unresolved_codepoints(&mut self) -> Vec<Unichar> {
180 let mut result = Vec::new();
181
182 let mut set_chars = |chars: &[Unichar]| {
183 result = chars.to_vec();
184 };
185
186 unsafe {
187 sb::C_Paragraph_unresolvedCodepoints(
188 self.native_mut_force(),
189 VecSink::new(&mut set_chars).native_mut(),
190 )
191 }
192
193 result
194 }
195
196 pub fn visit<'a, F>(&mut self, mut visitor: F)
197 where
198 F: FnMut(usize, Option<&'a VisitorInfo>),
199 {
200 unsafe {
201 sb::C_Paragraph_visit(
202 self.native_mut(),
203 &mut visitor as *mut F as *mut _,
204 Some(visitor_trampoline::<'a, F>),
205 );
206 }
207
208 unsafe extern "C" fn visitor_trampoline<'a, F: FnMut(usize, Option<&'a VisitorInfo>)>(
209 ctx: *mut ffi::c_void,
210 index: usize,
211 info: *const sb::skia_textlayout_Paragraph_VisitorInfo,
212 ) {
213 let info = if info.is_null() {
214 None
215 } else {
216 Some(VisitorInfo::from_native_ref(&*info))
217 };
218 (*(ctx as *mut F))(index, info)
219 }
220 }
221
222 pub fn extended_visit<'a, F>(&mut self, mut visitor: F)
223 where
224 F: FnMut(usize, Option<&'a ExtendedVisitorInfo>),
225 {
226 unsafe {
227 sb::C_Paragraph_extendedVisit(
228 self.native_mut(),
229 &mut visitor as *mut F as *mut _,
230 Some(visitor_trampoline::<'a, F>),
231 );
232 }
233
234 unsafe extern "C" fn visitor_trampoline<
235 'a,
236 F: FnMut(usize, Option<&'a ExtendedVisitorInfo>),
237 >(
238 ctx: *mut ffi::c_void,
239 index: usize,
240 info: *const sb::skia_textlayout_Paragraph_ExtendedVisitorInfo,
241 ) {
242 let info = if info.is_null() {
243 None
244 } else {
245 Some(ExtendedVisitorInfo::from_native_ref(&*info))
246 };
247 (*(ctx as *mut F))(index, info)
248 }
249 }
250
251 /// Returns path for a given line
252 ///
253 /// * `line_number` - a line number
254 /// * `dest` - a resulting path
255 ///
256 /// Returns: a number glyphs that could not be converted to path
257 pub fn get_path_at(&mut self, line_number: usize) -> (usize, Path) {
258 let mut path = Path::default();
259 let unconverted_glyphs = unsafe {
260 sb::C_Paragraph_getPath(
261 self.native_mut(),
262 line_number.try_into().unwrap(),
263 path.native_mut(),
264 )
265 };
266 (unconverted_glyphs.try_into().unwrap(), path)
267 }
268
269 /// Returns path for a text blob
270 ///
271 /// * `text_blob` - a text blob
272 ///
273 /// Returns: a path
274 pub fn get_path(text_blob: &mut TextBlob) -> Path {
275 Path::construct(|p| unsafe { sb::C_Paragraph_GetPath(text_blob.native_mut(), p) })
276 }
277
278 /// Checks if a given text blob contains
279 /// glyph with emoji
280 ///
281 /// * `text_blob` - a text blob
282 ///
283 /// Returns: `true` if there is such a glyph
284 pub fn contains_emoji(&mut self, text_blob: &mut TextBlob) -> bool {
285 unsafe { sb::C_Paragraph_containsEmoji(self.native_mut(), text_blob.native_mut()) }
286 }
287
288 /// Checks if a given text blob contains colored font or bitmap
289 ///
290 /// * `text_blob` - a text blob
291 ///
292 /// Returns: `true` if there is such a glyph
293 pub fn contains_color_font_or_bitmap(&mut self, text_blob: &mut TextBlob) -> bool {
294 unsafe {
295 sb::C_Paragraph_containsColorFontOrBitmap(self.native_mut(), text_blob.native_mut())
296 }
297 }
298
299 /// Finds the line number of the line that contains the given UTF-8 index.
300 ///
301 /// * `index` - a UTF-8 TextIndex into the paragraph
302 ///
303 /// Returns: the line number the glyph that corresponds to the
304 /// given `code_unit_index` is in, or -1 if the `code_unit_index`
305 /// is out of bounds, or when the glyph is truncated or
306 /// ellipsized away.
307 pub fn get_line_number_at(&self, code_unit_index: TextIndex) -> Option<usize> {
308 // Returns -1 if `code_unit_index` is out of range.
309 unsafe { sb::C_Paragraph_getLineNumberAt(self.native(), code_unit_index) }
310 .try_into()
311 .ok()
312 }
313
314 /// Finds the line number of the line that contains the given UTF-16 index.
315 ///
316 /// * `index` - a UTF-16 offset into the paragraph
317 ///
318 /// Returns: the line number the glyph that corresponds to the
319 /// given `code_unit_index` is in, or -1 if the `code_unit_index`
320 /// is out of bounds, or when the glyph is truncated or
321 /// ellipsized away.
322 pub fn get_line_number_at_utf16_offset(&self, code_unit_index: TextIndex) -> Option<usize> {
323 // Returns -1 if `code_unit_index` is out of range.
324 unsafe {
325 sb::C_Paragraph_getLineNumberAtUTF16Offset(self.native_mut_force(), code_unit_index)
326 }
327 .try_into()
328 .ok()
329 }
330
331 /// Returns line metrics info for the line
332 ///
333 /// * `line_number` - a line number
334 /// * `line_metrics` - an address to return the info (in case of null just skipped)
335 ///
336 /// Returns: `true` if the line is found; `false` if not
337 pub fn get_line_metrics_at(&self, line_number: usize) -> Option<LineMetrics> {
338 let mut r = None;
339 let mut set_lm = |lm: &sb::skia_textlayout_LineMetrics| {
340 r = Some(LineMetrics::from_native_ref(lm));
341 };
342 unsafe {
343 sb::C_Paragraph_getLineMetricsAt(
344 self.native(),
345 line_number,
346 Sink::new(&mut set_lm).native_mut(),
347 )
348 }
349 r
350 }
351
352 /// Returns the visible text on the line (excluding a possible ellipsis)
353 ///
354 /// * `line_number` - a line number
355 /// * `include_spaces` - indicates if the whitespaces should be included
356 ///
357 /// Returns: the range of the text that is shown in the line
358 pub fn get_actual_text_range(&self, line_number: usize, include_spaces: bool) -> TextRange {
359 let mut range = [0usize; 2];
360 unsafe {
361 sb::C_Paragraph_getActualTextRange(
362 self.native(),
363 line_number,
364 include_spaces,
365 range.as_mut_ptr(),
366 )
367 }
368 TextRange {
369 start: range[0],
370 end: range[1],
371 }
372 }
373
374 /// Finds a glyph cluster for text index
375 ///
376 /// * `code_unit_index` - a text index
377 /// * `glyph_info` - a glyph cluster info filled if not null
378 ///
379 /// Returns: `true` if glyph cluster was found; `false` if not
380 pub fn get_glyph_cluster_at(&self, code_unit_index: TextIndex) -> Option<GlyphClusterInfo> {
381 let mut r = None;
382 let mut set_fn = |gci: &sb::skia_textlayout_Paragraph_GlyphClusterInfo| {
383 r = Some(GlyphClusterInfo::from_native_ref(gci))
384 };
385 unsafe {
386 sb::C_Paragraph_getGlyphClusterAt(
387 self.native(),
388 code_unit_index,
389 Sink::new(&mut set_fn).native_mut(),
390 )
391 }
392 r
393 }
394
395 /// Finds the closest glyph cluster for a visual text position
396 ///
397 /// * `dx` - x coordinate
398 /// * `dy` - y coordinate
399 /// * `glyph_info` - a glyph cluster info filled if not null
400 ///
401 /// Returns: `true` if glyph cluster was found; `false` if not
402 /// (which usually means the paragraph is empty)
403 pub fn get_closest_glyph_cluster_at(&self, d: impl Into<Point>) -> Option<GlyphClusterInfo> {
404 let mut r = None;
405 let mut set_fn = |gci: &sb::skia_textlayout_Paragraph_GlyphClusterInfo| {
406 r = Some(GlyphClusterInfo::from_native_ref(gci))
407 };
408 let d = d.into();
409 unsafe {
410 sb::C_Paragraph_getClosestGlyphClusterAt(
411 self.native(),
412 d.x,
413 d.y,
414 Sink::new(&mut set_fn).native_mut(),
415 )
416 }
417 r
418 }
419
420 /// Retrieves the information associated with the glyph located at the given
421 /// `code_unit_index`.
422 ///
423 /// * `code_unit_index` - a UTF-16 offset into the paragraph
424 /// * `glyph_info` - an optional GlyphInfo struct to hold the
425 /// information associated with the glyph found at the
426 /// given index
427 ///
428 /// Returns: `false` only if the offset is out of bounds
429 pub fn get_glyph_info_at_utf16_offset(&mut self, code_unit_index: usize) -> Option<GlyphInfo> {
430 GlyphInfo::try_construct(|gi| unsafe {
431 sb::C_Paragraph_getGlyphInfoAtUTF16Offset(self.native_mut(), code_unit_index, gi)
432 })
433 }
434
435 /// Finds the information associated with the closest glyph to the given
436 /// paragraph coordinates.
437 ///
438 /// * `d` - x/y coordinate
439 /// * `glyph_info` - an optional GlyphInfo struct to hold the
440 /// information associated with the glyph found. The
441 /// text indices and text ranges are described using
442 /// UTF-16 offsets
443 ///
444 /// Returns: `true` if a grapheme cluster was found; `false` if not
445 /// (which usually means the paragraph is empty)
446 pub fn get_closest_utf16_glyph_info_at(&mut self, d: impl Into<Point>) -> Option<GlyphInfo> {
447 let d = d.into();
448 GlyphInfo::try_construct(|gi| unsafe {
449 sb::C_Paragraph_getClosestUTF16GlyphInfoAt(self.native_mut(), d.x, d.y, gi)
450 })
451 }
452
453 /// Returns the font that is used to shape the text at the position
454 ///
455 /// * `code_unit_index` - text index
456 ///
457 /// Returns: font info or an empty font info if the text is not found
458 pub fn get_font_at(&self, code_unit_index: TextIndex) -> Font {
459 Font::construct(|f| unsafe { sb::C_Paragraph_getFontAt(self.native(), code_unit_index, f) })
460 }
461
462 /// Returns the font used to shape the text at the given UTF-16 offset.
463 ///
464 /// * `code_unit_index` - a UTF-16 offset in the paragraph
465 ///
466 /// Returns: font info or an empty font info if the text is not found
467 pub fn get_font_at_utf16_offset(&mut self, code_unit_index: usize) -> Font {
468 Font::construct(|f| unsafe {
469 sb::C_Paragraph_getFontAtUTF16Offset(self.native_mut(), code_unit_index, f)
470 })
471 }
472
473 /// Returns the information about all the fonts used to shape the paragraph text
474 ///
475 /// Returns: a list of fonts and text ranges
476 pub fn get_fonts(&self) -> Vec<FontInfo> {
477 let mut result = Vec::new();
478 let mut set_fn = |fis: &[sb::skia_textlayout_Paragraph_FontInfo]| {
479 result = fis.iter().map(FontInfo::from_native_ref).collect();
480 };
481 unsafe { sb::C_Paragraph_getFonts(self.native(), VecSink::new(&mut set_fn).native_mut()) }
482 result
483 }
484}
485
486pub type VisitorInfo = Handle<sb::skia_textlayout_Paragraph_VisitorInfo>;
487
488impl NativeDrop for sb::skia_textlayout_Paragraph_VisitorInfo {
489 fn drop(&mut self) {
490 panic!("Internal error, Paragraph visitor can't be created in Rust")
491 }
492}
493
494impl fmt::Debug for VisitorInfo {
495 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496 f&mut DebugStruct<'_, '_>.debug_struct("VisitorInfo")
497 .field("font", &self.font())
498 .field("origin", &self.origin())
499 .field("advance_x", &self.advance_x())
500 .field("count", &self.count())
501 .field("glyphs", &self.glyphs())
502 .field("positions", &self.positions())
503 .field("utf8_starts", &self.utf8_starts())
504 .field(name:"flags", &self.flags())
505 .finish()
506 }
507}
508
509impl VisitorInfo {
510 pub fn font(&self) -> &Font {
511 Font::from_native_ref(unsafe { &*self.native().font })
512 }
513
514 pub fn origin(&self) -> Point {
515 Point::from_native_c(self.native().origin)
516 }
517
518 pub fn advance_x(&self) -> scalar {
519 self.native().advanceX
520 }
521
522 pub fn count(&self) -> usize {
523 self.native().count as usize
524 }
525
526 pub fn glyphs(&self) -> &[u16] {
527 unsafe { safer::from_raw_parts(self.native().glyphs, self.count()) }
528 }
529
530 pub fn positions(&self) -> &[Point] {
531 unsafe {
532 safer::from_raw_parts(
533 Point::from_native_ptr(self.native().positions),
534 self.count(),
535 )
536 }
537 }
538
539 pub fn utf8_starts(&self) -> &[u32] {
540 unsafe { safer::from_raw_parts(self.native().utf8Starts, self.count() + 1) }
541 }
542
543 pub fn flags(&self) -> VisitorFlags {
544 VisitorFlags::from_bits_truncate(self.native().flags)
545 }
546}
547
548pub type ExtendedVisitorInfo = Handle<sb::skia_textlayout_Paragraph_ExtendedVisitorInfo>;
549
550impl NativeDrop for sb::skia_textlayout_Paragraph_ExtendedVisitorInfo {
551 fn drop(&mut self) {
552 panic!("Internal error, Paragraph extended visitor info can't be created in Rust")
553 }
554}
555
556impl fmt::Debug for ExtendedVisitorInfo {
557 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
558 f&mut DebugStruct<'_, '_>.debug_struct("VisitorInfo")
559 .field("font", &self.font())
560 .field("origin", &self.origin())
561 .field("advance", &self.advance())
562 .field("count", &self.count())
563 .field("glyphs", &self.glyphs())
564 .field("positions", &self.positions())
565 .field("bounds", &self.bounds())
566 .field("utf8_starts", &self.utf8_starts())
567 .field(name:"flags", &self.flags())
568 .finish()
569 }
570}
571
572impl ExtendedVisitorInfo {
573 pub fn font(&self) -> &Font {
574 Font::from_native_ref(unsafe { &*self.native().font })
575 }
576
577 pub fn origin(&self) -> Point {
578 Point::from_native_c(self.native().origin)
579 }
580
581 pub fn advance(&self) -> Size {
582 Size::from_native_c(self.native().advance)
583 }
584
585 pub fn count(&self) -> usize {
586 self.native().count as usize
587 }
588
589 pub fn glyphs(&self) -> &[u16] {
590 unsafe { safer::from_raw_parts(self.native().glyphs, self.count()) }
591 }
592
593 pub fn positions(&self) -> &[Point] {
594 unsafe {
595 safer::from_raw_parts(
596 Point::from_native_ptr(self.native().positions),
597 self.count(),
598 )
599 }
600 }
601
602 pub fn bounds(&self) -> &[Rect] {
603 let ptr = Rect::from_native_ptr(self.native().bounds);
604 unsafe { safer::from_raw_parts(ptr, self.count()) }
605 }
606
607 pub fn utf8_starts(&self) -> &[u32] {
608 unsafe { safer::from_raw_parts(self.native().utf8Starts, self.count() + 1) }
609 }
610
611 pub fn flags(&self) -> VisitorFlags {
612 VisitorFlags::from_bits_truncate(self.native().flags)
613 }
614}
615
616bitflags! {
617 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
618 pub struct VisitorFlags: u32 {
619 const WHITE_SPACE = sb::skia_textlayout_Paragraph_VisitorFlags_kWhiteSpace_VisitorFlag as _;
620 }
621}
622
623#[derive(Clone, PartialEq, Debug)]
624pub struct GlyphClusterInfo {
625 pub bounds: Rect,
626 pub text_range: TextRange,
627 pub position: TextDirection,
628}
629
630impl GlyphClusterInfo {
631 fn from_native_ref(native: &sb::skia_textlayout_Paragraph_GlyphClusterInfo) -> Self {
632 unsafe {
633 Self {
634 bounds: *Rect::from_native_ptr(&native.fBounds),
635 text_range: TextRange {
636 start: native.fClusterTextRange.start,
637 end: native.fClusterTextRange.end,
638 },
639 position: native.fGlyphClusterPosition,
640 }
641 }
642 }
643}
644
645/// The glyph and grapheme cluster information associated with a unicode
646/// codepoint in the paragraph.
647#[repr(C)]
648#[derive(Clone, PartialEq, Debug)]
649pub struct GlyphInfo {
650 pub grapheme_layout_bounds: Rect,
651 pub grapheme_cluster_text_range: TextRange,
652 pub text_direction: TextDirection,
653 pub is_ellipsis: bool,
654}
655native_transmutable!(
656 sb::skia_textlayout_Paragraph_GlyphInfo,
657 GlyphInfo,
658 glyph_info_layout
659);
660
661#[derive(Clone, PartialEq, Debug)]
662pub struct FontInfo {
663 pub font: Font,
664 pub text_range: TextRange,
665}
666
667impl FontInfo {
668 pub fn new(font: Font, text_range: TextRange) -> Self {
669 Self { font, text_range }
670 }
671
672 fn from_native_ref(native: &sb::skia_textlayout_Paragraph_FontInfo) -> Self {
673 Self {
674 font: Font::from_native_ref(&native.fFont).clone(),
675 text_range: TextRange {
676 start: native.fTextRange.start,
677 end: native.fTextRange.end,
678 },
679 }
680 }
681}
682
683#[cfg(test)]
684mod tests {
685 use super::Paragraph;
686 use crate::{
687 icu,
688 textlayout::{FontCollection, ParagraphBuilder, ParagraphStyle, TextStyle},
689 FontMgr,
690 };
691
692 #[test]
693 #[serial_test::serial]
694 fn test_line_metrics() {
695 let paragraph = mk_lorem_ipsum_paragraph();
696 let line_metrics = paragraph.get_line_metrics();
697 for (line, lm) in line_metrics.iter().enumerate() {
698 println!("line {}: width: {}", line + 1, lm.width)
699 }
700 }
701
702 /// Regression test for <https://github.com/rust-skia/rust-skia/issues/585>
703 #[test]
704 #[serial_test::serial]
705 fn test_style_metrics() {
706 icu::init();
707
708 let mut style = ParagraphStyle::new();
709 let ts = TextStyle::new();
710 style.set_text_style(&ts);
711 let mut font_collection = FontCollection::new();
712 font_collection.set_default_font_manager(FontMgr::default(), None);
713 let mut paragraph_builder = ParagraphBuilder::new(&style, font_collection);
714 paragraph_builder.add_text("Lorem ipsum dolor sit amet\n");
715 let mut paragraph = paragraph_builder.build();
716 paragraph.layout(100.0);
717
718 let line_metrics = &paragraph.get_line_metrics()[0];
719 line_metrics.get_style_metrics(line_metrics.start_index..line_metrics.end_index);
720 }
721
722 #[test]
723 #[serial_test::serial]
724 fn test_font_infos() {
725 let paragraph = mk_lorem_ipsum_paragraph();
726 let infos = paragraph.get_fonts();
727 assert!(!infos.is_empty())
728 }
729
730 #[test]
731 #[serial_test::serial]
732 fn test_visit() {
733 let mut paragraph = mk_lorem_ipsum_paragraph();
734 let visitor = |line, info| {
735 println!("line {}: {:?}", line, info);
736 };
737 paragraph.visit(visitor);
738 }
739
740 #[test]
741 #[serial_test::serial]
742 fn test_extended_visit() {
743 let mut paragraph = mk_lorem_ipsum_paragraph();
744 let visitor = |line, info| {
745 println!("line {}: {:?}", line, info);
746 };
747 paragraph.extended_visit(visitor);
748 }
749
750 fn mk_lorem_ipsum_paragraph() -> Paragraph {
751 icu::init();
752
753 let mut font_collection = FontCollection::new();
754 font_collection.set_default_font_manager(FontMgr::new(), None);
755 let paragraph_style = ParagraphStyle::new();
756 let mut paragraph_builder = ParagraphBuilder::new(&paragraph_style, font_collection);
757 let ts = TextStyle::new();
758 paragraph_builder.push_style(&ts);
759 paragraph_builder.add_text(LOREM_IPSUM);
760 let mut paragraph = paragraph_builder.build();
761 paragraph.layout(256.0);
762
763 return paragraph;
764
765 static LOREM_IPSUM: &str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur at leo at nulla tincidunt placerat. Proin eget purus augue. Quisque et est ullamcorper, pellentesque felis nec, pulvinar massa. Aliquam imperdiet, nulla ut dictum euismod, purus dui pulvinar risus, eu suscipit elit neque ac est. Nullam eleifend justo quis placerat ultricies. Vestibulum ut elementum velit. Praesent et dolor sit amet purus bibendum mattis. Aliquam erat volutpat.";
766 }
767
768 /// <https://github.com/rust-skia/rust-skia/issues/984>
769 #[test]
770 #[serial_test::serial]
771 fn skia_crash_macos() {
772 let mut font_collection = FontCollection::new();
773 font_collection.set_dynamic_font_manager(FontMgr::default());
774 let mut p = ParagraphBuilder::new(&ParagraphStyle::new(), font_collection);
775 p.add_text("👋test test 🦀");
776 let mut paragraph = p.build();
777 paragraph.layout(200.);
778 }
779}
780