Implement the atlas

This commit is contained in:
Patrick Walton 2017-01-07 11:52:45 -08:00
parent 34cd1ef9b0
commit ce811c0e48
6 changed files with 166 additions and 0 deletions

View File

@ -9,3 +9,6 @@ byteorder = "1"
euclid = "0.10"
memmap = "0.5"
[dev-dependencies]
quickcheck = "0.4"

View File

@ -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;

91
src/atlas.rs Normal file
View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<Rect<u32>>,
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<u32>) -> Result<Point2D<u32>, ()> {
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>) -> u32 {
rect.size.width * rect.size.height
}

View File

@ -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;

48
src/tests/atlas.rs Normal file
View File

@ -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<Rect<u32>>) {
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()
})
}
}

12
src/tests/mod.rs Normal file
View File

@ -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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
mod atlas;