1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
use rusttype::Scale;

use crate::{
    collision::CollisionStrategy, geometry::Rectangle, placement::PlacementStrategy,
    visualization::Canvas, words::FontProvider, words::WordsProvider,
};

/// The cloud generation engine.
pub struct CloudBuilder {
    viewport: Rectangle,
    listeners: Vec<Box<dyn CanvasListener>>,
}

impl CloudBuilder {
    pub fn new(viewport: Rectangle) -> CloudBuilder {
        CloudBuilder {
            viewport,
            listeners: vec![],
        }
    }

    pub fn add_listener<T: CanvasListener + 'static>(&mut self, listener: T) {
        self.listeners.push(Box::new(listener));
    }

    fn trigger_updated(&self, canvas: &Canvas) {
        for l in self.listeners.iter() {
            l.canvas_updated(canvas);
        }
    }

    pub fn run<WP, PS, CS>(
        &self,
        provider: &mut WP,
        mut placer: PS,
        mut collider: CS,
        font_provider: &FontProvider,
    ) -> Canvas
    where
        WP: WordsProvider,
        PS: PlacementStrategy,
        CS: CollisionStrategy,
    {
        let mut canvas = Canvas::new();

        while let Some(word) = provider.get_next_word() {
            let mut place_found = false;
            let mut pos = None;
            let Ok(font) = font_provider.get_font(&word.font_name) else {
                panic!("Font not found: {}", word.font_name);
            };

            let r = font.get_bounding_box(&word, Scale::uniform(word.size as f32));

            while !place_found && self.viewport.collides(&r) {
                pos = placer.find_next_place(pos, &r);

                let Some(upos) = pos else { break };

                if !collider.collides(upos, &r) {
                    if cfg!(debug_assertions) {
                        println!(
                            "Place found for word '{}': [{}, {}]",
                            word.text, upos.0, upos.1
                        );
                    }
                    place_found = true;
                    canvas.add(&word.displace(upos));
                    collider.add(upos, r.clone());

                    self.trigger_updated(&canvas);
                }
            }

            if cfg!(debug_assertions) && !place_found {
                println!("No place found for word '{}'", word.text);
            }
        }

        canvas
    }
}

pub trait CanvasListener {
    fn canvas_updated(&self, canvas: &Canvas);
}