Luma color
This commit is contained in:
parent
d4e59d4be1
commit
1a7ce3da02
@ -36,8 +36,9 @@ pub fn pdf(ctx: &Context, frames: &[Arc<Frame>]) -> Vec<u8> {
|
||||
PdfExporter::new(ctx).export(frames)
|
||||
}
|
||||
|
||||
/// Identifies the sRGB color space definition.
|
||||
/// Identifies the color space definitions.
|
||||
const SRGB: Name<'static> = Name(b"sRGB");
|
||||
const SRGB_GRAY: Name<'static> = Name(b"sRGBGray");
|
||||
|
||||
/// An exporter for a whole PDF document.
|
||||
struct PdfExporter<'a> {
|
||||
@ -365,6 +366,11 @@ impl<'a> PdfExporter<'a> {
|
||||
|
||||
let mut resources = pages.resources();
|
||||
resources.color_spaces().insert(SRGB).start::<ColorSpace>().srgb();
|
||||
resources
|
||||
.color_spaces()
|
||||
.insert(SRGB_GRAY)
|
||||
.start::<ColorSpace>()
|
||||
.srgb_gray();
|
||||
|
||||
let mut fonts = resources.fonts();
|
||||
for (font_ref, f) in self.face_map.pdf_indices(&self.face_refs) {
|
||||
@ -437,9 +443,11 @@ struct Page {
|
||||
#[derive(Debug, Default, Clone)]
|
||||
struct State {
|
||||
transform: Transform,
|
||||
fill: Option<Paint>,
|
||||
stroke: Option<Stroke>,
|
||||
font: Option<(FaceId, Length)>,
|
||||
fill: Option<Paint>,
|
||||
fill_space: Option<Name<'static>>,
|
||||
stroke: Option<Stroke>,
|
||||
stroke_space: Option<Name<'static>>,
|
||||
}
|
||||
|
||||
impl<'a> PageExporter<'a> {
|
||||
@ -469,8 +477,6 @@ impl<'a> PageExporter<'a> {
|
||||
tx: Length::zero(),
|
||||
ty: frame.size.y,
|
||||
});
|
||||
self.content.set_fill_color_space(ColorSpaceOperand::Named(SRGB));
|
||||
self.content.set_stroke_color_space(ColorSpaceOperand::Named(SRGB));
|
||||
self.write_frame(frame);
|
||||
Page {
|
||||
size: frame.size,
|
||||
@ -708,6 +714,7 @@ impl<'a> PageExporter<'a> {
|
||||
self.font_map.insert(face_id);
|
||||
let name = format_eco!("F{}", self.font_map.map(face_id));
|
||||
self.content.set_font(Name(name.as_bytes()), size.to_f32());
|
||||
self.state.font = Some((face_id, size));
|
||||
}
|
||||
}
|
||||
|
||||
@ -716,13 +723,26 @@ impl<'a> PageExporter<'a> {
|
||||
let f = |c| c as f32 / 255.0;
|
||||
let Paint::Solid(color) = fill;
|
||||
match color {
|
||||
Color::Luma(c) => {
|
||||
self.set_fill_color_space(SRGB_GRAY);
|
||||
self.content.set_fill_gray(f(c.0));
|
||||
}
|
||||
Color::Rgba(c) => {
|
||||
self.set_fill_color_space(SRGB);
|
||||
self.content.set_fill_color([f(c.r), f(c.g), f(c.b)]);
|
||||
}
|
||||
Color::Cmyk(c) => {
|
||||
self.content.set_fill_cmyk(f(c.c), f(c.m), f(c.y), f(c.k));
|
||||
}
|
||||
}
|
||||
self.state.fill = Some(fill);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_fill_color_space(&mut self, space: Name<'static>) {
|
||||
if self.state.fill_space != Some(space) {
|
||||
self.content.set_fill_color_space(ColorSpaceOperand::Named(space));
|
||||
self.state.fill_space = Some(space);
|
||||
}
|
||||
}
|
||||
|
||||
@ -731,7 +751,12 @@ impl<'a> PageExporter<'a> {
|
||||
let f = |c| c as f32 / 255.0;
|
||||
let Paint::Solid(color) = stroke.paint;
|
||||
match color {
|
||||
Color::Luma(c) => {
|
||||
self.set_stroke_color_space(SRGB_GRAY);
|
||||
self.content.set_stroke_gray(f(c.0));
|
||||
}
|
||||
Color::Rgba(c) => {
|
||||
self.set_stroke_color_space(SRGB);
|
||||
self.content.set_stroke_color([f(c.r), f(c.g), f(c.b)]);
|
||||
}
|
||||
Color::Cmyk(c) => {
|
||||
@ -740,6 +765,14 @@ impl<'a> PageExporter<'a> {
|
||||
}
|
||||
|
||||
self.content.set_line_width(stroke.thickness.to_f32());
|
||||
self.state.stroke = Some(stroke);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_stroke_color_space(&mut self, space: Name<'static>) {
|
||||
if self.state.stroke_space != Some(space) {
|
||||
self.content.set_stroke_color_space(ColorSpaceOperand::Named(space));
|
||||
self.state.stroke_space = Some(space);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ impl Debug for Paint {
|
||||
/// A color in a dynamic format.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Color {
|
||||
/// An 8-bit luma color.
|
||||
Luma(LumaColor),
|
||||
/// An 8-bit RGBA color.
|
||||
Rgba(RgbaColor),
|
||||
/// An 8-bit CMYK color.
|
||||
@ -60,6 +62,7 @@ impl Color {
|
||||
/// Convert this color to RGBA.
|
||||
pub fn to_rgba(self) -> RgbaColor {
|
||||
match self {
|
||||
Self::Luma(luma) => luma.to_rgba(),
|
||||
Self::Rgba(rgba) => rgba,
|
||||
Self::Cmyk(cmyk) => cmyk.to_rgba(),
|
||||
}
|
||||
@ -69,18 +72,48 @@ impl Color {
|
||||
impl Debug for Color {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Self::Luma(c) => Debug::fmt(c, f),
|
||||
Self::Rgba(c) => Debug::fmt(c, f),
|
||||
Self::Cmyk(c) => Debug::fmt(c, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Color
|
||||
where
|
||||
T: Into<RgbaColor>,
|
||||
{
|
||||
fn from(rgba: T) -> Self {
|
||||
Self::Rgba(rgba.into())
|
||||
/// An 8-bit Luma color.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct LumaColor(pub u8);
|
||||
|
||||
impl LumaColor {
|
||||
/// Construct a new luma color.
|
||||
pub const fn new(luma: u8) -> Self {
|
||||
Self(luma)
|
||||
}
|
||||
|
||||
/// Convert to an opque RGBA color.
|
||||
pub const fn to_rgba(self) -> RgbaColor {
|
||||
RgbaColor::new(self.0, self.0, self.0, 255)
|
||||
}
|
||||
|
||||
/// Convert to CMYK as a fraction of true black.
|
||||
pub fn to_cmyk(self) -> CmykColor {
|
||||
CmykColor::new(
|
||||
(self.0 as f64 * 0.75) as u8,
|
||||
(self.0 as f64 * 0.68) as u8,
|
||||
(self.0 as f64 * 0.67) as u8,
|
||||
(self.0 as f64 * 0.90) as u8,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for LumaColor {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "luma({})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LumaColor> for Color {
|
||||
fn from(luma: LumaColor) -> Self {
|
||||
Self::Luma(luma)
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,11 +135,6 @@ impl RgbaColor {
|
||||
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||
Self { r, g, b, a }
|
||||
}
|
||||
|
||||
/// Construct a new, opaque gray color.
|
||||
pub const fn gray(luma: u8) -> Self {
|
||||
Self::new(luma, luma, luma, 255)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RgbaColor {
|
||||
@ -161,11 +189,7 @@ impl From<SynColor> for RgbaColor {
|
||||
impl Debug for RgbaColor {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
write!(
|
||||
f,
|
||||
"rgba({:02}, {:02}, {:02}, {:02})",
|
||||
self.r, self.g, self.b, self.a,
|
||||
)?;
|
||||
write!(f, "rgba({}, {}, {}, {})", self.r, self.g, self.b, self.a,)?;
|
||||
} else {
|
||||
write!(f, "rgb(\"#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?;
|
||||
if self.a != 255 {
|
||||
@ -177,6 +201,15 @@ impl Debug for RgbaColor {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<T> for Color
|
||||
where
|
||||
T: Into<RgbaColor>,
|
||||
{
|
||||
fn from(rgba: T) -> Self {
|
||||
Self::Rgba(rgba.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// An 8-bit CMYK color.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct CmykColor {
|
||||
@ -196,16 +229,6 @@ impl CmykColor {
|
||||
Self { c, m, y, k }
|
||||
}
|
||||
|
||||
/// Construct a new, opaque gray color as a fraction of true black.
|
||||
pub fn gray(luma: u8) -> Self {
|
||||
Self::new(
|
||||
(luma as f64 * 0.75) as u8,
|
||||
(luma as f64 * 0.68) as u8,
|
||||
(luma as f64 * 0.67) as u8,
|
||||
(luma as f64 * 0.90) as u8,
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert this color to RGBA.
|
||||
pub fn to_rgba(self) -> RgbaColor {
|
||||
let k = self.k as f32 / 255.0;
|
||||
|
@ -85,6 +85,7 @@ pub fn new() -> Scope {
|
||||
std.def_fn("odd", utility::odd);
|
||||
std.def_fn("mod", utility::mod_);
|
||||
std.def_fn("range", utility::range);
|
||||
std.def_fn("luma", utility::luma);
|
||||
std.def_fn("rgb", utility::rgb);
|
||||
std.def_fn("cmyk", utility::cmyk);
|
||||
std.def_fn("repr", utility::repr);
|
||||
|
@ -2,57 +2,65 @@ use std::str::FromStr;
|
||||
|
||||
use crate::library::prelude::*;
|
||||
|
||||
/// Create a grayscale color.
|
||||
pub fn luma(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
||||
let Component(luma) = args.expect("gray component")?;
|
||||
Ok(Value::Color(LumaColor::new(luma).into()))
|
||||
}
|
||||
|
||||
/// Create an RGB(A) color.
|
||||
pub fn rgb(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
||||
Ok(Value::from(
|
||||
Ok(Value::Color(
|
||||
if let Some(string) = args.find::<Spanned<EcoString>>()? {
|
||||
match RgbaColor::from_str(&string.v) {
|
||||
Ok(color) => color,
|
||||
Ok(color) => color.into(),
|
||||
Err(msg) => bail!(string.span, msg),
|
||||
}
|
||||
} else {
|
||||
struct Component(u8);
|
||||
|
||||
castable! {
|
||||
Component,
|
||||
Expected: "integer or ratio",
|
||||
Value::Int(v) => match v {
|
||||
0 ..= 255 => Self(v as u8),
|
||||
_ => Err("must be between 0 and 255")?,
|
||||
},
|
||||
Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
|
||||
Self((v.get() * 255.0).round() as u8)
|
||||
} else {
|
||||
Err("must be between 0% and 100%")?
|
||||
},
|
||||
}
|
||||
|
||||
let Component(r) = args.expect("red component")?;
|
||||
let Component(g) = args.expect("green component")?;
|
||||
let Component(b) = args.expect("blue component")?;
|
||||
let Component(a) = args.eat()?.unwrap_or(Component(255));
|
||||
RgbaColor::new(r, g, b, a)
|
||||
RgbaColor::new(r, g, b, a).into()
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
/// Create a CMYK color.
|
||||
pub fn cmyk(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
||||
struct Component(u8);
|
||||
|
||||
castable! {
|
||||
Component,
|
||||
Expected: "ratio",
|
||||
Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
|
||||
Self((v.get() * 255.0).round() as u8)
|
||||
} else {
|
||||
Err("must be between 0% and 100%")?
|
||||
},
|
||||
}
|
||||
|
||||
let Component(c) = args.expect("cyan component")?;
|
||||
let Component(m) = args.expect("magenta component")?;
|
||||
let Component(y) = args.expect("yellow component")?;
|
||||
let Component(k) = args.expect("key component")?;
|
||||
let RatioComponent(c) = args.expect("cyan component")?;
|
||||
let RatioComponent(m) = args.expect("magenta component")?;
|
||||
let RatioComponent(y) = args.expect("yellow component")?;
|
||||
let RatioComponent(k) = args.expect("key component")?;
|
||||
Ok(Value::Color(CmykColor::new(c, m, y, k).into()))
|
||||
}
|
||||
|
||||
/// An integer or ratio component.
|
||||
struct Component(u8);
|
||||
|
||||
castable! {
|
||||
Component,
|
||||
Expected: "integer or ratio",
|
||||
Value::Int(v) => match v {
|
||||
0 ..= 255 => Self(v as u8),
|
||||
_ => Err("must be between 0 and 255")?,
|
||||
},
|
||||
Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
|
||||
Self((v.get() * 255.0).round() as u8)
|
||||
} else {
|
||||
Err("must be between 0% and 100%")?
|
||||
},
|
||||
}
|
||||
|
||||
/// A component that must be a ratio.
|
||||
struct RatioComponent(u8);
|
||||
|
||||
castable! {
|
||||
RatioComponent,
|
||||
Expected: "ratio",
|
||||
Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
|
||||
Self((v.get() * 255.0).round() as u8)
|
||||
} else {
|
||||
Err("must be between 0% and 100%")?
|
||||
},
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 168 B After Width: | Height: | Size: 271 B |
@ -8,6 +8,12 @@
|
||||
// Alpha channel.
|
||||
#test(rgb(255, 0, 0, 50%), rgb("ff000080"))
|
||||
|
||||
---
|
||||
// Test gray color conversion.
|
||||
// Ref: true
|
||||
#rect(fill: luma(0))
|
||||
#rect(fill: luma(80%))
|
||||
|
||||
---
|
||||
// Test CMYK color conversion.
|
||||
// Ref: true
|
||||
|
Loading…
x
Reference in New Issue
Block a user