From 4597414e21b4063992db8ba8caec82f9b60c0e4b Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 12 Dec 2018 14:55:53 -0800 Subject: [PATCH] Quickcheck intervals --- Cargo.lock | 67 ++++++++++++++ utils/tile-svg/Cargo.toml | 4 + utils/tile-svg/src/main.rs | 179 +++++++++++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 27632f61..445ea467 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -192,6 +192,14 @@ dependencies = [ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cmake" version = "0.1.34" @@ -462,6 +470,20 @@ dependencies = [ "servo-freetype-sys 4.0.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "gcc" version = "0.3.54" @@ -1035,6 +1057,17 @@ dependencies = [ "memchr 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "quickcheck" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quote" version = "0.6.8" @@ -1043,6 +1076,31 @@ dependencies = [ "proc-macro2 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "rayon" version = "0.7.1" @@ -1396,6 +1454,8 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", "quick-xml 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)", + "quickcheck 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "svgtypes 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1596,6 +1656,7 @@ source = "git+https://github.com/SergioBenitez/ring?branch=v0.12#9ccfa153a27aecc "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" "checksum cfg-if 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0c4e7bb64a8ebb0d856483e1e682ea3422f883c5f5615a90d51a2c82fe87fdd3" "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cmake 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "848b314ea70f48f0e13828c5554e34200952ce5720d6d3aa466b4d983af6c70e" "checksum cocoa 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "53a840785348e998a1433d1f9d0b350fd83e91711fae8507c76ce510afc77e72" "checksum color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" @@ -1624,6 +1685,8 @@ source = "git+https://github.com/SergioBenitez/ring?branch=v0.12#9ccfa153a27aecc "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" "checksum freetype 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "11926b2b410b469d0e9399eca4cbbe237a9ef02176c485803b29216307e8e028" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" "checksum gdk-pixbuf 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "16160d212ae91abe9f3324c3fb233929ba322dde63585d15cda3336f8c529ed1" "checksum gdk-pixbuf-sys 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "798f97101eea8180da363d0e80e07ec7ec6d1809306601c0100c1de5bc8b4f52" @@ -1682,7 +1745,11 @@ source = "git+https://github.com/SergioBenitez/ring?branch=v0.12#9ccfa153a27aecc "checksum proc-macro2 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "ffe022fb8c8bd254524b0b3305906c1921fa37a84a644e29079a9e62200c3901" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quick-xml 0.12.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1d8065cbb01701c11cc195cde85cbf39d1c6a80705b67a157ebb3042e0e5777f" +"checksum quickcheck 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4537d3e4edf73a15dd059b75bed1c292d17d3ea7517f583cebe716794fcf816" "checksum quote 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "dd636425967c33af890042c483632d33fa7a18f19ad1d7ea72e8998c6ef8dea5" +"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" +"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" +"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" "checksum rayon 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a77c51c07654ddd93f6cb543c7a849863b03abc7e82591afda6dc8ad4ac3ac4a" "checksum rayon 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "df7a791f788cb4c516f0e091301a29c2b71ef680db5e644a7d68835c8ae6dbfa" "checksum rayon-core 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b055d1e92aba6877574d8fe604a63c8b5df60f60e5982bf7ccbb1338ea527356" diff --git a/utils/tile-svg/Cargo.toml b/utils/tile-svg/Cargo.toml index 0a0d2929..607d0cd0 100644 --- a/utils/tile-svg/Cargo.toml +++ b/utils/tile-svg/Cargo.toml @@ -9,3 +9,7 @@ bitflags = "1.0" euclid = "0.19" quick-xml = "0.12" svgtypes = "0.2" + +[dev-dependencies] +quickcheck = "0.7" +rand = "0.5" diff --git a/utils/tile-svg/src/main.rs b/utils/tile-svg/src/main.rs index 3cbc278d..07cf30dd 100644 --- a/utils/tile-svg/src/main.rs +++ b/utils/tile-svg/src/main.rs @@ -11,11 +11,17 @@ #[macro_use] extern crate bitflags; +#[cfg(test)] +extern crate quickcheck; +#[cfg(test)] +extern crate rand; + use euclid::{Point2D, Transform2D}; use quick_xml::Reader; use quick_xml::events::Event; use std::env; use std::mem; +use std::ops::Range; use std::path::{Path, PathBuf}; use std::str::FromStr; use svgtypes::{Color as SvgColor, PathParser, PathSegment as SvgPathSegment, TransformListParser}; @@ -378,3 +384,176 @@ impl Contour { self.flags.push(flags); } } + +// Tiling + +struct Tiler { + outline: Outline, +} + +impl Tiler { + fn from_outline(outline: Outline) -> Tiler { + Tiler { + outline, + } + } +} + +// Intervals + +#[derive(Debug)] +struct Intervals { + ranges: Vec, +} + +#[derive(Clone, Copy, Debug)] +struct IntervalRange { + start: f32, + end: f32, + winding: f32, +} + +impl Intervals { + fn new(end: f32) -> Intervals { + Intervals { + ranges: vec![IntervalRange::new(0.0, end, 0.0)], + } + } + + fn add(&mut self, range: IntervalRange) { + self.split_at(range.start); + self.split_at(range.end); + + // Find bracketing range. + let mut start_index = 0; + while range.start < self.ranges[start_index].start { + start_index += 1 + } + let mut end_index = start_index; + while range.end < self.ranges[end_index].end { + end_index += 1 + } + + // Adjust winding numbers. + for existing_range in &mut self.ranges[start_index..(end_index + 1)] { + existing_range.winding += range.winding + } + + self.merge_adjacent(); + } + + fn clear(&mut self) { + let end = self.ranges.last().unwrap().end; + self.ranges.truncate(1); + self.ranges[0] = IntervalRange::new(0.0, end, 0.0); + } + + fn split_at(&mut self, value: f32) { + let mut range_index = 0; + while range_index < self.ranges.len() { + let IntervalRange { + start: old_start, + end: old_end, + winding, + } = self.ranges[range_index]; + if value < old_start || value > old_end { + range_index += 1; + continue + } + + self.ranges[range_index] = IntervalRange::new(old_start, value, winding); + self.ranges.insert(range_index + 1, IntervalRange::new(value, old_end, winding)); + return + } + } + + fn merge_adjacent(&mut self) { + let mut dest_range_index = 0; + let mut current_range = self.ranges[0]; + for src_range_index in 1..self.ranges.len() { + if self.ranges[src_range_index].winding == current_range.winding { + current_range.end = self.ranges[src_range_index].end + } else { + self.ranges[dest_range_index] = current_range; + dest_range_index += 1; + current_range = self.ranges[src_range_index]; + } + } + self.ranges[dest_range_index] = current_range; + dest_range_index += 1; + self.ranges.truncate(dest_range_index); + } +} + +impl IntervalRange { + fn new(start: f32, end: f32, winding: f32) -> IntervalRange { + IntervalRange { + start, + end, + winding, + } + } + + fn contains(&self, value: f32) -> bool { + value >= self.start && value < self.end + } +} + +#[cfg(test)] +mod test { + use crate::{IntervalRange, Intervals}; + use quickcheck::{self, Arbitrary, Gen}; + use rand::Rng; + + #[test] + fn test_intervals() { + quickcheck::quickcheck(prop_intervals as fn(Spec) -> bool); + + fn prop_intervals(spec: Spec) -> bool { + let mut intervals = Intervals::new(spec.end); + for range in spec.ranges { + intervals.add(range); + } + + assert!(intervals.ranges.len() > 0); + assert_eq!(intervals.ranges[0].start, 0.0); + assert_eq!(intervals.ranges.last().unwrap().end, spec.end); + for prev_index in 0..(intervals.ranges.len() - 1) { + let next_index = prev_index + 1; + assert_eq!(intervals.ranges[prev_index].end, intervals.ranges[next_index].start); + assert_ne!(intervals.ranges[prev_index].winding, + intervals.ranges[next_index].winding); + } + + true + } + + #[derive(Clone, Debug)] + struct Spec { + end: f32, + ranges: Vec, + } + + impl Arbitrary for Spec { + fn arbitrary(g: &mut G) -> Spec where G: Gen { + const EPSILON: f32 = 0.0001; + + let size = g.size(); + let end = g.gen_range(EPSILON, size as f32); + + let mut ranges = vec![]; + let range_count = g.gen_range(0, size); + for _ in 0..range_count { + let (a, b) = (g.gen_range(0.0, end), g.gen_range(0.0, end)); + let winding = g.gen_range(-(size as i32), size as i32) as f32; + ranges.push(IntervalRange::new(f32::min(a, b), f32::max(a, b), winding)); + } + + Spec { + end, + ranges, + } + } + } + } +}