mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
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)
|
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: Name<'static> = Name(b"sRGB");
|
||||||
|
const SRGB_GRAY: Name<'static> = Name(b"sRGBGray");
|
||||||
|
|
||||||
/// An exporter for a whole PDF document.
|
/// An exporter for a whole PDF document.
|
||||||
struct PdfExporter<'a> {
|
struct PdfExporter<'a> {
|
||||||
@ -365,6 +366,11 @@ impl<'a> PdfExporter<'a> {
|
|||||||
|
|
||||||
let mut resources = pages.resources();
|
let mut resources = pages.resources();
|
||||||
resources.color_spaces().insert(SRGB).start::<ColorSpace>().srgb();
|
resources.color_spaces().insert(SRGB).start::<ColorSpace>().srgb();
|
||||||
|
resources
|
||||||
|
.color_spaces()
|
||||||
|
.insert(SRGB_GRAY)
|
||||||
|
.start::<ColorSpace>()
|
||||||
|
.srgb_gray();
|
||||||
|
|
||||||
let mut fonts = resources.fonts();
|
let mut fonts = resources.fonts();
|
||||||
for (font_ref, f) in self.face_map.pdf_indices(&self.face_refs) {
|
for (font_ref, f) in self.face_map.pdf_indices(&self.face_refs) {
|
||||||
@ -437,9 +443,11 @@ struct Page {
|
|||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
struct State {
|
struct State {
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
fill: Option<Paint>,
|
|
||||||
stroke: Option<Stroke>,
|
|
||||||
font: Option<(FaceId, Length)>,
|
font: Option<(FaceId, Length)>,
|
||||||
|
fill: Option<Paint>,
|
||||||
|
fill_space: Option<Name<'static>>,
|
||||||
|
stroke: Option<Stroke>,
|
||||||
|
stroke_space: Option<Name<'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PageExporter<'a> {
|
impl<'a> PageExporter<'a> {
|
||||||
@ -469,8 +477,6 @@ impl<'a> PageExporter<'a> {
|
|||||||
tx: Length::zero(),
|
tx: Length::zero(),
|
||||||
ty: frame.size.y,
|
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);
|
self.write_frame(frame);
|
||||||
Page {
|
Page {
|
||||||
size: frame.size,
|
size: frame.size,
|
||||||
@ -708,6 +714,7 @@ impl<'a> PageExporter<'a> {
|
|||||||
self.font_map.insert(face_id);
|
self.font_map.insert(face_id);
|
||||||
let name = format_eco!("F{}", self.font_map.map(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.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 f = |c| c as f32 / 255.0;
|
||||||
let Paint::Solid(color) = fill;
|
let Paint::Solid(color) = fill;
|
||||||
match color {
|
match color {
|
||||||
|
Color::Luma(c) => {
|
||||||
|
self.set_fill_color_space(SRGB_GRAY);
|
||||||
|
self.content.set_fill_gray(f(c.0));
|
||||||
|
}
|
||||||
Color::Rgba(c) => {
|
Color::Rgba(c) => {
|
||||||
|
self.set_fill_color_space(SRGB);
|
||||||
self.content.set_fill_color([f(c.r), f(c.g), f(c.b)]);
|
self.content.set_fill_color([f(c.r), f(c.g), f(c.b)]);
|
||||||
}
|
}
|
||||||
Color::Cmyk(c) => {
|
Color::Cmyk(c) => {
|
||||||
self.content.set_fill_cmyk(f(c.c), f(c.m), f(c.y), f(c.k));
|
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 f = |c| c as f32 / 255.0;
|
||||||
let Paint::Solid(color) = stroke.paint;
|
let Paint::Solid(color) = stroke.paint;
|
||||||
match color {
|
match color {
|
||||||
|
Color::Luma(c) => {
|
||||||
|
self.set_stroke_color_space(SRGB_GRAY);
|
||||||
|
self.content.set_stroke_gray(f(c.0));
|
||||||
|
}
|
||||||
Color::Rgba(c) => {
|
Color::Rgba(c) => {
|
||||||
|
self.set_stroke_color_space(SRGB);
|
||||||
self.content.set_stroke_color([f(c.r), f(c.g), f(c.b)]);
|
self.content.set_stroke_color([f(c.r), f(c.g), f(c.b)]);
|
||||||
}
|
}
|
||||||
Color::Cmyk(c) => {
|
Color::Cmyk(c) => {
|
||||||
@ -740,6 +765,14 @@ impl<'a> PageExporter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.content.set_line_width(stroke.thickness.to_f32());
|
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.
|
/// A color in a dynamic format.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
|
/// An 8-bit luma color.
|
||||||
|
Luma(LumaColor),
|
||||||
/// An 8-bit RGBA color.
|
/// An 8-bit RGBA color.
|
||||||
Rgba(RgbaColor),
|
Rgba(RgbaColor),
|
||||||
/// An 8-bit CMYK color.
|
/// An 8-bit CMYK color.
|
||||||
@ -60,6 +62,7 @@ impl Color {
|
|||||||
/// Convert this color to RGBA.
|
/// Convert this color to RGBA.
|
||||||
pub fn to_rgba(self) -> RgbaColor {
|
pub fn to_rgba(self) -> RgbaColor {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Luma(luma) => luma.to_rgba(),
|
||||||
Self::Rgba(rgba) => rgba,
|
Self::Rgba(rgba) => rgba,
|
||||||
Self::Cmyk(cmyk) => cmyk.to_rgba(),
|
Self::Cmyk(cmyk) => cmyk.to_rgba(),
|
||||||
}
|
}
|
||||||
@ -69,18 +72,48 @@ impl Color {
|
|||||||
impl Debug for Color {
|
impl Debug for Color {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
|
Self::Luma(c) => Debug::fmt(c, f),
|
||||||
Self::Rgba(c) => Debug::fmt(c, f),
|
Self::Rgba(c) => Debug::fmt(c, f),
|
||||||
Self::Cmyk(c) => Debug::fmt(c, f),
|
Self::Cmyk(c) => Debug::fmt(c, f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<T> for Color
|
/// An 8-bit Luma color.
|
||||||
where
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
T: Into<RgbaColor>,
|
pub struct LumaColor(pub u8);
|
||||||
{
|
|
||||||
fn from(rgba: T) -> Self {
|
impl LumaColor {
|
||||||
Self::Rgba(rgba.into())
|
/// 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 {
|
pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
Self { r, g, b, a }
|
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 {
|
impl FromStr for RgbaColor {
|
||||||
@ -161,11 +189,7 @@ impl From<SynColor> for RgbaColor {
|
|||||||
impl Debug for RgbaColor {
|
impl Debug for RgbaColor {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
if f.alternate() {
|
if f.alternate() {
|
||||||
write!(
|
write!(f, "rgba({}, {}, {}, {})", self.r, self.g, self.b, self.a,)?;
|
||||||
f,
|
|
||||||
"rgba({:02}, {:02}, {:02}, {:02})",
|
|
||||||
self.r, self.g, self.b, self.a,
|
|
||||||
)?;
|
|
||||||
} else {
|
} else {
|
||||||
write!(f, "rgb(\"#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?;
|
write!(f, "rgb(\"#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?;
|
||||||
if self.a != 255 {
|
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.
|
/// An 8-bit CMYK color.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct CmykColor {
|
pub struct CmykColor {
|
||||||
@ -196,16 +229,6 @@ impl CmykColor {
|
|||||||
Self { c, m, y, k }
|
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.
|
/// Convert this color to RGBA.
|
||||||
pub fn to_rgba(self) -> RgbaColor {
|
pub fn to_rgba(self) -> RgbaColor {
|
||||||
let k = self.k as f32 / 255.0;
|
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("odd", utility::odd);
|
||||||
std.def_fn("mod", utility::mod_);
|
std.def_fn("mod", utility::mod_);
|
||||||
std.def_fn("range", utility::range);
|
std.def_fn("range", utility::range);
|
||||||
|
std.def_fn("luma", utility::luma);
|
||||||
std.def_fn("rgb", utility::rgb);
|
std.def_fn("rgb", utility::rgb);
|
||||||
std.def_fn("cmyk", utility::cmyk);
|
std.def_fn("cmyk", utility::cmyk);
|
||||||
std.def_fn("repr", utility::repr);
|
std.def_fn("repr", utility::repr);
|
||||||
|
@ -2,18 +2,43 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use crate::library::prelude::*;
|
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.
|
/// Create an RGB(A) color.
|
||||||
pub fn rgb(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
pub fn rgb(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
||||||
Ok(Value::from(
|
Ok(Value::Color(
|
||||||
if let Some(string) = args.find::<Spanned<EcoString>>()? {
|
if let Some(string) = args.find::<Spanned<EcoString>>()? {
|
||||||
match RgbaColor::from_str(&string.v) {
|
match RgbaColor::from_str(&string.v) {
|
||||||
Ok(color) => color,
|
Ok(color) => color.into(),
|
||||||
Err(msg) => bail!(string.span, msg),
|
Err(msg) => bail!(string.span, msg),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
struct Component(u8);
|
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).into()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
castable! {
|
/// Create a CMYK color.
|
||||||
|
pub fn cmyk(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
||||||
|
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,
|
Component,
|
||||||
Expected: "integer or ratio",
|
Expected: "integer or ratio",
|
||||||
Value::Int(v) => match v {
|
Value::Int(v) => match v {
|
||||||
@ -25,34 +50,17 @@ pub fn rgb(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
|||||||
} else {
|
} else {
|
||||||
Err("must be between 0% and 100%")?
|
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)
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a CMYK color.
|
/// A component that must be a ratio.
|
||||||
pub fn cmyk(_: &mut Machine, args: &mut Args) -> TypResult<Value> {
|
struct RatioComponent(u8);
|
||||||
struct Component(u8);
|
|
||||||
|
|
||||||
castable! {
|
castable! {
|
||||||
Component,
|
RatioComponent,
|
||||||
Expected: "ratio",
|
Expected: "ratio",
|
||||||
Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
|
Value::Ratio(v) => if (0.0 ..= 1.0).contains(&v.get()) {
|
||||||
Self((v.get() * 255.0).round() as u8)
|
Self((v.get() * 255.0).round() as u8)
|
||||||
} else {
|
} else {
|
||||||
Err("must be between 0% and 100%")?
|
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")?;
|
|
||||||
Ok(Value::Color(CmykColor::new(c, m, y, k).into()))
|
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 168 B After Width: | Height: | Size: 271 B |
@ -8,6 +8,12 @@
|
|||||||
// Alpha channel.
|
// Alpha channel.
|
||||||
#test(rgb(255, 0, 0, 50%), rgb("ff000080"))
|
#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.
|
// Test CMYK color conversion.
|
||||||
// Ref: true
|
// Ref: true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user