Skip to main content

spritebox/
background_builder.rs

1//! Typed scene authoring, using the same validation and generation as the parser.
2use crate::background::{ScatterContext, check_rect, scatter};
3use crate::scene::Budget;
4use crate::*;
5use std::collections::{HashMap, HashSet};
6use std::sync::Arc;
7
8pub struct BackgroundBuilder<'a> {
9    doc: &'a Document,
10    background: Background,
11    limits: Limits,
12    budget: Budget,
13    sprites: HashMap<String, Arc<Sprite>>,
14    operations: HashSet<String>,
15}
16impl Background {
17    pub fn builder<'a>(
18        doc: &'a Document,
19        name: &str,
20        size: (u32, u32),
21        seed: u64,
22        limits: &Limits,
23    ) -> Result<BackgroundBuilder<'a>> {
24        check_name(name)?;
25        check_size(size, limits)?;
26        let mut budget = Budget::new(limits);
27        budget.pixels(doc.scene_storage())?;
28        budget.commands(doc.scene_commands())?;
29        Ok(BackgroundBuilder {
30            doc,
31            background: Background {
32                commands: 0,
33                name: name.into(),
34                width: size.0,
35                height: size.1,
36                seed,
37                layers: Vec::new(),
38            },
39            limits: limits.clone(),
40            budget,
41            sprites: HashMap::new(),
42            operations: HashSet::new(),
43        })
44    }
45}
46pub(crate) fn check_name(name: &str) -> Result<()> {
47    if name.is_empty()
48        || !name
49            .chars()
50            .all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-'))
51    {
52        return Err(Error::invalid(
53            "Scene name must contain ASCII letters, digits, underscores or hyphens",
54        ));
55    }
56    Ok(())
57}
58fn check_size(size: (u32, u32), limits: &Limits) -> Result<()> {
59    if size.0 == 0 || size.1 == 0 {
60        return Err(Error::invalid("Scene size must be positive"));
61    }
62    limits.check_image(size.0, size.1)
63}
64impl BackgroundBuilder<'_> {
65    pub fn layer(
66        &mut self,
67        name: &str,
68        size: (u32, u32),
69        options: LayerOptions,
70    ) -> Result<&mut Self> {
71        check_name(name)?;
72        check_size(size, &self.limits)?;
73        if size.0 > i32::MAX as u32 || size.1 > i32::MAX as u32 {
74            return Err(Error::limit("Layer coordinates exceed signed range"));
75        }
76        if self.background.layers.len() >= self.limits.max_layers {
77            return Err(Error::limit("Too many background layers"));
78        }
79        if self.background.layers.iter().any(|l| l.name == name) {
80            return Err(Error::new(
81                ErrorCode::DuplicateName,
82                "Duplicate background layer",
83            ));
84        }
85        if let Some(clip) = options.clip {
86            check_rect(clip, size)?;
87        }
88        self.budget.commands(1)?;
89        self.background.commands += 1;
90        self.operations.clear();
91        self.background.layers.push(BackgroundLayer {
92            name: name.into(),
93            width: size.0,
94            height: size.1,
95            options,
96            items: Vec::new(),
97            position: Position::new(1, 1),
98        });
99        Ok(self)
100    }
101    fn resolve(&mut self, source: &SourceRef) -> Result<(SourceRef, Option<Arc<Sprite>>)> {
102        match source {
103            SourceRef::Sprite(name) => {
104                let canonical = crate::text::scene_chain_name(name, &self.limits)?;
105                if let Some(s) = self.sprites.get(&canonical) {
106                    return Ok((SourceRef::Sprite(canonical), Some(s.clone())));
107                }
108                let (name, sprite) =
109                    crate::text::scene_chain(name, self.doc, &self.limits, &mut self.budget)?;
110                let sprite = Arc::new(sprite);
111                self.sprites.insert(name.clone(), sprite.clone());
112                Ok((SourceRef::Sprite(name), Some(sprite)))
113            }
114            _ => Ok((source.clone(), None)),
115        }
116    }
117    pub fn place(&mut self, source: SourceRef, options: PlacementOptions) -> Result<&mut Self> {
118        if self.background.layers.is_empty() {
119            return Err(Error::invalid("Add a layer before placing sources"));
120        }
121        self.budget.commands(1)?;
122        self.background.commands += 1;
123        let (source, sprite) = self.resolve(&source)?;
124        let placement = BackgroundPlacement {
125            source,
126            sprite,
127            options,
128            position: Position::new(1, 1),
129        };
130        placement.crop(placement.view(self.doc)?.size()?)?;
131        self.budget.pixels(1)?;
132        self.background
133            .layers
134            .last_mut()
135            .unwrap()
136            .items
137            .push(placement);
138        Ok(self)
139    }
140    pub fn scatter(
141        &mut self,
142        name: &str,
143        options: &ScatterOptions,
144        variants: &[WeightedVariant],
145    ) -> Result<&mut Self> {
146        check_name(name)?;
147        if !self.operations.insert(name.into()) {
148            return Err(Error::new(
149                ErrorCode::DuplicateName,
150                "Duplicate layer operation",
151            ));
152        }
153        self.budget.commands(
154            variants
155                .len()
156                .checked_add(1)
157                .ok_or_else(|| Error::limit("Scene command overflow"))?,
158        )?;
159        self.background.commands += 1 + variants.len();
160        let mut resolved = Vec::new();
161        for variant in variants {
162            let (source, sprite) = self.resolve(&variant.source)?;
163            resolved.push((
164                WeightedVariant {
165                    source,
166                    weight: variant.weight,
167                },
168                sprite,
169            ));
170        }
171        let layer = self
172            .background
173            .layers
174            .last()
175            .ok_or_else(|| Error::invalid("Add a layer before scattering"))?;
176        let items = scatter(
177            self.doc,
178            ScatterContext {
179                background: &self.background.name,
180                layer,
181                operation: name,
182                owner_seed: self.background.seed,
183                position: Position::new(1, 1),
184            },
185            options,
186            &resolved,
187            &mut self.budget,
188        )?;
189        self.background
190            .layers
191            .last_mut()
192            .unwrap()
193            .items
194            .extend(items);
195        Ok(self)
196    }
197    pub fn finish(self) -> Background {
198        self.background
199    }
200}
201impl Document {
202    pub fn with_surface(&self, mut surface: Surface, limits: &Limits) -> Result<Document> {
203        self.check_new_definition(limits)?;
204        if self.surface(surface.name()).is_some() {
205            return Err(Error::new(ErrorCode::DuplicateName, "Duplicate surface"));
206        }
207        limits.check_image(surface.width, surface.height)?;
208        let palette = self.palette(surface.palette.name()).ok_or_else(|| {
209            Error::new(
210                ErrorCode::UnknownName,
211                "Surface palette must exist in receiver document",
212            )
213        })?;
214        if surface
215            .keys
216            .iter()
217            .any(|&k| k != crate::sprite::EMPTY && palette.color(k).is_none())
218        {
219            return Err(Error::new(
220                ErrorCode::UnknownName,
221                "Surface key is absent from receiver palette",
222            ));
223        }
224        surface.palette = palette.clone();
225        let mut b = Budget::new(limits);
226        b.pixels(self.scene_storage())?;
227        b.pixels(surface.storage())?;
228        b.commands(
229            self.scene_commands()
230                .checked_add(surface.commands)
231                .ok_or_else(|| Error::limit("Scene command overflow"))?,
232        )?;
233        let mut doc = self.clone();
234        doc.surfaces.push(surface);
235        Ok(doc)
236    }
237    pub fn with_background(&self, background: Background, limits: &Limits) -> Result<Document> {
238        self.check_new_definition(limits)?;
239        if self.background(background.name()).is_some() {
240            return Err(Error::new(ErrorCode::DuplicateName, "Duplicate background"));
241        }
242        limits.check_image(background.width, background.height)?;
243        if background.layers.len() > limits.max_layers {
244            return Err(Error::limit("Too many background layers"));
245        }
246        for layer in &background.layers {
247            limits.check_image(layer.width, layer.height)?;
248            if let Some(clip) = layer.options.clip {
249                check_rect(clip, layer.size())?;
250            }
251            for p in &layer.items {
252                p.crop(p.view(self)?.size()?)?;
253                if let Some(s) = &p.sprite {
254                    s.effect_size(s.width, s.height, s.layers.len(), limits)?;
255                    for p in std::iter::once(s.palette()).chain(s.layer_palettes()) {
256                        let palette = self.palette(p.name()).ok_or_else(|| {
257                            Error::new(
258                                ErrorCode::UnknownName,
259                                "Background sprite palette is absent from receiver",
260                            )
261                        })?;
262                        for (owner, key) in s.palette_keys() {
263                            if owner == p.name() && palette.color(key).is_none() {
264                                return Err(Error::new(
265                                    ErrorCode::UnknownName,
266                                    format!(
267                                        "Background key `{key}` is absent from receiver palette"
268                                    ),
269                                ));
270                            }
271                        }
272                    }
273                }
274            }
275        }
276        let mut b = Budget::new(limits);
277        let mut shared = HashSet::new();
278        b.pixels(self.scene_storage_seen(&mut shared))?;
279        b.pixels(background.storage(&mut shared))?;
280        b.commands(
281            self.scene_commands()
282                .checked_add(background.commands)
283                .ok_or_else(|| Error::limit("Scene command overflow"))?,
284        )?;
285        let mut doc = self.clone();
286        doc.backgrounds.push(background);
287        Ok(doc)
288    }
289    fn check_new_definition(&self, limits: &Limits) -> Result<()> {
290        let count = self.palettes.len()
291            + self.sprites.len()
292            + self.animations.len()
293            + self.tilesets.len()
294            + self.tilemaps.len()
295            + self.fonts.len()
296            + self.groups.len()
297            + self.screens.len()
298            + self.themes.len()
299            + self.states.len()
300            + self.icon_sets.len()
301            + self.surfaces.len()
302            + self.backgrounds.len();
303        if count >= limits.max_definitions {
304            Err(Error::limit("More than the definition limit"))
305        } else {
306            Ok(())
307        }
308    }
309}