diff --git a/Cargo.toml b/Cargo.toml index 3706cce7..48f24d36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,6 @@ byteorder = "1" euclid = "0.10" memmap = "0.5" +[dev-dependencies] +quickcheck = "0.4" + diff --git a/examples/dump-outlines.rs b/examples/dump-outlines.rs index cb75f237..118219d6 100644 --- a/examples/dump-outlines.rs +++ b/examples/dump-outlines.rs @@ -1,3 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + extern crate memmap; extern crate pathfinder; diff --git a/src/atlas.rs b/src/atlas.rs new file mode 100644 index 00000000..09936b36 --- /dev/null +++ b/src/atlas.rs @@ -0,0 +1,91 @@ +// Copyright 2017 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use euclid::{Point2D, Rect, Size2D}; + +/// TODO(pcwalton): Track width of last shelf. +pub struct Atlas { + free_rects: Vec>, + available_width: u32, + shelf_height: u32, + shelf_count: u32, +} + +impl Atlas { + #[inline] + pub fn new(available_width: u32, shelf_height: u32) -> Atlas { + Atlas { + free_rects: vec![], + available_width: available_width, + shelf_height: shelf_height, + shelf_count: 0, + } + } + + pub fn place(&mut self, size: &Size2D) -> Result, ()> { + let chosen_index_and_rect = + self.free_rects + .iter() + .enumerate() + .filter(|&(_, rect)| { + size.width <= rect.size.width && size.height <= rect.size.height + }) + .min_by(|&(_, a), &(_, b)| area(a).cmp(&area(b))) + .map(|(index, rect)| (index, *rect)); + + let chosen_rect; + match chosen_index_and_rect { + None => { + // Make a new shelf. + chosen_rect = Rect::new(Point2D::new(0, self.shelf_height * self.shelf_count), + Size2D::new(self.available_width, self.shelf_height)); + self.shelf_count += 1 + } + Some((index, rect)) => { + self.free_rects.swap_remove(index); + chosen_rect = rect; + } + } + + // Guillotine to bottom. + let free_below = + Rect::new(Point2D::new(chosen_rect.origin.x, chosen_rect.origin.y + size.height), + Size2D::new(size.width, chosen_rect.size.height - size.height)); + if !free_below.is_empty() { + self.free_rects.push(free_below); + } + + // Guillotine to right. + let free_to_right = + Rect::new(Point2D::new(chosen_rect.origin.x + size.width, chosen_rect.origin.y), + Size2D::new(chosen_rect.size.width - size.width, chosen_rect.size.height)); + if !free_to_right.is_empty() { + self.free_rects.push(free_to_right); + } + + Ok(chosen_rect.origin) + } + + #[inline] + pub fn available_width(&self) -> u32 { + self.available_width + } + + #[inline] + pub fn shelf_height(&self) -> u32 { + self.shelf_height + } +} + +#[inline] +fn area(rect: &Rect) -> u32 { + rect.size.width * rect.size.height +} + diff --git a/src/lib.rs b/src/lib.rs index 6ed8e6dd..d63957ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,12 +8,21 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![feature(iter_min_by)] + #[macro_use] extern crate bitflags; extern crate byteorder; extern crate euclid; +#[cfg(test)] +#[macro_use] +extern crate quickcheck; +pub mod atlas; pub mod batch; pub mod otf; mod util; +#[cfg(test)] +mod tests; + diff --git a/src/tests/atlas.rs b/src/tests/atlas.rs new file mode 100644 index 00000000..2e449563 --- /dev/null +++ b/src/tests/atlas.rs @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +use atlas::Atlas; +use euclid::{Rect, Size2D}; +use std::cmp; + +fn place_objects(available_width: u32, objects: Vec<(u32, u32)>) -> (Atlas, Vec>) { + let objects: Vec<_> = objects.iter() + .map(|&(width, height)| Size2D::new(width, height)) + .collect(); + + let available_width = cmp::max(available_width, + objects.iter().map(|object| object.width).max().unwrap_or(0)); + let shelf_height = objects.iter().map(|object| object.height).max().unwrap_or(0); + + let mut atlas = Atlas::new(available_width, shelf_height); + let rects = objects.iter() + .map(|object| Rect::new(atlas.place(object).unwrap(), *object)) + .collect(); + (atlas, rects) +} + +quickcheck! { + fn objects_dont_overlap(available_width: u32, objects: Vec<(u32, u32)>) -> bool { + let (_, rects) = place_objects(available_width, objects); + for (i, a) in rects.iter().enumerate() { + for b in &rects[(i + 1)..] { + assert!(!a.intersects(b)) + } + } + true + } + + fn objects_dont_exceed_available_width(available_width: u32, objects: Vec<(u32, u32)>) -> bool { + let (atlas, rects) = place_objects(available_width, objects); + rects.iter().all(|rect| rect.max_x() <= atlas.available_width()) + } + + fn objects_dont_cross_shelves(available_width: u32, objects: Vec<(u32, u32)>) -> bool { + let (atlas, rects) = place_objects(available_width, objects); + rects.iter().all(|rect| { + rect.is_empty() || + rect.origin.y / atlas.shelf_height() == (rect.max_y() - 1) / atlas.shelf_height() + }) + } +} + diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 00000000..abddfd58 --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,12 @@ +// Copyright 2017 The Servo Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +mod atlas; +