Skip to main content

editorial/
editorial.rs

1//! A portable prepared-text flow example. The deterministic monospace measurer
2//! illustrates geometry; applications use the native text adapter for typography.
3
4use std::error::Error;
5use std::rc::Rc;
6
7use ferroui_pretext::{
8    DelegateTextMeasurerFactory, PrepareOptions, PretextLayout, RichInlineBreakMode, RichInlineItem,
9};
10use ferroui_pretext_layout::{CircleObstacle, ColumnFlowLayout, PreparedTextMetrics, RectObstacle};
11
12fn main() -> Result<(), Box<dyn Error>> {
13    let engine = PretextLayout::new(Rc::new(DelegateTextMeasurerFactory::new(
14        |text: &str, _: &str| text.chars().count() as f64 * 8.0,
15    )));
16    let prepared = engine.prepare_with_segments(
17        "Prepare this paragraph once. Reflow it through columns as the exclusion \
18         shape moves. Width changes reuse the prepared measurements, and only \
19         the visible line ranges need their text materialized. This is useful \
20         for editorial pages, canvas labels and virtualized cards.",
21        "16px Monospace",
22        PrepareOptions::default(),
23    )?;
24    let columns = [
25        RectObstacle {
26            x: 0.0,
27            y: 0.0,
28            width: 240.0,
29            height: 220.0,
30        },
31        RectObstacle {
32            x: 264.0,
33            y: 0.0,
34            width: 240.0,
35            height: 220.0,
36        },
37    ];
38    let rectangles = [RectObstacle {
39        x: 0.0,
40        y: 0.0,
41        width: 80.0,
42        height: 40.0,
43    }];
44
45    println!("Illustrative monospace geometry (8 units per Unicode scalar)");
46    println!("Prepared segments: {}", prepared.segments().len());
47    for width in [120.0, 180.0, 240.0] {
48        let metrics = PreparedTextMetrics::collect_wrap_metrics(&prepared, width, 20.0);
49        println!(
50            "Width {width:>3.0}: {} lines, height {:.0}, widest {:.0}",
51            metrics.line_count, metrics.height, metrics.max_line_width,
52        );
53    }
54
55    for circle_y in [70.0, 150.0] {
56        let circles = [CircleObstacle {
57            x: 150.0,
58            y: circle_y,
59            radius: 45.0,
60        }];
61        let lines = ColumnFlowLayout::layout_into_columns(
62            &prepared,
63            &columns,
64            &rectangles,
65            &circles,
66            20.0,
67            48.0,
68        )?;
69        println!(
70            "\nCircle centered at (150, {circle_y:.0}); {} positioned lines:",
71            lines.len()
72        );
73        for line in lines {
74            println!(
75                "  x={:>3.0} y={:>3.0} w={:>3.0}  {}",
76                line.x, line.y, line.width, line.text
77            );
78        }
79    }
80
81    let inline = engine.prepare_rich_inline(&[
82        RichInlineItem::new("Assigned to ", "16px Monospace"),
83        RichInlineItem::new("@maya", "16px Monospace")
84            .with_break_mode(RichInlineBreakMode::Never)
85            .with_extra_width(16.0),
86        RichInlineItem::new(" for the next editorial review.", "16px Monospace"),
87    ])?;
88    println!("\nRich inline flow with an atomic badge:");
89    PretextLayout::walk_rich_inline_line_ranges(&inline, 160.0, |range| {
90        let line = PretextLayout::materialize_rich_inline_line_range(&inline, &range);
91        print!("  width={:>3.0}", line.width);
92        for fragment in line.fragments {
93            print!(
94                "  [item {} gap {:.0}: {}]",
95                fragment.item_index, fragment.gap_before, fragment.text
96            );
97        }
98        println!();
99    });
100    Ok(())
101}