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 /// Returns: a number glyphs that could not be converted to path
256 pub fn get_path_at(&mut self, line_number: usize) -> (usize, Path) {
257 let mut path = Path::default();
258 let unconverted_glyphs = unsafe {
259 sb::C_Paragraph_getPath(
260 self.native_mut(),
261 line_number.try_into().unwrap(),
262 path.native_mut(),
263 )
264 };
265 (unconverted_glyphs.try_into().unwrap(), path)
266 }
267
268 /// Returns path for a text blob
269 ///
270 /// * `text_blob` - a text blob
271 /// Returns: a path
272 pub fn get_path(text_blob: &mut TextBlob) -> Path {
273 Path::construct(|p| unsafe { sb::C_Paragraph_GetPath(text_blob.native_mut(), p) })
274 }
275
276 /// Checks if a given text blob contains
277 /// glyph with emoji
278 ///
279 /// * `text_blob` - a text blob
280 /// Returns: `true` if there is such a glyph
281 pub fn contains_emoji(&mut self, text_blob: &mut TextBlob) -> bool {
282 unsafe { sb::C_Paragraph_containsEmoji(self.native_mut(), text_blob.native_mut()) }
283 }
284
285 /// Checks if a given text blob contains colored font or bitmap
286 ///
287 /// * `text_blob` - a text blob
288 /// Returns: `true` if there is such a glyph
289 pub fn contains_color_font_or_bitmap(&mut self, text_blob: &mut TextBlob) -> bool {
290 unsafe {
291 sb::C_Paragraph_containsColorFontOrBitmap(self.native_mut(), text_blob.native_mut())
292 }
293 }
294
295 /// Finds the line number of the line that contains the given UTF-8 index.
296 ///
297 /// * `index` - a UTF-8 TextIndex into the paragraph
298 /// Returns: the line number the glyph that corresponds to the
299 /// given `code_unit_index` is in, or -1 if the `code_unit_index`
300 /// is out of bounds, or when the glyph is truncated or
301 /// ellipsized away.
302 pub fn get_line_number_at(&self, code_unit_index: TextIndex) -> Option<usize> {
303 // Returns -1 if `code_unit_index` is out of range.
304 unsafe { sb::C_Paragraph_getLineNumberAt(self.native(), code_unit_index) }
305 .try_into()
306 .ok()
307 }
308
309 /// Finds the line number of the line that contains the given UTF-16 index.
310 ///
311 /// * `index` - a UTF-16 offset into the paragraph
312 /// Returns: the line number the glyph that corresponds to the
313 /// given `code_unit_index` is in, or -1 if the `code_unit_index`
314 /// is out of bounds, or when the glyph is truncated or
315 /// ellipsized away.
316 pub fn get_line_number_at_utf16_offset(&self, code_unit_index: TextIndex) -> Option<usize> {
317 // Returns -1 if `code_unit_index` is out of range.
318 unsafe {
319 sb::C_Paragraph_getLineNumberAtUTF16Offset(self.native_mut_force(), code_unit_index)
320 }
321 .try_into()
322 .ok()
323 }
324
325 /// Returns line metrics info for the line
326 ///
327 /// * `line_number` - a line number
328 /// * `line_metrics` - an address to return the info (in case of null just skipped)
329 /// Returns: `true` if the line is found; `false` if not
330 pub fn get_line_metrics_at(&self, line_number: usize) -> Option<LineMetrics> {
331 let mut r = None;
332 let mut set_lm = |lm: &sb::skia_textlayout_LineMetrics| {
333 r = Some(LineMetrics::from_native_ref(lm));
334 };
335 unsafe {
336 sb::C_Paragraph_getLineMetricsAt(
337 self.native(),
338 line_number,
339 Sink::new(&mut set_lm).native_mut(),
340 )
341 }
342 r
343 }
344
345 /// Returns the visible text on the line (excluding a possible ellipsis)
346 ///
347 /// * `line_number` - a line number
348 /// * `include_spaces` - indicates if the whitespaces should be included
349 /// Returns: the range of the text that is shown in the line
350 pub fn get_actual_text_range(&self, line_number: usize, include_spaces: bool) -> TextRange {
351 let mut range = [0usize; 2];
352 unsafe {
353 sb::C_Paragraph_getActualTextRange(
354 self.native(),
355 line_number,
356 include_spaces,
357 range.as_mut_ptr(),
358 )
359 }
360 TextRange {
361 start: range[0],
362 end: range[1],
363 }
364 }
365
366 /// Finds a glyph cluster for text index
367 ///
368 /// * `code_unit_index` - a text index
369 /// * `glyph_info` - a glyph cluster info filled if not null
370 /// Returns: `true` if glyph cluster was found; `false` if not
371 pub fn get_glyph_cluster_at(&self, code_unit_index: TextIndex) -> Option<GlyphClusterInfo> {
372 let mut r = None;
373 let mut set_fn = |gci: &sb::skia_textlayout_Paragraph_GlyphClusterInfo| {
374 r = Some(GlyphClusterInfo::from_native_ref(gci))
375 };
376 unsafe {
377 sb::C_Paragraph_getGlyphClusterAt(
378 self.native(),
379 code_unit_index,
380 Sink::new(&mut set_fn).native_mut(),
381 )
382 }
383 r
384 }
385
386 /// Finds the closest glyph cluster for a visual text position
387 ///
388 /// * `dx` - x coordinate
389 /// * `dy` - y coordinate
390 /// * `glyph_info` - a glyph cluster info filled if not null
391 /// Returns: `true` if glyph cluster was found; `false` if not
392 /// (which usually means the paragraph is empty)
393 pub fn get_closest_glyph_cluster_at(&self, d: impl Into<Point>) -> Option<GlyphClusterInfo> {
394 let mut r = None;
395 let mut set_fn = |gci: &sb::skia_textlayout_Paragraph_GlyphClusterInfo| {
396 r = Some(GlyphClusterInfo::from_native_ref(gci))
397 };
398 let d = d.into();
399 unsafe {
400 sb::C_Paragraph_getClosestGlyphClusterAt(
401 self.native(),
402 d.x,
403 d.y,
404 Sink::new(&mut set_fn).native_mut(),
405 )
406 }
407 r
408 }
409
410 /// Retrieves the information associated with the glyph located at the given
411 /// `code_unit_index`.
412 ///
413 /// * `code_unit_index` - a UTF-16 offset into the paragraph
414 /// * `glyph_info` - an optional GlyphInfo struct to hold the
415 /// information associated with the glyph found at the
416 /// given index
417 /// Returns: `false` only if the offset is out of bounds
418 pub fn get_glyph_info_at_utf16_offset(&mut self, code_unit_index: usize) -> Option<GlyphInfo> {
419 GlyphInfo::try_construct(|gi| unsafe {
420 sb::C_Paragraph_getGlyphInfoAtUTF16Offset(self.native_mut(), code_unit_index, gi)
421 })
422 }
423
424 /// Finds the information associated with the closest glyph to the given
425 /// paragraph coordinates.
426 ///
427 /// * `d` - x/y coordinate
428 /// * `glyph_info` - an optional GlyphInfo struct to hold the
429 /// information associated with the glyph found. The
430 /// text indices and text ranges are described using
431 /// UTF-16 offsets
432 /// Returns: `true` if a grapheme cluster was found; `false` if not
433 /// (which usually means the paragraph is empty)
434 pub fn get_closest_utf16_glyph_info_at(&mut self, d: impl Into<Point>) -> Option<GlyphInfo> {
435 let d = d.into();
436 GlyphInfo::try_construct(|gi| unsafe {
437 sb::C_Paragraph_getClosestUTF16GlyphInfoAt(self.native_mut(), d.x, d.y, gi)
438 })
439 }
440
441 /// Returns the font that is used to shape the text at the position
442 ///
443 /// * `code_unit_index` - text index
444 /// Returns: font info or an empty font info if the text is not found
445 pub fn get_font_at(&self, code_unit_index: TextIndex) -> Font {
446 Font::construct(|f| unsafe { sb::C_Paragraph_getFontAt(self.native(), code_unit_index, f) })
447 }
448
449 /// Returns the font used to shape the text at the given UTF-16 offset.
450 ///
451 /// * `code_unit_index` - a UTF-16 offset in the paragraph
452 /// Returns: font info or an empty font info if the text is not found
453 pub fn get_font_at_utf16_offset(&mut self, code_unit_index: usize) -> Font {
454 Font::construct(|f| unsafe {
455 sb::C_Paragraph_getFontAtUTF16Offset(self.native_mut(), code_unit_index, f)
456 })
457 }
458
459 /// Returns the information about all the fonts used to shape the paragraph text
460 ///
461 /// Returns: a list of fonts and text ranges
462 pub fn get_fonts(&self) -> Vec<FontInfo> {
463 let mut result = Vec::new();
464 let mut set_fn = |fis: &[sb::skia_textlayout_Paragraph_FontInfo]| {
465 result = fis.iter().map(FontInfo::from_native_ref).collect();
466 };
467 unsafe { sb::C_Paragraph_getFonts(self.native(), VecSink::new(&mut set_fn).native_mut()) }
468 result
469 }
470}
471
472pub type VisitorInfo = Handle<sb::skia_textlayout_Paragraph_VisitorInfo>;
473
474impl NativeDrop for sb::skia_textlayout_Paragraph_VisitorInfo {
475 fn drop(&mut self) {
476 panic!("Internal error, Paragraph visitor can't be created in Rust")
477 }
478}
479
480impl fmt::Debug for VisitorInfo {
481 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
482 f&mut DebugStruct<'_, '_>.debug_struct("VisitorInfo")
483 .field("font", &self.font())
484 .field("origin", &self.origin())
485 .field("advance_x", &self.advance_x())
486 .field("count", &self.count())
487 .field("glyphs", &self.glyphs())
488 .field("positions", &self.positions())
489 .field("utf8_starts", &self.utf8_starts())
490 .field(name:"flags", &self.flags())
491 .finish()
492 }
493}
494
495impl VisitorInfo {
496 pub fn font(&self) -> &Font {
497 Font::from_native_ref(unsafe { &*self.native().font })
498 }
499
500 pub fn origin(&self) -> Point {
501 Point::from_native_c(self.native().origin)
502 }
503
504 pub fn advance_x(&self) -> scalar {
505 self.native().advanceX
506 }
507
508 pub fn count(&self) -> usize {
509 self.native().count as usize
510 }
511
512 pub fn glyphs(&self) -> &[u16] {
513 unsafe { safer::from_raw_parts(self.native().glyphs, self.count()) }
514 }
515
516 pub fn positions(&self) -> &[Point] {
517 unsafe {
518 safer::from_raw_parts(
519 Point::from_native_ptr(self.native().positions),
520 self.count(),
521 )
522 }
523 }
524
525 pub fn utf8_starts(&self) -> &[u32] {
526 unsafe { safer::from_raw_parts(self.native().utf8Starts, self.count() + 1) }
527 }
528
529 pub fn flags(&self) -> VisitorFlags {
530 VisitorFlags::from_bits_truncate(self.native().flags)
531 }
532}
533
534pub type ExtendedVisitorInfo = Handle<sb::skia_textlayout_Paragraph_ExtendedVisitorInfo>;
535
536impl NativeDrop for sb::skia_textlayout_Paragraph_ExtendedVisitorInfo {
537 fn drop(&mut self) {
538 panic!("Internal error, Paragraph extended visitor info can't be created in Rust")
539 }
540}
541
542impl fmt::Debug for ExtendedVisitorInfo {
543 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
544 f&mut DebugStruct<'_, '_>.debug_struct("VisitorInfo")
545 .field("font", &self.font())
546 .field("origin", &self.origin())
547 .field("advance", &self.advance())
548 .field("count", &self.count())
549 .field("glyphs", &self.glyphs())
550 .field("positions", &self.positions())
551 .field("bounds", &self.bounds())
552 .field("utf8_starts", &self.utf8_starts())
553 .field(name:"flags", &self.flags())
554 .finish()
555 }
556}
557
558impl ExtendedVisitorInfo {
559 pub fn font(&self) -> &Font {
560 Font::from_native_ref(unsafe { &*self.native().font })
561 }
562
563 pub fn origin(&self) -> Point {
564 Point::from_native_c(self.native().origin)
565 }
566
567 pub fn advance(&self) -> Size {
568 Size::from_native_c(self.native().advance)
569 }
570
571 pub fn count(&self) -> usize {
572 self.native().count as usize
573 }
574
575 pub fn glyphs(&self) -> &[u16] {
576 unsafe { safer::from_raw_parts(self.native().glyphs, self.count()) }
577 }
578
579 pub fn positions(&self) -> &[Point] {
580 unsafe {
581 safer::from_raw_parts(
582 Point::from_native_ptr(self.native().positions),
583 self.count(),
584 )
585 }
586 }
587
588 pub fn bounds(&self) -> &[Rect] {
589 let ptr = Rect::from_native_ptr(self.native().bounds);
590 unsafe { safer::from_raw_parts(ptr, self.count()) }
591 }
592
593 pub fn utf8_starts(&self) -> &[u32] {
594 unsafe { safer::from_raw_parts(self.native().utf8Starts, self.count() + 1) }
595 }
596
597 pub fn flags(&self) -> VisitorFlags {
598 VisitorFlags::from_bits_truncate(self.native().flags)
599 }
600}
601
602bitflags! {
603 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
604 pub struct VisitorFlags: u32 {
605 const WHITE_SPACE = sb::skia_textlayout_Paragraph_VisitorFlags_kWhiteSpace_VisitorFlag as _;
606 }
607}
608
609#[derive(Clone, PartialEq, Debug)]
610pub struct GlyphClusterInfo {
611 pub bounds: Rect,
612 pub text_range: TextRange,
613 pub position: TextDirection,
614}
615
616impl GlyphClusterInfo {
617 fn from_native_ref(native: &sb::skia_textlayout_Paragraph_GlyphClusterInfo) -> Self {
618 unsafe {
619 Self {
620 bounds: *Rect::from_native_ptr(&native.fBounds),
621 text_range: TextRange {
622 start: native.fClusterTextRange.start,
623 end: native.fClusterTextRange.end,
624 },
625 position: native.fGlyphClusterPosition,
626 }
627 }
628 }
629}
630
631/// The glyph and grapheme cluster information associated with a unicode
632/// codepoint in the paragraph.
633#[repr(C)]
634#[derive(Clone, PartialEq, Debug)]
635pub struct GlyphInfo {
636 pub grapheme_layout_bounds: Rect,
637 pub grapheme_cluster_text_range: TextRange,
638 pub text_direction: TextDirection,
639 pub is_ellipsis: bool,
640}
641native_transmutable!(
642 sb::skia_textlayout_Paragraph_GlyphInfo,
643 GlyphInfo,
644 glyph_info_layout
645);
646
647#[derive(Clone, PartialEq, Debug)]
648pub struct FontInfo {
649 pub font: Font,
650 pub text_range: TextRange,
651}
652
653impl FontInfo {
654 pub fn new(font: Font, text_range: TextRange) -> Self {
655 Self { font, text_range }
656 }
657
658 fn from_native_ref(native: &sb::skia_textlayout_Paragraph_FontInfo) -> Self {
659 Self {
660 font: Font::from_native_ref(&native.fFont).clone(),
661 text_range: TextRange {
662 start: native.fTextRange.start,
663 end: native.fTextRange.end,
664 },
665 }
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 use super::Paragraph;
672 use crate::{
673 icu,
674 textlayout::{FontCollection, ParagraphBuilder, ParagraphStyle, TextStyle},
675 FontMgr,
676 };
677
678 #[test]
679 #[serial_test::serial]
680 fn test_line_metrics() {
681 let paragraph = mk_lorem_ipsum_paragraph();
682 let line_metrics = paragraph.get_line_metrics();
683 for (line, lm) in line_metrics.iter().enumerate() {
684 println!("line {}: width: {}", line + 1, lm.width)
685 }
686 }
687
688 /// Regression test for <https://github.com/rust-skia/rust-skia/issues/585>
689 #[test]
690 #[serial_test::serial]
691 fn test_style_metrics() {
692 icu::init();
693
694 let mut style = ParagraphStyle::new();
695 let ts = TextStyle::new();
696 style.set_text_style(&ts);
697 let mut font_collection = FontCollection::new();
698 font_collection.set_default_font_manager(FontMgr::default(), None);
699 let mut paragraph_builder = ParagraphBuilder::new(&style, font_collection);
700 paragraph_builder.add_text("Lorem ipsum dolor sit amet\n");
701 let mut paragraph = paragraph_builder.build();
702 paragraph.layout(100.0);
703
704 let line_metrics = &paragraph.get_line_metrics()[0];
705 line_metrics.get_style_metrics(line_metrics.start_index..line_metrics.end_index);
706 }
707
708 #[test]
709 #[serial_test::serial]
710 fn test_font_infos() {
711 let paragraph = mk_lorem_ipsum_paragraph();
712 let infos = paragraph.get_fonts();
713 assert!(!infos.is_empty())
714 }
715
716 #[test]
717 #[serial_test::serial]
718 fn test_visit() {
719 let mut paragraph = mk_lorem_ipsum_paragraph();
720 let visitor = |line, info| {
721 println!("line {}: {:?}", line, info);
722 };
723 paragraph.visit(visitor);
724 }
725
726 #[test]
727 #[serial_test::serial]
728 fn test_extended_visit() {
729 let mut paragraph = mk_lorem_ipsum_paragraph();
730 let visitor = |line, info| {
731 println!("line {}: {:?}", line, info);
732 };
733 paragraph.extended_visit(visitor);
734 }
735
736 fn mk_lorem_ipsum_paragraph() -> Paragraph {
737 icu::init();
738
739 let mut font_collection = FontCollection::new();
740 font_collection.set_default_font_manager(FontMgr::new(), None);
741 let paragraph_style = ParagraphStyle::new();
742 let mut paragraph_builder = ParagraphBuilder::new(&paragraph_style, font_collection);
743 let ts = TextStyle::new();
744 paragraph_builder.push_style(&ts);
745 paragraph_builder.add_text(LOREM_IPSUM);
746 let mut paragraph = paragraph_builder.build();
747 paragraph.layout(256.0);
748
749 return paragraph;
750
751 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.";
752 }
753}
754