word_cloud/
cloud_builder.rs

1use rusttype::Scale;
2
3use crate::{
4    collision::CollisionStrategy, geometry::Rectangle, placement::PlacementStrategy,
5    visualization::Canvas, words::FontProvider, words::WordsProvider,
6};
7
8/// The cloud generation engine.
9pub struct CloudBuilder {
10    viewport: Rectangle,
11    listeners: Vec<Box<dyn CanvasListener>>,
12}
13
14impl CloudBuilder {
15    pub fn new(viewport: Rectangle) -> CloudBuilder {
16        CloudBuilder {
17            viewport,
18            listeners: vec![],
19        }
20    }
21
22    pub fn add_listener<T: CanvasListener + 'static>(&mut self, listener: T) {
23        self.listeners.push(Box::new(listener));
24    }
25
26    fn trigger_updated(&self, canvas: &Canvas) {
27        for l in self.listeners.iter() {
28            l.canvas_updated(canvas);
29        }
30    }
31
32    pub fn run<WP, PS, CS>(
33        &self,
34        provider: &mut WP,
35        mut placer: PS,
36        mut collider: CS,
37        font_provider: &FontProvider,
38    ) -> Canvas
39    where
40        WP: WordsProvider,
41        PS: PlacementStrategy,
42        CS: CollisionStrategy,
43    {
44        let mut canvas = Canvas::new();
45
46        while let Some(word) = provider.get_next_word() {
47            let mut place_found = false;
48            let mut pos = None;
49            let Ok(font) = font_provider.get_font(&word.font_name) else {
50                panic!("Font not found: {}", word.font_name);
51            };
52
53            let r = font.get_bounding_box(&word, Scale::uniform(word.size as f32));
54
55            while !place_found && self.viewport.collides(&r) {
56                pos = placer.find_next_place(pos, &r);
57
58                let Some(upos) = pos else { break };
59
60                if !collider.collides(upos, &r) {
61                    if cfg!(debug_assertions) {
62                        println!(
63                            "Place found for word '{}': [{}, {}]",
64                            word.text, upos.0, upos.1
65                        );
66                    }
67                    place_found = true;
68                    canvas.add(&word.displace(upos));
69                    collider.add(upos, r.clone());
70
71                    self.trigger_updated(&canvas);
72                }
73            }
74
75            if cfg!(debug_assertions) && !place_found {
76                println!("No place found for word '{}'", word.text);
77            }
78        }
79
80        canvas
81    }
82}
83
84pub trait CanvasListener {
85    fn canvas_updated(&self, canvas: &Canvas);
86}