modernize badge package

This commit is contained in:
Rob Ede 2020-09-28 23:18:06 +01:00
parent 0d3e0ac0f5
commit 3e3e9eea9d
No known key found for this signature in database
GPG key ID: C2A3B36E841A91E6
5 changed files with 925 additions and 566 deletions

1404
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -3,6 +3,12 @@ name = "shiny-robots"
version = "0.1.0" version = "0.1.0"
authors = ["Sam Rijs <srijs@airpost.net>"] authors = ["Sam Rijs <srijs@airpost.net>"]
[workspace]
members = [
".",
"./libs/badge",
]
[dependencies] [dependencies]
cadence = "0.13.1" cadence = "0.13.1"
failure = "0.1.1" failure = "0.1.1"

1
libs/badge/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/test.svg

View file

@ -6,11 +6,12 @@ authors = ["Onur Aslan <onur@onur.im>"]
license-file = "LICENSE" license-file = "LICENSE"
repository = "https://github.com/onur/docs.rs" repository = "https://github.com/onur/docs.rs"
documentation = "https://docs.rs/badge" documentation = "https://docs.rs/badge"
edition = "2018"
[lib] [lib]
path = "badge.rs" path = "badge.rs"
[dependencies] [dependencies]
base64 = "0.9.0" base64 = "0.12"
lazy_static = "1.0.0" once_cell = "1"
rusttype = "0.5.0" rusttype = "0.9"

View file

@ -1,18 +1,16 @@
//! Simple badge generator //! Simple badge generator
extern crate base64;
#[macro_use] extern crate lazy_static;
extern crate rusttype;
use base64::display::Base64Display; use base64::display::Base64Display;
use rusttype::{Font, FontCollection, Scale, point, Point, PositionedGlyph}; use once_cell::sync::Lazy;
use rusttype::{point, Font, Point, PositionedGlyph, Scale};
const FONT_DATA: &'static [u8] =
const FONT_DATA: &'static [u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/DejaVuSans.ttf"));
"/DejaVuSans.ttf"));
const FONT_SIZE: f32 = 11.; const FONT_SIZE: f32 = 11.;
const SCALE: Scale = Scale {
x: FONT_SIZE,
y: FONT_SIZE,
};
pub struct BadgeOptions { pub struct BadgeOptions {
/// Subject will be displayed on the left side of badge /// Subject will be displayed on the left side of badge
@ -23,7 +21,6 @@ pub struct BadgeOptions {
pub color: String, pub color: String,
} }
impl Default for BadgeOptions { impl Default for BadgeOptions {
fn default() -> BadgeOptions { fn default() -> BadgeOptions {
BadgeOptions { BadgeOptions {
@ -34,54 +31,47 @@ impl Default for BadgeOptions {
} }
} }
struct BadgeStaticData { struct BadgeStaticData {
font: Font<'static>, font: Font<'static>,
scale: Scale, scale: Scale,
offset: Point<f32> offset: Point<f32>,
} }
static DATA: Lazy<BadgeStaticData> = Lazy::new(|| {
let font = Font::try_from_bytes(FONT_DATA).expect("failed to parse font collection");
lazy_static! { let v_metrics = font.v_metrics(SCALE);
static ref DATA: BadgeStaticData = {
let collection = FontCollection::from_bytes(FONT_DATA)
.expect("failed to parse font collection");
let font = collection.into_font()
.expect("failed to load font data");
let scale = Scale {
x: FONT_SIZE,
y: FONT_SIZE,
};
let v_metrics = font.v_metrics(scale);
let offset = point(0.0, v_metrics.ascent); let offset = point(0.0, v_metrics.ascent);
BadgeStaticData { font, scale, offset } BadgeStaticData {
}; font,
} scale: SCALE.clone(),
offset,
}
});
pub struct Badge { pub struct Badge {
options: BadgeOptions options: BadgeOptions,
} }
impl Badge { impl Badge {
pub fn new(options: BadgeOptions) -> Badge { pub fn new(options: BadgeOptions) -> Badge {
Badge { options } Badge { options }
} }
pub fn to_svg_data_uri(&self) -> String { pub fn to_svg_data_uri(&self) -> String {
format!("data:image/svg+xml;base64,{}", format!(
Base64Display::standard(self.to_svg().as_bytes())) "data:image/svg+xml;base64,{}",
Base64Display::with_config(self.to_svg().as_bytes(), base64::STANDARD)
)
} }
pub fn to_svg(&self) -> String { pub fn to_svg(&self) -> String {
let left_width = self.calculate_width(&self.options.subject) + 6; let left_width = self.calculate_width(&self.options.subject) + 6;
let right_width = self.calculate_width(&self.options.status) + 6; let right_width = self.calculate_width(&self.options.status) + 6;
let svg = format!(r###"<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{}" height="20"> let svg = format!(
r###"<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{}" height="20">
<linearGradient id="smooth" x2="0" y2="100%"> <linearGradient id="smooth" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/> <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/> <stop offset="1" stop-opacity=".1"/>
@ -118,16 +108,17 @@ impl Badge {
left_width + (right_width / 2), left_width + (right_width / 2),
self.options.status, self.options.status,
left_width + (right_width / 2), left_width + (right_width / 2),
self.options.status); self.options.status
);
svg svg
} }
fn calculate_width(&self, text: &str) -> u32 { fn calculate_width(&self, text: &str) -> u32 {
let glyphs: Vec<PositionedGlyph> = let glyphs: Vec<PositionedGlyph> =
DATA.font.layout(text, DATA.scale, DATA.offset).collect(); DATA.font.layout(text, DATA.scale, DATA.offset).collect();
let width = glyphs.iter() let width = glyphs
.iter()
.rev() .rev()
.filter_map(|g| { .filter_map(|g| {
g.pixel_bounding_box() g.pixel_bounding_box()
@ -139,8 +130,6 @@ impl Badge {
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;