diff --git a/Cargo.lock b/Cargo.lock index e22f65e..7a21cc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,10 +29,10 @@ dependencies = [ [[package]] name = "badge" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" +version = "0.2.0" dependencies = [ - "rusttype 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rusttype 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -270,11 +270,6 @@ name = "linked-hash-map" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "linked-hash-map" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "literalext" version = "0.1.1" @@ -466,6 +461,15 @@ dependencies = [ "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ordered-float" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ordermap" version = "0.4.0" @@ -551,11 +555,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rusttype" -version = "0.2.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", - "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ordered-float 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", "stb_truetype 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -670,8 +674,7 @@ dependencies = [ name = "shiny-robots" version = "0.1.0" dependencies = [ - "badge 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "badge 0.2.0", "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.11.18 (registry+https://github.com/rust-lang/crates.io-index)", @@ -866,6 +869,14 @@ name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unreachable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "vcpkg" version = "0.2.2" @@ -876,6 +887,11 @@ name = "version_check" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "winapi" version = "0.2.8" @@ -918,7 +934,6 @@ dependencies = [ "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" "checksum backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbbf59b1c43eefa8c3ede390fcc36820b4999f7914104015be25025e0d62af2" "checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661" -"checksum badge 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "252a8110fb51d7dbb24b35e3832ea6634c27867c34c68f166962555e4aa4c10f" "checksum base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "229d032f1a99302697f10b27167ae6d03d49d032e6a8e2550e8d3fc13356d2b4" "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" @@ -951,7 +966,6 @@ dependencies = [ "checksum lazycell 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a6f08839bc70ef4a3fe1d566d5350f519c5912ea86be0df1740a7d247c7fc0ef" "checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" "checksum linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7860ec297f7008ff7a1e3382d7f7e1dcd69efc94751a2284bafc3d013c2aa939" -"checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e" "checksum literalext 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2f42dd699527975a1e0d722e0707998671188a0125f2051d2d192fc201184a81" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" @@ -973,6 +987,7 @@ dependencies = [ "checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum openssl 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)" = "a3605c298474a3aa69de92d21139fb5e2a81688d308262359d85cdd0d12a7985" "checksum openssl-sys 0.9.25 (registry+https://github.com/rust-lang/crates.io-index)" = "93b3cbfaccf11969aea8c2041bfafc43c81666c1ce673476e19395c92cc77bf4" +"checksum ordered-float 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "58d25b6c0e47b20d05226d288ff434940296e7e2f8b877975da32f862152241f" "checksum ordermap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "88fc9d511a8e8d3adc7ba9f4b8f9683a2eface9c14b652ad77f975c4f95c787b" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" @@ -985,7 +1000,7 @@ dependencies = [ "checksum remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d2f806b0fcdabd98acd380dc8daef485e22bcb7cddc811d1337967f2528cf5" "checksum route-recognizer 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3255338088df8146ba63d60a9b8e3556f1146ce2973bc05a75181a42ce2256" "checksum rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aee45432acc62f7b9a108cc054142dac51f979e69e71ddce7d6fc7adf29e817e" -"checksum rusttype 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "30047cc747a78ae042bf2cd65c79f83c3485d90107535b532d6e8f60e2c89cb1" +"checksum rusttype 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b948303d19988f60ed3e49551f4fedf28c86a2025842a07c6c1d8aa985b8557" "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" "checksum sass-rs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90f8cf6e645aa843ffffcbdc1e8752b1f221dfa314c81895aeb229a77aea7e05" "checksum sass-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8bc8cb11c6906ced0e62751f93fa1f30216612a182d4e9a7d8ace302834b1742" @@ -1019,8 +1034,10 @@ dependencies = [ "checksum toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a7540f4ffc193e0d3c94121edb19b055670d369f77d5804db11ae053a45b6e7e" "checksum unicase 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284b6d3db520d67fbe88fd778c21510d1b0ba4a551e5d0fbb023d33405f6de8a" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" "checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" diff --git a/Cargo.toml b/Cargo.toml index 29c0c21..62c1976 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,6 @@ version = "0.1.0" authors = ["Sam Rijs "] [dependencies] -badge = "0.1.0" -base64 = "0.9.0" failure = "0.1.1" futures = "0.1.18" hyper = "0.11.15" @@ -26,5 +24,9 @@ tokio-core = "0.1.12" tokio-service = "0.1.0" toml = "0.4.5" +[dependencies.badge] +version = "0.2.0" +path = "libs/badge" + [build-dependencies] sass-rs = "0.2.1" diff --git a/libs/badge/Cargo.toml b/libs/badge/Cargo.toml new file mode 100644 index 0000000..8efc1e4 --- /dev/null +++ b/libs/badge/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "badge" +version = "0.2.0" +description = "Simple badge generator" +authors = ["Onur Aslan "] +license-file = "LICENSE" +repository = "https://github.com/onur/docs.rs" +documentation = "https://docs.rs/badge" + +[lib] +path = "badge.rs" + +[dependencies] +base64 = "0.9.0" +rusttype = "0.4.0" diff --git a/libs/badge/DejaVuSans.ttf b/libs/badge/DejaVuSans.ttf new file mode 100644 index 0000000..de12789 Binary files /dev/null and b/libs/badge/DejaVuSans.ttf differ diff --git a/libs/badge/LICENSE b/libs/badge/LICENSE new file mode 100644 index 0000000..48a62eb --- /dev/null +++ b/libs/badge/LICENSE @@ -0,0 +1,70 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: badge +Source: https://github.com/onur/docs.rs + +Files: * +Copyright: Copyright (c) 2016 Onur Aslan +License: Expat + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +Files: DejaVuSans.ttf +Copyright: Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. + Bitstream Vera is a trademark of Bitstream, Inc. + DejaVu changes are in public domain. +License: bitstream-vera + Permission is hereby granted, free of charge, to any person obtaining a copy + of the fonts accompanying this license ("Fonts") and associated + documentation files (the "Font Software"), to reproduce and distribute the + Font Software, including without limitation the rights to use, copy, merge, + publish, distribute, and/or sell copies of the Font Software, and to permit + persons to whom the Font Software is furnished to do so, subject to the + following conditions: + . + The above copyright and trademark notices and this permission notice shall + be included in all copies of one or more of the Font Software typefaces. + . + The Font Software may be modified, altered, or added to, and in particular + the designs of glyphs or characters in the Fonts may be modified and + additional glyphs or characters may be added to the Fonts, only if the fonts + are renamed to names not containing either the words "Bitstream" or the word + "Vera". + . + This License becomes null and void to the extent applicable to Fonts or Font + Software that has been modified and is distributed under the "Bitstream + Vera" names. + . + The Font Software may be sold as part of a larger software package but no + copy of one or more of the Font Software typefaces may be sold by itself. + . + THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, + TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME + FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING + ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF + THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE + FONT SOFTWARE. + . + Except as contained in this notice, the names of Gnome, the Gnome + Foundation, and Bitstream Inc., shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this Font Software + without prior written authorization from the Gnome Foundation or Bitstream + Inc., respectively. For further information, contact: fonts at gnome dot + org. diff --git a/libs/badge/badge.rs b/libs/badge/badge.rs new file mode 100644 index 0000000..0194ae8 --- /dev/null +++ b/libs/badge/badge.rs @@ -0,0 +1,170 @@ +//! Simple badge generator + +extern crate base64; +extern crate rusttype; + + +use base64::display::Base64Display; +use rusttype::{Font, FontCollection, Scale, point, Point, PositionedGlyph}; + + +const FONT_DATA: &'static [u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), + "/DejaVuSans.ttf")); +const FONT_SIZE: f32 = 11.; + + +pub struct BadgeOptions { + /// Subject will be displayed on the left side of badge + pub subject: String, + /// Status will be displayed on the right side of badge + pub status: String, + /// HTML color of badge + pub color: String, +} + + +impl Default for BadgeOptions { + fn default() -> BadgeOptions { + BadgeOptions { + subject: "build".to_owned(), + status: "passing".to_owned(), + color: "#4c1".to_owned(), + } + } +} + + +pub struct Badge { + options: BadgeOptions, + font: Font<'static>, + scale: Scale, + offset: Point, +} + + +impl Badge { + pub fn new(options: BadgeOptions) -> Result { + let collection = FontCollection::from_bytes(FONT_DATA); + // this should never fail in practice + let font = try!(collection.into_font().ok_or("Failed to load font data".to_owned())); + let scale = Scale { + x: FONT_SIZE, + y: FONT_SIZE, + }; + let v_metrics = font.v_metrics(scale); + let offset = point(0.0, v_metrics.ascent); + Ok(Badge { + options: options, + font: font, + scale: scale, + offset: offset, + }) + } + + + pub fn to_svg_data_uri(&self) -> String { + format!("data:image/svg+xml;base64,{}", + Base64Display::standard(self.to_svg().as_bytes())) + } + + + pub fn to_svg(&self) -> String { + let left_width = self.calculate_width(&self.options.subject) + 6; + let right_width = self.calculate_width(&self.options.status) + 6; + + let svg = format!(r###" + + + + + + + + + + + + + + + + + {} + {} + {} + {} + +"###, + left_width + right_width, + left_width + right_width, + left_width, + left_width, + right_width, + self.options.color, + left_width + right_width, + (left_width) / 2, + self.options.subject, + (left_width) / 2, + self.options.subject, + left_width + (right_width / 2), + self.options.status, + left_width + (right_width / 2), + self.options.status); + + svg + } + + + fn calculate_width(&self, text: &str) -> u32 { + let glyphs: Vec = + self.font.layout(text, self.scale, self.offset).collect(); + let width = glyphs.iter() + .rev() + .filter_map(|g| { + g.pixel_bounding_box() + .map(|b| b.min.x as f32 + g.unpositioned().h_metrics().advance_width) + }) + .next() + .unwrap_or(0.0); + (width + ((text.len() as f32 - 1f32) * 1.5)).ceil() as u32 + } +} + + + +#[cfg(test)] +mod tests { + use super::*; + + fn options() -> BadgeOptions { + BadgeOptions::default() + } + + + #[test] + fn test_new() { + assert!(Badge::new(options()).is_ok()); + } + + #[test] + fn test_calculate_width() { + let badge = Badge::new(options()).unwrap(); + assert_eq!(badge.calculate_width("build"), 31); + assert_eq!(badge.calculate_width("passing"), 48); + } + + #[test] + #[ignore] + fn test_to_svg() { + use std::fs::File; + use std::io::Write; + let mut file = File::create("test.svg").unwrap(); + let options = BadgeOptions { + subject: "build".to_owned(), + status: "passing".to_owned(), + ..BadgeOptions::default() + }; + let badge = Badge::new(options).unwrap(); + file.write_all(badge.to_svg().as_bytes()).unwrap(); + } +} diff --git a/src/main.rs b/src/main.rs index a243b95..84fe8d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ #![feature(proc_macro)] extern crate badge; -extern crate base64; #[macro_use] extern crate failure; #[macro_use] extern crate futures; extern crate hyper; diff --git a/src/server/views/badge.rs b/src/server/views/badge.rs index 224451c..7ffc6ec 100644 --- a/src/server/views/badge.rs +++ b/src/server/views/badge.rs @@ -4,7 +4,7 @@ use hyper::header::ContentType; use ::engine::AnalyzeDependenciesOutcome; -pub fn svg(analysis_outcome: Option<&AnalyzeDependenciesOutcome>) -> Vec { +pub fn badge(analysis_outcome: Option<&AnalyzeDependenciesOutcome>) -> Badge { let opts = match analysis_outcome { Some(outcome) => { if outcome.any_outdated() { @@ -32,12 +32,10 @@ pub fn svg(analysis_outcome: Option<&AnalyzeDependenciesOutcome>) -> Vec { Badge::new(opts) .expect("failed to create badge") - .to_svg() - .into_bytes() } pub fn response(analysis_outcome: Option<&AnalyzeDependenciesOutcome>) -> Response { Response::new() .with_header(ContentType("image/svg+xml;charset=utf-8".parse().unwrap())) - .with_body(svg(analysis_outcome)) + .with_body(badge(analysis_outcome).to_svg().into_bytes()) } diff --git a/src/server/views/html/status.rs b/src/server/views/html/status.rs index 199d1be..116b98a 100644 --- a/src/server/views/html/status.rs +++ b/src/server/views/html/status.rs @@ -1,4 +1,3 @@ -use base64::display::Base64Display; use hyper::Response; use maud::{Markup, html}; use ordermap::OrderMap; @@ -126,8 +125,7 @@ fn render_success(analysis_outcome: AnalyzeDependenciesOutcome, repo_path: RepoP let status_base_url = format!("{}/{}", &super::SELF_BASE_URL as &str, self_path); let site_icon = get_site_icon(&repo_path.site); - let status_badge = badge::svg(Some(&analysis_outcome)); - let status_data_url = format!("data:image/svg+xml;base64,{}", Base64Display::standard(&status_badge)); + let status_data_uri = badge::badge(Some(&analysis_outcome)).to_svg_data_uri(); let hero_class = if analysis_outcome.any_outdated() { "is-warning" @@ -147,7 +145,7 @@ fn render_success(analysis_outcome: AnalyzeDependenciesOutcome, repo_path: RepoP } } - img src=(status_data_url); + img src=(status_data_uri); } } div class="hero-footer" {