diff --git a/crates/typst-pdf/src/color.rs b/crates/typst-pdf/src/color.rs index e414d255d..ccc67b286 100644 --- a/crates/typst-pdf/src/color.rs +++ b/crates/typst-pdf/src/color.rs @@ -1,6 +1,6 @@ use arrayvec::ArrayVec; use once_cell::sync::Lazy; -use pdf_writer::{types::DeviceNSubtype, writers, Chunk, Dict, Filter, Name, Ref}; +use pdf_writer::{writers, Chunk, Dict, Filter, Name, Ref}; use typst::visualize::{Color, ColorSpace, Paint}; use crate::{content, deflate, PdfChunk, Renumber, WithResources}; @@ -8,28 +8,17 @@ use crate::{content, deflate, PdfChunk, Renumber, WithResources}; // The names of the color spaces. pub const SRGB: Name<'static> = Name(b"srgb"); pub const D65_GRAY: Name<'static> = Name(b"d65gray"); -pub const OKLAB: Name<'static> = Name(b"oklab"); pub const LINEAR_SRGB: Name<'static> = Name(b"linearrgb"); -// The names of the color components. -const OKLAB_L: Name<'static> = Name(b"L"); -const OKLAB_A: Name<'static> = Name(b"A"); -const OKLAB_B: Name<'static> = Name(b"B"); - // The ICC profiles. static SRGB_ICC_DEFLATED: Lazy> = Lazy::new(|| deflate(typst_assets::icc::S_RGB_V4)); static GRAY_ICC_DEFLATED: Lazy> = Lazy::new(|| deflate(typst_assets::icc::S_GREY_V4)); -// The PostScript functions for color spaces. -static OKLAB_DEFLATED: Lazy> = - Lazy::new(|| deflate(minify(include_str!("oklab.ps")).as_bytes())); - /// The color spaces present in the PDF document #[derive(Default)] pub struct ColorSpaces { - use_oklab: bool, use_srgb: bool, use_d65_gray: bool, use_linear_rgb: bool, @@ -39,11 +28,11 @@ impl ColorSpaces { /// Mark a color space as used. pub fn mark_as_used(&mut self, color_space: ColorSpace) { match color_space { - ColorSpace::Oklch | ColorSpace::Oklab | ColorSpace::Hsl | ColorSpace::Hsv => { - self.use_oklab = true; - self.use_linear_rgb = true; - } - ColorSpace::Srgb => { + ColorSpace::Oklch + | ColorSpace::Oklab + | ColorSpace::Hsl + | ColorSpace::Hsv + | ColorSpace::Srgb => { self.use_srgb = true; } ColorSpace::D65Gray => { @@ -58,10 +47,6 @@ impl ColorSpaces { /// Write the color spaces to the PDF file. pub fn write_color_spaces(&self, mut spaces: Dict, refs: &ColorFunctionRefs) { - if self.use_oklab { - write(ColorSpace::Oklab, spaces.insert(OKLAB).start(), refs); - } - if self.use_srgb { write(ColorSpace::Srgb, spaces.insert(SRGB).start(), refs); } @@ -78,15 +63,6 @@ impl ColorSpaces { /// Write the necessary color spaces functions and ICC profiles to the /// PDF file. pub fn write_functions(&self, chunk: &mut Chunk, refs: &ColorFunctionRefs) { - // Write the Oklab function & color space. - if self.use_oklab { - chunk - .post_script_function(refs.oklab.unwrap(), &OKLAB_DEFLATED) - .domain([0.0, 1.0, 0.0, 1.0, 0.0, 1.0]) - .range([0.0, 1.0, 0.0, 1.0, 0.0, 1.0]) - .filter(Filter::FlateDecode); - } - // Write the sRGB color space. if self.use_srgb { chunk @@ -111,7 +87,6 @@ impl ColorSpaces { pub fn merge(&mut self, other: &Self) { self.use_d65_gray |= other.use_d65_gray; self.use_linear_rgb |= other.use_linear_rgb; - self.use_oklab |= other.use_oklab; self.use_srgb |= other.use_srgb; } } @@ -123,14 +98,11 @@ pub fn write( refs: &ColorFunctionRefs, ) { match color_space { - ColorSpace::Oklab | ColorSpace::Hsl | ColorSpace::Hsv => { - let mut oklab = writer.device_n([OKLAB_L, OKLAB_A, OKLAB_B]); - write(ColorSpace::LinearRgb, oklab.alternate_color_space(), refs); - oklab.tint_ref(refs.oklab.unwrap()); - oklab.attrs().subtype(DeviceNSubtype::DeviceN); - } - ColorSpace::Oklch => write(ColorSpace::Oklab, writer, refs), - ColorSpace::Srgb => writer.icc_based(refs.srgb.unwrap()), + ColorSpace::Srgb + | ColorSpace::Oklab + | ColorSpace::Hsl + | ColorSpace::Hsv + | ColorSpace::Oklch => writer.icc_based(refs.srgb.unwrap()), ColorSpace::D65Gray => writer.icc_based(refs.d65_gray.unwrap()), ColorSpace::LinearRgb => { writer.cal_rgb( @@ -152,16 +124,12 @@ pub fn write( /// needed) in the final document, and be shared by all color space /// dictionaries. pub struct ColorFunctionRefs { - oklab: Option, srgb: Option, d65_gray: Option, } impl Renumber for ColorFunctionRefs { fn renumber(&mut self, offset: i32) { - if let Some(r) = &mut self.oklab { - r.renumber(offset); - } if let Some(r) = &mut self.srgb { r.renumber(offset); } @@ -183,7 +151,6 @@ pub fn alloc_color_functions_refs( }); let refs = ColorFunctionRefs { - oklab: if used_color_spaces.use_oklab { Some(chunk.alloc()) } else { None }, srgb: if used_color_spaces.use_srgb { Some(chunk.alloc()) } else { None }, d65_gray: if used_color_spaces.use_d65_gray { Some(chunk.alloc()) } else { None }, }; @@ -191,28 +158,6 @@ pub fn alloc_color_functions_refs( (chunk, refs) } -/// This function removes comments, line spaces and carriage returns from a -/// PostScript program. This is necessary to optimize the size of the PDF file. -fn minify(source: &str) -> String { - let mut buf = String::with_capacity(source.len()); - let mut s = unscanny::Scanner::new(source); - while let Some(c) = s.eat() { - match c { - '%' => { - s.eat_until('\n'); - } - c if c.is_whitespace() => { - s.eat_whitespace(); - if buf.ends_with(|c: char| !c.is_whitespace()) { - buf.push(' '); - } - } - _ => buf.push(c), - } - } - buf -} - /// Encodes the color into four f32s, which can be used in a PDF file. /// Ensures that the values are in the range [0.0, 1.0]. /// @@ -233,13 +178,7 @@ impl ColorEncode for ColorSpace { fn encode(&self, color: Color) -> [f32; 4] { match self { ColorSpace::Oklab | ColorSpace::Oklch | ColorSpace::Hsl | ColorSpace::Hsv => { - let [l, c, h, alpha] = color.to_oklch().to_vec4(); - // Clamp on Oklch's chroma, not Oklab's a\* and b\* as to not distort hue. - let c = c.clamp(0.0, 0.5); - // Convert cylindrical coordinates back to rectangular ones. - let a = c * h.to_radians().cos(); - let b = c * h.to_radians().sin(); - [l, a + 0.5, b + 0.5, alpha] + color.to_space(ColorSpace::Srgb).to_vec4() } _ => color.to_space(*self).to_vec4(), } @@ -303,14 +242,6 @@ impl PaintEncode for Color { let [l, _, _, _] = ColorSpace::D65Gray.encode(*self); ctx.content.set_fill_color([l]); } - // Oklch is converted to Oklab. - Color::Oklab(_) | Color::Oklch(_) | Color::Hsl(_) | Color::Hsv(_) => { - ctx.resources.colors.mark_as_used(ColorSpace::Oklab); - ctx.set_fill_color_space(OKLAB); - - let [l, a, b, _] = ColorSpace::Oklab.encode(*self); - ctx.content.set_fill_color([l, a, b]); - } Color::LinearRgb(_) => { ctx.resources.colors.mark_as_used(ColorSpace::LinearRgb); ctx.set_fill_color_space(LINEAR_SRGB); @@ -318,7 +249,12 @@ impl PaintEncode for Color { let [r, g, b, _] = ColorSpace::LinearRgb.encode(*self); ctx.content.set_fill_color([r, g, b]); } - Color::Rgb(_) => { + // Oklab & friends are encoded as RGB. + Color::Rgb(_) + | Color::Oklab(_) + | Color::Oklch(_) + | Color::Hsl(_) + | Color::Hsv(_) => { ctx.resources.colors.mark_as_used(ColorSpace::Srgb); ctx.set_fill_color_space(SRGB); @@ -343,14 +279,6 @@ impl PaintEncode for Color { let [l, _, _, _] = ColorSpace::D65Gray.encode(*self); ctx.content.set_stroke_color([l]); } - // Oklch is converted to Oklab. - Color::Oklab(_) | Color::Oklch(_) | Color::Hsl(_) | Color::Hsv(_) => { - ctx.resources.colors.mark_as_used(ColorSpace::Oklab); - ctx.set_stroke_color_space(OKLAB); - - let [l, a, b, _] = ColorSpace::Oklab.encode(*self); - ctx.content.set_stroke_color([l, a, b]); - } Color::LinearRgb(_) => { ctx.resources.colors.mark_as_used(ColorSpace::LinearRgb); ctx.set_stroke_color_space(LINEAR_SRGB); @@ -358,7 +286,12 @@ impl PaintEncode for Color { let [r, g, b, _] = ColorSpace::LinearRgb.encode(*self); ctx.content.set_stroke_color([r, g, b]); } - Color::Rgb(_) => { + // Oklab & friends are encoded as RGB. + Color::Rgb(_) + | Color::Oklab(_) + | Color::Oklch(_) + | Color::Hsl(_) + | Color::Hsv(_) => { ctx.resources.colors.mark_as_used(ColorSpace::Srgb); ctx.set_stroke_color_space(SRGB); diff --git a/crates/typst-pdf/src/gradient.rs b/crates/typst-pdf/src/gradient.rs index 81dc27483..280783658 100644 --- a/crates/typst-pdf/src/gradient.rs +++ b/crates/typst-pdf/src/gradient.rs @@ -181,9 +181,9 @@ fn shading_function( for window in gradient.stops_ref().windows(2) { let (first, second) = (window[0], window[1]); - // If we have a hue index, we will create several stops in-between - // to make the gradient smoother without interpolation issues with - // native color spaces. + // If we have a hue index or are using Oklab, we will create several + // stops in-between to make the gradient smoother without interpolation + // issues with native color spaces. let mut last_c = first.0; if gradient.space().hue_index().is_some() { for i in 0..=32 { diff --git a/crates/typst-pdf/src/oklab.ps b/crates/typst-pdf/src/oklab.ps deleted file mode 100644 index e766bbd84..000000000 --- a/crates/typst-pdf/src/oklab.ps +++ /dev/null @@ -1,78 +0,0 @@ -{ - % Starting stack: L, A, B - % /!\ WARNING: The A and B components **MUST** be encoded - % in the range [0, 1] before calling this function. - % This is because the function assumes that the - % A and B components are offset by a factor of 0.5 - % in order to meet the range requirements of the - % PDF specification. - - exch 0.5 sub - exch 0.5 sub - - % Load L a and b into the stack - 2 index - 2 index - 2 index - - % Compute f1 = ((0.3963377774 * a) + (0.2158037573 * b) + L)^3 - 0.2158037573 mul exch - 0.3963377774 mul add add - dup dup mul mul - - % Load L, a, and b into the stack - 3 index - 3 index - 3 index - - % Compute f2 = ((-0.1055613458 * a) + (-0.0638541728 * b) + L)^3 - -0.0638541728 mul exch - -0.1055613458 mul add add - dup dup mul mul - - % Load L, a, and b into the stack - 4 index - 4 index - 4 index - - % Compute f3 = ((-0.0894841775 * a) + (-1.2914855480 * b) + L)^3 - -1.2914855480 mul exch - -0.0894841775 mul add add - dup dup mul mul - - % Discard L, a, and b by rolling the stack and popping - 6 3 roll pop pop pop - - % Load f1, f2, and f3 into the stack - 2 index - 2 index - 2 index - - % Compute R = f1 * 4.0767416621 + f2 * -3.3077115913 + f3 * 0.2309699292 - 0.2309699292 mul exch - -3.3077115913 mul add exch - 4.0767416621 mul add - - % Load f1, f2, and f3 into the stack - 3 index - 3 index - 3 index - - % Compute G = f1 * -1.2684380046 + f2 * 2.6097574011 + f3 * -0.3413193965 - -0.3413193965 mul exch - 2.6097574011 mul add exch - -1.2684380046 mul add - - % Load f1, f2, and f3 into the stack - 4 index - 4 index - 4 index - - % Compute B = f1 * -0.0041960863 + f2 * -0.7034186147 + f3 * 1.7076147010 - 1.7076147010 mul exch - -0.7034186147 mul add exch - -0.0041960863 mul add - - % Discard f1, f2, and f3 by rolling the stack and popping - 6 3 roll pop pop pop -} diff --git a/crates/typst/src/visualize/gradient.rs b/crates/typst/src/visualize/gradient.rs index 19435ebd6..452818209 100644 --- a/crates/typst/src/visualize/gradient.rs +++ b/crates/typst/src/visualize/gradient.rs @@ -169,11 +169,12 @@ use crate::visualize::{Color, ColorSpace, WeightedColor}; /// consider the following: /// - SVG gradients are currently inefficiently encoded. This will be improved /// in the future. -/// - PDF gradients in the [`color.hsv`]($color.hsv), [`color.hsl`]($color.hsl), -/// and [`color.oklch`]($color.oklch) color spaces are stored as a list of -/// [`color.oklab`]($color.oklab) colors with extra stops in between. This -/// avoids needing to encode these color spaces in your PDF file, but it does -/// add extra stops to your gradient, which can increase the file size. +/// - PDF gradients in the [`color.oklab`]($color.oklab), [`color.hsv`]($color.hsv), +/// [`color.hsl`]($color.hsl), and [`color.oklch`]($color.oklch) color spaces +/// are stored as a list of [`color.rgb`]($color.rgb) colors with extra stops +/// in between. This avoids needing to encode these color spaces in your PDF +/// file, but it does add extra stops to your gradient, which can increase +/// the file size. #[ty(scope, cast)] #[derive(Clone, PartialEq, Eq, Hash)] pub enum Gradient {