mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Add color enum 🎨
This commit is contained in:
parent
c44ebf876f
commit
750d220bb0
49
src/color.rs
49
src/color.rs
@ -3,6 +3,21 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
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.
|
||||
///
|
||||
/// # Example
|
||||
@ -20,34 +35,23 @@ pub struct RgbaColor {
|
||||
pub b: u8,
|
||||
/// Alpha channel.
|
||||
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 {
|
||||
/// Constructs a new, unhealed color.
|
||||
/// Constructs a new RGBA color.
|
||||
pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self { r, g, b, a, healed: false }
|
||||
}
|
||||
|
||||
/// 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 }
|
||||
Self { r, g, b, a }
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RgbaColor {
|
||||
type Err = ParseColorError;
|
||||
type Err = ParseRgbaError;
|
||||
|
||||
/// Constructs a new color from a hex string like `7a03c2`. Do not specify a
|
||||
/// leading `#`.
|
||||
fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
|
||||
if !hex_str.is_ascii() {
|
||||
return Err(ParseColorError);
|
||||
return Err(ParseRgbaError);
|
||||
}
|
||||
|
||||
let len = hex_str.len();
|
||||
@ -56,7 +60,7 @@ impl FromStr for RgbaColor {
|
||||
let alpha = len == 4 || len == 8;
|
||||
|
||||
if !long && !short {
|
||||
return Err(ParseColorError);
|
||||
return Err(ParseRgbaError);
|
||||
}
|
||||
|
||||
let mut values: [u8; 4] = [255; 4];
|
||||
@ -66,7 +70,7 @@ impl FromStr for RgbaColor {
|
||||
let pos = elem * 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 {
|
||||
// Duplicate number for shorthand notation, i.e. `a` -> `aa`
|
||||
@ -92,20 +96,17 @@ impl Debug for RgbaColor {
|
||||
write!(f, "{:02x}", self.a)?;
|
||||
}
|
||||
}
|
||||
if self.healed {
|
||||
f.write_str(" [healed]")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// The error when parsing an [`RgbaColor`] fails.
|
||||
/// The error when parsing an [`RgbaColor`] from a string fails.
|
||||
#[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 {
|
||||
f.pad("invalid color")
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ use fontdock::FontStyle;
|
||||
|
||||
use crate::diag::Diag;
|
||||
use crate::diag::{Deco, Feedback, Pass};
|
||||
use crate::color::Color;
|
||||
use crate::env::SharedEnv;
|
||||
use crate::geom::{BoxAlign, Dir, Flow, Gen, Length, Linear, Relative, Sides, Size};
|
||||
use crate::layout::{
|
||||
@ -436,7 +437,7 @@ impl Eval for Lit {
|
||||
Lit::Float(v) => Value::Float(v),
|
||||
Lit::Length(v, unit) => Value::Length(Length::with_unit(v, unit)),
|
||||
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::Dict(ref v) => Value::Dict(v.eval(ctx)),
|
||||
Lit::Content(ref v) => Value::Content(v.clone()),
|
||||
|
@ -7,7 +7,7 @@ use std::rc::Rc;
|
||||
use fontdock::{FontStretch, FontStyle, FontWeight};
|
||||
|
||||
use super::{Args, Dict, Eval, EvalContext, SpannedEntry};
|
||||
use crate::color::RgbaColor;
|
||||
use crate::color::Color;
|
||||
use crate::diag::Diag;
|
||||
use crate::geom::{Dir, Length, Linear, Relative};
|
||||
use crate::paper::Paper;
|
||||
@ -32,8 +32,8 @@ pub enum Value {
|
||||
Relative(Relative),
|
||||
/// A combination of an absolute length and a relative value: `20% + 5cm`.
|
||||
Linear(Linear),
|
||||
/// A color value with alpha channel: `#f79143ff`.
|
||||
Color(RgbaColor),
|
||||
/// A color value: `#f79143ff`.
|
||||
Color(Color),
|
||||
/// A string: `"string"`.
|
||||
Str(String),
|
||||
/// A dictionary value: `(false, 12cm, greeting="hi")`.
|
||||
@ -285,6 +285,7 @@ try_from_match!(Linear["linear"]:
|
||||
Value::Length(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!(SynTree["tree"]: Value::Content(v) => v);
|
||||
try_from_match!(ValueDict["dictionary"]: Value::Dict(v) => v);
|
||||
|
@ -2,9 +2,9 @@ use std::rc::Rc;
|
||||
|
||||
use fontdock::{FontStretch, FontStyle, FontWeight};
|
||||
|
||||
use crate::color::RgbaColor;
|
||||
use crate::eval::StringLike;
|
||||
use crate::geom::Linear;
|
||||
use crate::color::{Color, RgbaColor};
|
||||
use crate::prelude::*;
|
||||
|
||||
/// `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);
|
||||
args.done(ctx);
|
||||
|
||||
let mut healed = r.is_none() || g.is_none() || b.is_none();
|
||||
let mut clamp = |component: Option<Spanned<f64>>, default| {
|
||||
component.map_or(default, |c| {
|
||||
if c.v < 0.0 || c.v > 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
|
||||
})
|
||||
};
|
||||
|
||||
Value::Color(RgbaColor::with_healed(
|
||||
Value::Color(Color::Rgba(RgbaColor::new(
|
||||
clamp(r, 0),
|
||||
clamp(g, 0),
|
||||
clamp(b, 0),
|
||||
clamp(a, 255),
|
||||
healed,
|
||||
))
|
||||
)))
|
||||
}
|
||||
|
@ -487,9 +487,9 @@ fn ident(p: &mut Parser) -> Option<Ident> {
|
||||
/// Parse a color.
|
||||
fn color(p: &mut Parser, hex: &str, start: Pos) -> RgbaColor {
|
||||
RgbaColor::from_str(hex).unwrap_or_else(|_| {
|
||||
// Heal color by assuming black.
|
||||
// Replace color with black.
|
||||
p.diag(error!(start .. p.pos(), "invalid color"));
|
||||
RgbaColor::with_healed(0, 0, 0, 255, true)
|
||||
RgbaColor::new(0, 0, 0, 255)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -436,7 +436,7 @@ fn test_parse_values() {
|
||||
e!("[val: [hi]]" => );
|
||||
|
||||
// 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: #a5]" => s(6, 9, "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 |
Loading…
x
Reference in New Issue
Block a user