Add color enum 🎨

This commit is contained in:
Laurenz 2020-12-27 19:23:26 +01:00
parent c44ebf876f
commit 750d220bb0
7 changed files with 37 additions and 37 deletions

View File

@ -3,6 +3,21 @@
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::str::FromStr; use std::str::FromStr;
/// A color in a dynamic format.
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum Color {
/// An 8-bit RGBA color.
Rgba(RgbaColor),
}
impl Debug for Color {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Rgba(c) => c.fmt(f),
}
}
}
/// An 8-bit RGBA color. /// An 8-bit RGBA color.
/// ///
/// # Example /// # Example
@ -20,34 +35,23 @@ pub struct RgbaColor {
pub b: u8, pub b: u8,
/// Alpha channel. /// Alpha channel.
pub a: u8, pub a: u8,
/// Whether the color was provided as a fail-over by the parser because the
/// user-defined value was invalid.
///
/// If this is true, the color may be replaced with any color deemed
/// appropriate at the use-site.
pub healed: bool,
} }
impl RgbaColor { impl RgbaColor {
/// Constructs a new, unhealed color. /// Constructs a new RGBA color.
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a, healed: false } Self { r, g, b, a }
}
/// Constructs a new color with a configurable healed status.
pub fn with_healed(r: u8, g: u8, b: u8, a: u8, healed: bool) -> Self {
Self { r, g, b, a, healed }
} }
} }
impl FromStr for RgbaColor { impl FromStr for RgbaColor {
type Err = ParseColorError; type Err = ParseRgbaError;
/// Constructs a new color from a hex string like `7a03c2`. Do not specify a /// Constructs a new color from a hex string like `7a03c2`. Do not specify a
/// leading `#`. /// leading `#`.
fn from_str(hex_str: &str) -> Result<Self, Self::Err> { fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
if !hex_str.is_ascii() { if !hex_str.is_ascii() {
return Err(ParseColorError); return Err(ParseRgbaError);
} }
let len = hex_str.len(); let len = hex_str.len();
@ -56,7 +60,7 @@ impl FromStr for RgbaColor {
let alpha = len == 4 || len == 8; let alpha = len == 4 || len == 8;
if !long && !short { if !long && !short {
return Err(ParseColorError); return Err(ParseRgbaError);
} }
let mut values: [u8; 4] = [255; 4]; let mut values: [u8; 4] = [255; 4];
@ -66,7 +70,7 @@ impl FromStr for RgbaColor {
let pos = elem * item_len; let pos = elem * item_len;
let item = &hex_str[pos .. (pos + item_len)]; let item = &hex_str[pos .. (pos + item_len)];
values[elem] = u8::from_str_radix(item, 16).map_err(|_| ParseColorError)?; values[elem] = u8::from_str_radix(item, 16).map_err(|_| ParseRgbaError)?;
if short { if short {
// Duplicate number for shorthand notation, i.e. `a` -> `aa` // Duplicate number for shorthand notation, i.e. `a` -> `aa`
@ -92,20 +96,17 @@ impl Debug for RgbaColor {
write!(f, "{:02x}", self.a)?; write!(f, "{:02x}", self.a)?;
} }
} }
if self.healed {
f.write_str(" [healed]")?;
}
Ok(()) Ok(())
} }
} }
/// The error when parsing an [`RgbaColor`] fails. /// The error when parsing an [`RgbaColor`] from a string fails.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct ParseColorError; pub struct ParseRgbaError;
impl std::error::Error for ParseColorError {} impl std::error::Error for ParseRgbaError {}
impl fmt::Display for ParseColorError { impl fmt::Display for ParseRgbaError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad("invalid color") f.pad("invalid color")
} }

View File

