mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Nuked custom PDF Oklab colorspace code (#4871)
This commit is contained in:
parent
51df7aee76
commit
ecad396cc8
@ -1,6 +1,6 @@
|
|||||||
use arrayvec::ArrayVec;
|
use arrayvec::ArrayVec;
|
||||||
use once_cell::sync::Lazy;
|
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 typst::visualize::{Color, ColorSpace, Paint};
|
||||||
|
|
||||||
use crate::{content, deflate, PdfChunk, Renumber, WithResources};
|
use crate::{content, deflate, PdfChunk, Renumber, WithResources};
|
||||||
@ -8,28 +8,17 @@ use crate::{content, deflate, PdfChunk, Renumber, WithResources};
|
|||||||
// The names of the color spaces.
|
// The names of the color spaces.
|
||||||
pub const SRGB: Name<'static> = Name(b"srgb");
|
pub const SRGB: Name<'static> = Name(b"srgb");
|
||||||
pub const D65_GRAY: Name<'static> = Name(b"d65gray");
|
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");
|
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.
|
// The ICC profiles.
|
||||||
static SRGB_ICC_DEFLATED: Lazy<Vec<u8>> =
|
static SRGB_ICC_DEFLATED: Lazy<Vec<u8>> =
|
||||||
Lazy::new(|| deflate(typst_assets::icc::S_RGB_V4));
|
Lazy::new(|| deflate(typst_assets::icc::S_RGB_V4));
|
||||||
static GRAY_ICC_DEFLATED: Lazy<Vec<u8>> =
|
static GRAY_ICC_DEFLATED: Lazy<Vec<u8>> =
|
||||||
Lazy::new(|| deflate(typst_assets::icc::S_GREY_V4));
|
Lazy::new(|| deflate(typst_assets::icc::S_GREY_V4));
|
||||||
|
|
||||||
// The PostScript functions for color spaces.
|
|
||||||
static OKLAB_DEFLATED: Lazy<Vec<u8>> =
|
|
||||||
Lazy::new(|| deflate(minify(include_str!("oklab.ps")).as_bytes()));
|
|
||||||
|
|
||||||
/// The color spaces present in the PDF document
|
/// The color spaces present in the PDF document
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ColorSpaces {
|
pub struct ColorSpaces {
|
||||||
use_oklab: bool,
|
|
||||||
use_srgb: bool,
|
use_srgb: bool,
|
||||||
use_d65_gray: bool,
|
use_d65_gray: bool,
|
||||||
use_linear_rgb: bool,
|
use_linear_rgb: bool,
|
||||||
@ -39,11 +28,11 @@ impl ColorSpaces {
|
|||||||
/// Mark a color space as used.
|
/// Mark a color space as used.
|
||||||
pub fn mark_as_used(&mut self, color_space: ColorSpace) {
|
pub fn mark_as_used(&mut self, color_space: ColorSpace) {
|
||||||
match color_space {
|
match color_space {
|
||||||
ColorSpace::Oklch | ColorSpace::Oklab | ColorSpace::Hsl | ColorSpace::Hsv => {
|
ColorSpace::Oklch
|
||||||
self.use_oklab = true;
|
| ColorSpace::Oklab
|
||||||
self.use_linear_rgb = true;
|
| ColorSpace::Hsl
|
||||||
}
|
| ColorSpace::Hsv
|
||||||
ColorSpace::Srgb => {
|
| ColorSpace::Srgb => {
|
||||||
self.use_srgb = true;
|
self.use_srgb = true;
|
||||||
}
|
}
|
||||||
ColorSpace::D65Gray => {
|
ColorSpace::D65Gray => {
|
||||||
@ -58,10 +47,6 @@ impl ColorSpaces {
|
|||||||
|
|
||||||
/// Write the color spaces to the PDF file.
|
/// Write the color spaces to the PDF file.
|
||||||
pub fn write_color_spaces(&self, mut spaces: Dict, refs: &ColorFunctionRefs) {
|
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 {
|
if self.use_srgb {
|
||||||
write(ColorSpace::Srgb, spaces.insert(SRGB).start(), refs);
|
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
|
/// Write the necessary color spaces functions and ICC profiles to the
|
||||||
/// PDF file.
|
/// PDF file.
|
||||||
pub fn write_functions(&self, chunk: &mut Chunk, refs: &ColorFunctionRefs) {
|
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.
|
// Write the sRGB color space.
|
||||||
if self.use_srgb {
|
if self.use_srgb {
|
||||||
chunk
|
chunk
|
||||||
@ -111,7 +87,6 @@ impl ColorSpaces {
|
|||||||
pub fn merge(&mut self, other: &Self) {
|
pub fn merge(&mut self, other: &Self) {
|
||||||
self.use_d65_gray |= other.use_d65_gray;
|
self.use_d65_gray |= other.use_d65_gray;
|
||||||
self.use_linear_rgb |= other.use_linear_rgb;
|
self.use_linear_rgb |= other.use_linear_rgb;
|
||||||
self.use_oklab |= other.use_oklab;
|
|
||||||
self.use_srgb |= other.use_srgb;
|
self.use_srgb |= other.use_srgb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,14 +98,11 @@ pub fn write(
|
|||||||
refs: &ColorFunctionRefs,
|
refs: &ColorFunctionRefs,
|
||||||
) {
|
) {
|
||||||
match color_space {
|
match color_space {
|
||||||
ColorSpace::Oklab | ColorSpace::Hsl | ColorSpace::Hsv => {
|
ColorSpace::Srgb
|
||||||
let mut oklab = writer.device_n([OKLAB_L, OKLAB_A, OKLAB_B]);
|
| ColorSpace::Oklab
|
||||||
write(ColorSpace::LinearRgb, oklab.alternate_color_space(), refs);
|
| ColorSpace::Hsl
|
||||||
oklab.tint_ref(refs.oklab.unwrap());
|
| ColorSpace::Hsv
|
||||||
oklab.attrs().subtype(DeviceNSubtype::DeviceN);
|
| ColorSpace::Oklch => writer.icc_based(refs.srgb.unwrap()),
|
||||||
}
|
|
||||||
ColorSpace::Oklch => write(ColorSpace::Oklab, writer, refs),
|
|
||||||
ColorSpace::Srgb => writer.icc_based(refs.srgb.unwrap()),
|
|
||||||
ColorSpace::D65Gray => writer.icc_based(refs.d65_gray.unwrap()),
|
ColorSpace::D65Gray => writer.icc_based(refs.d65_gray.unwrap()),
|
||||||
ColorSpace::LinearRgb => {
|
ColorSpace::LinearRgb => {
|
||||||
writer.cal_rgb(
|
writer.cal_rgb(
|
||||||
@ -152,16 +124,12 @@ pub fn write(
|
|||||||
/// needed) in the final document, and be shared by all color space
|
/// needed) in the final document, and be shared by all color space
|
||||||
/// dictionaries.
|
/// dictionaries.
|
||||||
pub struct ColorFunctionRefs {
|
pub struct ColorFunctionRefs {
|
||||||
oklab: Option<Ref>,
|
|
||||||
srgb: Option<Ref>,
|
srgb: Option<Ref>,
|
||||||
d65_gray: Option<Ref>,
|
d65_gray: Option<Ref>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renumber for ColorFunctionRefs {
|
impl Renumber for ColorFunctionRefs {
|
||||||
fn renumber(&mut self, offset: i32) {
|
fn renumber(&mut self, offset: i32) {
|
||||||
if let Some(r) = &mut self.oklab {
|
|
||||||
r.renumber(offset);
|
|
||||||
}
|
|
||||||
if let Some(r) = &mut self.srgb {
|
if let Some(r) = &mut self.srgb {
|
||||||
r.renumber(offset);
|
r.renumber(offset);
|
||||||
}
|
}
|
||||||
@ -183,7 +151,6 @@ pub fn alloc_color_functions_refs(
|
|||||||
});
|
});
|
||||||
|
|
||||||
let refs = ColorFunctionRefs {
|
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 },
|
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 },
|
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)
|
(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.
|
/// 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].
|
/// 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] {
|
fn encode(&self, color: Color) -> [f32; 4] {
|
||||||
match self {
|
match self {
|
||||||
ColorSpace::Oklab | ColorSpace::Oklch | ColorSpace::Hsl | ColorSpace::Hsv => {
|
ColorSpace::Oklab | ColorSpace::Oklch | ColorSpace::Hsl | ColorSpace::Hsv => {
|
||||||
let [l, c, h, alpha] = color.to_oklch().to_vec4();
|
color.to_space(ColorSpace::Srgb).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(*self).to_vec4(),
|
_ => color.to_space(*self).to_vec4(),
|
||||||
}
|
}
|
||||||
@ -303,14 +242,6 @@ impl PaintEncode for Color {
|
|||||||
let [l, _, _, _] = ColorSpace::D65Gray.encode(*self);
|
let [l, _, _, _] = ColorSpace::D65Gray.encode(*self);
|
||||||
ctx.content.set_fill_color([l]);
|
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(_) => {
|
Color::LinearRgb(_) => {
|
||||||
ctx.resources.colors.mark_as_used(ColorSpace::LinearRgb);
|
ctx.resources.colors.mark_as_used(ColorSpace::LinearRgb);
|
||||||
ctx.set_fill_color_space(LINEAR_SRGB);
|
ctx.set_fill_color_space(LINEAR_SRGB);
|
||||||
@ -318,7 +249,12 @@ impl PaintEncode for Color {
|
|||||||
let [r, g, b, _] = ColorSpace::LinearRgb.encode(*self);
|
let [r, g, b, _] = ColorSpace::LinearRgb.encode(*self);
|
||||||
ctx.content.set_fill_color([r, g, b]);
|
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.resources.colors.mark_as_used(ColorSpace::Srgb);
|
||||||
ctx.set_fill_color_space(SRGB);
|
ctx.set_fill_color_space(SRGB);
|
||||||
|
|
||||||
@ -343,14 +279,6 @@ impl PaintEncode for Color {
|
|||||||
let [l, _, _, _] = ColorSpace::D65Gray.encode(*self);
|
let [l, _, _, _] = ColorSpace::D65Gray.encode(*self);
|
||||||
ctx.content.set_stroke_color([l]);
|
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(_) => {
|
Color::LinearRgb(_) => {
|
||||||
ctx.resources.colors.mark_as_used(ColorSpace::LinearRgb);
|
ctx.resources.colors.mark_as_used(ColorSpace::LinearRgb);
|
||||||
ctx.set_stroke_color_space(LINEAR_SRGB);
|
ctx.set_stroke_color_space(LINEAR_SRGB);
|
||||||
@ -358,7 +286,12 @@ impl PaintEncode for Color {
|
|||||||
let [r, g, b, _] = ColorSpace::LinearRgb.encode(*self);
|
let [r, g, b, _] = ColorSpace::LinearRgb.encode(*self);
|
||||||
ctx.content.set_stroke_color([r, g, b]);
|
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.resources.colors.mark_as_used(ColorSpace::Srgb);
|
||||||
ctx.set_stroke_color_space(SRGB);
|
ctx.set_stroke_color_space(SRGB);
|
||||||
|
|
||||||
|
@ -181,9 +181,9 @@ fn shading_function(
|
|||||||
for window in gradient.stops_ref().windows(2) {
|
for window in gradient.stops_ref().windows(2) {
|
||||||
let (first, second) = (window[0], window[1]);
|
let (first, second) = (window[0], window[1]);
|
||||||
|
|
||||||
// If we have a hue index, we will create several stops in-between
|
// If we have a hue index or are using Oklab, we will create several
|
||||||
// to make the gradient smoother without interpolation issues with
|
// stops in-between to make the gradient smoother without interpolation
|
||||||
// native color spaces.
|
// issues with native color spaces.
|
||||||
let mut last_c = first.0;
|
let mut last_c = first.0;
|
||||||
if gradient.space().hue_index().is_some() {
|
if gradient.space().hue_index().is_some() {
|
||||||
for i in 0..=32 {
|
for i in 0..=32 {
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -169,11 +169,12 @@ use crate::visualize::{Color, ColorSpace, WeightedColor};
|
|||||||
/// consider the following:
|
/// consider the following:
|
||||||
/// - SVG gradients are currently inefficiently encoded. This will be improved
|
/// - SVG gradients are currently inefficiently encoded. This will be improved
|
||||||
/// in the future.
|
/// in the future.
|
||||||
/// - PDF gradients in the [`color.hsv`]($color.hsv), [`color.hsl`]($color.hsl),
|
/// - PDF gradients in the [`color.oklab`]($color.oklab), [`color.hsv`]($color.hsv),
|
||||||
/// and [`color.oklch`]($color.oklch) color spaces are stored as a list of
|
/// [`color.hsl`]($color.hsl), and [`color.oklch`]($color.oklch) color spaces
|
||||||
/// [`color.oklab`]($color.oklab) colors with extra stops in between. This
|
/// are stored as a list of [`color.rgb`]($color.rgb) colors with extra stops
|
||||||
/// avoids needing to encode these color spaces in your PDF file, but it does
|
/// in between. This avoids needing to encode these color spaces in your PDF
|
||||||
/// add extra stops to your gradient, which can increase the file size.
|
/// file, but it does add extra stops to your gradient, which can increase
|
||||||
|
/// the file size.
|
||||||
#[ty(scope, cast)]
|
#[ty(scope, cast)]
|
||||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Gradient {
|
pub enum Gradient {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user