@ -20,6 +20,7 @@ use fontdock::FontStyle;
use crate::diag::Diag; use crate::diag::Diag;
use crate::diag::{Deco, Feedback, Pass}; use crate::diag::{Deco, Feedback, Pass};
use crate::color::Color;
use crate::env::SharedEnv; use crate::env::SharedEnv;
use crate::geom::{BoxAlign, Dir, Flow, Gen, Length, Linear, Relative, Sides, Size}; use crate::geom::{BoxAlign, Dir, Flow, Gen, Length, Linear, Relative, Sides, Size};
use crate::layout::{ use crate::layout::{
@ -436,7 +437,7 @@ impl Eval for Lit {
Lit::Float(v) => Value::Float(v), Lit::Float(v) => Value::Float(v),
Lit::Length(v, unit) => Value::Length(Length::with_unit(v, unit)), Lit::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)), Lit::Percent(v) => Value::Relative(Relative::new(v / 100.0)),
Lit::Color(v) => Value::Color(v), Lit::Color(v) => Value::Color(Color::Rgba(v)),
Lit::Str(ref v) => Value::Str(v.clone()), Lit::Str(ref v) => Value::Str(v.clone()),
Lit::Dict(ref v) => Value::Dict(v.eval(ctx)), Lit::Dict(ref v) => Value::Dict(v.eval(ctx)),
Lit::Content(ref v) => Value::Content(v.clone()), Lit::Content(ref v) => Value::Content(v.clone()),

View File

@ -7,7 +7,7 @@ use std::rc::Rc;
use fontdock::{FontStretch, FontStyle, FontWeight}; use fontdock::{FontStretch, FontStyle, FontWeight};
use super::{Args, Dict, Eval, EvalContext, SpannedEntry}; use super::{Args, Dict, Eval, EvalContext, SpannedEntry};
use crate::color::RgbaColor; use crate::color::Color;
use crate::diag::Diag; use crate::diag::Diag;
use crate::geom::{Dir, Length, Linear, Relative}; use crate::geom::{Dir, Length, Linear, Relative};
use crate::paper::Paper; use crate::paper::Paper;
@ -32,8 +32,8 @@ pub enum Value {
Relative(Relative), Relative(Relative),
/// A combination of an absolute length and a relative value: `20% + 5cm`. /// A combination of an absolute length and a relative value: `20% + 5cm`.
Linear(Linear), Linear(Linear),
/// A color value with alpha channel: `#f79143ff`. /// A color value: `#f79143ff`.
Color(RgbaColor), Color(Color),
/// A string: `"string"`. /// A string: `"string"`.
Str(String), Str(String),
/// A dictionary value: `(false, 12cm, greeting="hi")`. /// A dictionary value: `(false, 12cm, greeting="hi")`.
@ -285,6 +285,7 @@ try_from_match!(Linear["linear"]:
Value::Length(v) => v.into(), Value::Length(v) => v.into(),
Value::Relative(v) => v.into(), Value::Relative(v) => v.into(),
); );
try_from_match!(Color["color"]: Value::Color(v) => v);
try_from_match!(String["string"]: Value::Str(v) => v); try_from_match!(String["string"]: Value::Str(v) => v);
try_from_match!(SynTree["tree"]: Value::Content(v) => v); try_from_match!(SynTree["tree"]: Value::Content(v) => v);
try_from_match!(ValueDict["dictionary"]: Value::Dict(v) => v); try_from_match!(ValueDict["dictionary"]: Value::Dict(v) => v);

View File

@ -2,9 +2,9 @@ use std::rc::Rc;
use fontdock::{FontStretch, FontStyle, FontWeight}; use fontdock::{FontStretch, FontStyle, FontWeight};
use crate::color::RgbaColor;
use crate::eval::StringLike; use crate::eval::StringLike;
use crate::geom::Linear; use crate::geom::Linear;
use crate::color::{Color, RgbaColor};
use crate::prelude::*; use crate::prelude::*;
/// `font`: Configure the font. /// `font`: Configure the font.
@ -152,22 +152,19 @@ pub fn rgb(mut args: Args, ctx: &mut EvalContext) -> Value {
let a = args.get::<_, Spanned<f64>>(ctx, 3); let a = args.get::<_, Spanned<f64>>(ctx, 3);
args.done(ctx); args.done(ctx);
let mut healed = r.is_none() || g.is_none() || b.is_none();
let mut clamp = |component: Option<Spanned<f64>>, default| { let mut clamp = |component: Option<Spanned<f64>>, default| {
component.map_or(default, |c| { component.map_or(default, |c| {
if c.v < 0.0 || c.v > 1.0 { if c.v < 0.0 || c.v > 1.0 {
ctx.diag(error!(c.span, "should be between 0.0 and 1.0")); ctx.diag(error!(c.span, "should be between 0.0 and 1.0"));
healed = true;
} }
(c.v.max(0.0).min(1.0) * 255.0).round() as u8 (c.v.max(0.0).min(1.0) * 255.0).round() as u8
}) })
}; };
Value::Color(RgbaColor::with_healed( Value::Color(Color::Rgba(RgbaColor::new(
clamp(r, 0), clamp(r, 0),
clamp(g, 0), clamp(g, 0),
clamp(b, 0), clamp(b, 0),
clamp(a, 255), clamp(a, 255),
healed, )))
))
} }

View File

@ -487,9 +487,9 @@ fn ident(p: &mut Parser) -> Option<Ident> {
/// Parse a color. /// Parse a color.
fn color(p: &mut Parser, hex: &str, start: Pos) -> RgbaColor { fn color(p: &mut Parser, hex: &str, start: Pos) -> RgbaColor {
RgbaColor::from_str(hex).unwrap_or_else(|_| { RgbaColor::from_str(hex).unwrap_or_else(|_| {
// Heal color by assuming black. // Replace color with black.
p.diag(error!(start .. p.pos(), "invalid color")); p.diag(error!(start .. p.pos(), "invalid color"));
RgbaColor::with_healed(0, 0, 0, 255, true) RgbaColor::new(0, 0, 0, 255)
}) })
} }

View File

@ -436,7 +436,7 @@ fn test_parse_values() {
e!("[val: [hi]]" => ); e!("[val: [hi]]" => );
// Healed colors. // Healed colors.
v!("#12345" => Color(RgbaColor::with_healed(0, 0, 0, 0xff, true))); v!("#12345" => Color(RgbaColor::new(0, 0, 0, 0xff)));
e!("[val: #12345]" => s(6, 12, "invalid color")); e!("[val: #12345]" => s(6, 12, "invalid color"));
e!("[val: #a5]" => s(6, 9, "invalid color")); e!("[val: #a5]" => s(6, 9, "invalid color"));
e!("[val: #14b2ah]" => s(6, 13, "invalid color")); e!("[val: #14b2ah]" => s(6, 13, "invalid color"));

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB