Skip to content

Commit 75fa957

Browse files
committed
Implement better handling of color spaces
1 parent 07f2538 commit 75fa957

File tree

5 files changed

+128
-82
lines changed

5 files changed

+128
-82
lines changed

crates/krilla/src/content.rs

Lines changed: 69 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use skrifa::GlyphId;
1414
use tiny_skia_path::Size;
1515
use tiny_skia_path::{NormalizedF32, Path, PathSegment, Point, Rect, Transform};
1616

17-
use crate::color::Color;
17+
use crate::color::{Color, ColorSpace};
1818
use crate::font::{Font, Glyph, GlyphUnits};
1919
use crate::graphics_state::GraphicsStates;
2020
#[cfg(feature = "raster-images")]
@@ -32,7 +32,7 @@ use crate::paint::{InnerPaint, Paint};
3232
use crate::path::{Fill, FillRule, LineCap, LineJoin, Stroke};
3333
use crate::resource;
3434
use crate::resource::{Resource, ResourceDictionaryBuilder};
35-
use crate::serialize::SerializeContext;
35+
use crate::serialize::{MaybeDeviceColorSpace, SerializeContext};
3636
use crate::stream::Stream;
3737
use crate::tagging::ContentTag;
3838
use crate::util::{calculate_stroke_bbox, LineCapExt, LineJoinExt, NameExt, RectExt, TransformExt};
@@ -48,6 +48,14 @@ pub(crate) struct ContentBuilder {
4848
pub(crate) active_marked_content: bool,
4949
}
5050

51+
/// Stores either a device-specific color space,
52+
/// or the name of a different colorspace (e.g. ICCBased) stored in
53+
/// the current resource dictionary
54+
enum ContentColorSpace {
55+
Device,
56+
Named(String),
57+
}
58+
5159
impl ContentBuilder {
5260
pub(crate) fn new(root_transform: Transform) -> Self {
5361
Self {
@@ -653,7 +661,7 @@ impl ContentBuilder {
653661
opacity: NormalizedF32,
654662
sc: &mut SerializeContext,
655663
mut set_pattern_fn: impl FnMut(&mut Content, String),
656-
mut set_solid_fn: impl FnMut(&mut Content, String, Color),
664+
mut set_solid_fn: impl FnMut(&mut Content, ContentColorSpace, Color),
657665
) {
658666
let pattern_transform = |transform: Transform| -> Transform {
659667
transform.post_concat(self.cur_transform_with_root_transform())
@@ -667,10 +675,8 @@ impl ContentBuilder {
667675
if let Some((color, opacity)) = gradient_props.single_stop_color() {
668676
// Write gradients with one stop as a solid color fill.
669677
content_builder.set_fill_opacity(opacity);
670-
let color_space = color.color_space(sc);
671-
let color_space_resource = content_builder
672-
.rd_builder
673-
.register_resource(sc.register_colorspace(color_space));
678+
let cs = color.color_space(sc);
679+
let color_space_resource = Self::cs_to_content_cs(content_builder, sc, cs);
674680
set_solid_fn(&mut content_builder.content, color_space_resource, color);
675681
} else {
676682
let shading_mask =
@@ -706,9 +712,7 @@ impl ContentBuilder {
706712
match &paint.0 {
707713
InnerPaint::Color(c) => {
708714
let cs = c.color_space(sc);
709-
let color_space_resource = self
710-
.rd_builder
711-
.register_resource(sc.register_colorspace(cs));
715+
let color_space_resource = Self::cs_to_content_cs(self, sc, cs);
712716
set_solid_fn(&mut self.content, color_space_resource, *c);
713717
}
714718
InnerPaint::LinearGradient(lg) => {
@@ -744,6 +748,21 @@ impl ContentBuilder {
744748
}
745749
}
746750

751+
fn cs_to_content_cs(
752+
content_builder: &mut ContentBuilder,
753+
sc: &mut SerializeContext,
754+
cs: ColorSpace,
755+
) -> ContentColorSpace {
756+
match sc.register_colorspace(cs) {
757+
MaybeDeviceColorSpace::ColorSpace(s) => {
758+
ContentColorSpace::Named(content_builder.rd_builder.register_resource(s))
759+
}
760+
MaybeDeviceColorSpace::DeviceGray => ContentColorSpace::Device,
761+
MaybeDeviceColorSpace::DeviceRgb => ContentColorSpace::Device,
762+
MaybeDeviceColorSpace::DeviceCMYK => ContentColorSpace::Device,
763+
}
764+
}
765+
747766
fn content_set_fill_properties(
748767
&mut self,
749768
bounds: Rect,
@@ -755,9 +774,26 @@ impl ContentBuilder {
755774
content.set_fill_pattern(None, color_space.to_pdf_name());
756775
}
757776

758-
fn set_solid_fn(content: &mut Content, color_space: String, color: Color) {
759-
content.set_fill_color_space(color_space.to_pdf_name());
760-
content.set_fill_color(color.to_pdf_color());
777+
fn set_solid_fn(content: &mut Content, color_space: ContentColorSpace, color: Color) {
778+
match color_space {
779+
ContentColorSpace::Device => match color {
780+
Color::Rgb(r) => {
781+
let comps = r.to_pdf_color();
782+
content.set_fill_rgb(comps[0], comps[1], comps[2]);
783+
}
784+
Color::Luma(l) => {
785+
content.set_fill_gray(l.to_pdf_color());
786+
}
787+
Color::Cmyk(c) => {
788+
let comps = c.to_pdf_color();
789+
content.set_fill_cmyk(comps[0], comps[1], comps[2], comps[3]);
790+
}
791+
},
792+
ContentColorSpace::Named(n) => {
793+
content.set_fill_color_space(n.to_pdf_name());
794+
content.set_fill_color(color.to_pdf_color());
795+
}
796+
}
761797
}
762798

763799
self.content_set_fill_stroke_properties(
@@ -781,9 +817,26 @@ impl ContentBuilder {
781817
content.set_stroke_pattern(None, color_space.to_pdf_name());
782818
}
783819

784-
fn set_solid_fn(content: &mut Content, color_space: String, color: Color) {
785-
content.set_stroke_color_space(color_space.to_pdf_name());
786-
content.set_stroke_color(color.to_pdf_color());
820+
fn set_solid_fn(content: &mut Content, color_space: ContentColorSpace, color: Color) {
821+
match color_space {
822+
ContentColorSpace::Device => match color {
823+
Color::Rgb(r) => {
824+
let comps = r.to_pdf_color();
825+
content.set_stroke_rgb(comps[0], comps[1], comps[2]);
826+
}
827+
Color::Luma(l) => {
828+
content.set_stroke_gray(l.to_pdf_color());
829+
}
830+
Color::Cmyk(c) => {
831+
let comps = c.to_pdf_color();
832+
content.set_stroke_cmyk(comps[0], comps[1], comps[2], comps[3]);
833+
}
834+
},
835+
ContentColorSpace::Named(n) => {
836+
content.set_stroke_color_space(n.to_pdf_name());
837+
content.set_stroke_color(color.to_pdf_color());
838+
}
839+
}
787840
}
788841

789842
self.content_set_fill_stroke_properties(

crates/krilla/src/object/color.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ pub(crate) enum Color {
7373
impl Color {
7474
pub(crate) fn to_pdf_color(self) -> Vec<f32> {
7575
match self {
76-
Color::Rgb(rgb) => rgb.to_pdf_color().into_iter().collect::<Vec<_>>(),
77-
Color::Luma(l) => l.to_pdf_color().into_iter().collect::<Vec<_>>(),
78-
Color::Cmyk(cmyk) => cmyk.to_pdf_color().into_iter().collect::<Vec<_>>(),
76+
Color::Rgb(rgb) => rgb.to_pdf_color().to_vec(),
77+
Color::Luma(l) => vec![l.to_pdf_color()],
78+
Color::Cmyk(cmyk) => cmyk.to_pdf_color().to_vec(),
7979
}
8080
}
8181

@@ -118,8 +118,8 @@ pub mod luma {
118118
Self::new(255)
119119
}
120120

121-
pub(crate) fn to_pdf_color(self) -> impl IntoIterator<Item = f32> {
122-
[self.0 as f32 / 255.0]
121+
pub(crate) fn to_pdf_color(self) -> f32 {
122+
self.0 as f32 / 255.0
123123
}
124124

125125
pub(crate) fn color_space(no_device_cs: bool) -> ColorSpace {
@@ -160,7 +160,7 @@ pub mod cmyk {
160160
Color(cyan, magenta, yellow, black)
161161
}
162162

163-
pub(crate) fn to_pdf_color(self) -> impl IntoIterator<Item = f32> {
163+
pub(crate) fn to_pdf_color(self) -> [f32; 4] {
164164
[
165165
self.0 as f32 / 255.0,
166166
self.1 as f32 / 255.0,
@@ -229,8 +229,8 @@ pub mod rgb {
229229
Self::new(255, 255, 255)
230230
}
231231

232-
pub(crate) fn to_pdf_color(self) -> impl IntoIterator<Item = f32> {
233-
vec![
232+
pub(crate) fn to_pdf_color(self) -> [f32; 3] {
233+
[
234234
self.0 as f32 / 255.0,
235235
self.1 as f32 / 255.0,
236236
self.2 as f32 / 255.0,

crates/krilla/src/object/shading_function.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
//! Shading functions.
22
33
use std::hash::{Hash, Hasher};
4+
use std::ops::DerefMut;
45
use std::sync::Arc;
56

67
use bumpalo::Bump;
78
use pdf_writer::types::{FunctionShadingType, PostScriptOp};
8-
use pdf_writer::{Chunk, Finish, Name, Ref};
9+
use pdf_writer::{Chunk, Dict, Finish, Name, Ref};
910
use tiny_skia_path::{NormalizedF32, Point, Rect, Transform};
1011

11-
use crate::color::luma;
12+
use crate::color::{luma, ColorSpace, DEVICE_CMYK, DEVICE_GRAY, DEVICE_RGB};
1213
use crate::object::color::Color;
1314
use crate::object::{Cacheable, ChunkContainerFn, Resourceable};
1415
use crate::paint::SpreadMethod;
1516
use crate::paint::{LinearGradient, RadialGradient, SweepGradient};
1617
use crate::resource;
1718
use crate::resource::Resource;
18-
use crate::serialize::SerializeContext;
19-
use crate::util::{RectExt, RectWrapper};
19+
use crate::serialize::{MaybeDeviceColorSpace, SerializeContext};
20+
use crate::util::{NameExt, RectExt, RectWrapper};
2021
use crate::validation::ValidationError;
2122

2223
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)]
@@ -271,18 +272,14 @@ fn serialize_postscript_shading(
271272
let cs = if use_opacities {
272273
luma::Color::color_space(sc.serialize_settings().no_device_cs)
273274
} else {
274-
// Note: This means for example if the user provides a linear RGB stop as the first
275-
// and sRGB as the remaining ones, the whole gradient will
276-
// use linear RGB.
277275
post_script_gradient.stops[0].color.color_space(sc)
278276
};
279277

280278
let mut shading = chunk.function_shading(root_ref);
281279
shading.shading_type(FunctionShadingType::Function);
282280

283-
shading
284-
.insert(Name(b"ColorSpace"))
285-
.primitive(sc.register_colorspace(cs).get_ref());
281+
set_colorspace(sc, cs, shading.deref_mut());
282+
286283
// Write the identity matrix, because ghostscript has a bug where
287284
// it thinks the entry is mandatory.
288285
shading.matrix([1.0, 0.0, 0.0, 1.0, 0.0, 0.0]);
@@ -293,6 +290,17 @@ fn serialize_postscript_shading(
293290
shading.finish();
294291
}
295292

293+
fn set_colorspace(sc: &mut SerializeContext, cs: ColorSpace, target: &mut Dict) {
294+
let pdf_cs = target.insert(Name(b"ColorSpace"));
295+
296+
match sc.register_colorspace(cs) {
297+
MaybeDeviceColorSpace::DeviceGray => pdf_cs.primitive(DEVICE_GRAY.to_pdf_name()),
298+
MaybeDeviceColorSpace::DeviceRgb => pdf_cs.primitive(DEVICE_RGB.to_pdf_name()),
299+
MaybeDeviceColorSpace::DeviceCMYK => pdf_cs.primitive(DEVICE_CMYK.to_pdf_name()),
300+
MaybeDeviceColorSpace::ColorSpace(cs) => pdf_cs.primitive(cs.get_ref()),
301+
}
302+
}
303+
296304
fn serialize_axial_radial_shading(
297305
sc: &mut SerializeContext,
298306
chunk: &mut Chunk,
@@ -317,9 +325,8 @@ fn serialize_axial_radial_shading(
317325
} else {
318326
shading.shading_type(FunctionShadingType::Axial);
319327
}
320-
shading
321-
.insert(Name(b"ColorSpace"))
322-
.primitive(sc.register_colorspace(cs).get_ref());
328+
329+
set_colorspace(sc, cs, shading.deref_mut());
323330

324331
shading.anti_alias(radial_axial_gradient.anti_alias);
325332
shading.function(function_ref);

crates/krilla/src/object/xobject.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ use pdf_writer::{Chunk, Finish, Name, Ref};
44
use std::ops::DerefMut;
55
use tiny_skia_path::Rect;
66

7-
use crate::color::rgb;
7+
use crate::color::{rgb, DEVICE_RGB};
88
use crate::object::{Cacheable, ChunkContainerFn, Resourceable};
99
use crate::resource;
1010
use crate::resource::Resource;
11-
use crate::serialize::SerializeContext;
11+
use crate::serialize::{MaybeDeviceColorSpace, SerializeContext};
1212
use crate::stream::{FilterStreamBuilder, Stream};
13-
use crate::util::{RectExt, RectWrapper};
13+
use crate::util::{NameExt, RectExt, RectWrapper};
1414
use crate::validation::ValidationError;
1515

1616
#[derive(Debug, Hash, Eq, PartialEq)]
@@ -87,7 +87,14 @@ impl Cacheable for XObject {
8787

8888
if self.transparency_group_color_space {
8989
let cs = rgb::Color::rgb_color_space(sc.serialize_settings().no_device_cs);
90-
transparency.pair(Name(b"CS"), sc.register_colorspace(cs).get_ref());
90+
let pdf_cs = transparency.insert(Name(b"CS"));
91+
92+
match sc.register_colorspace(cs) {
93+
MaybeDeviceColorSpace::DeviceRgb => pdf_cs.primitive(DEVICE_RGB.to_pdf_name()),
94+
// Can only be SRGB
95+
MaybeDeviceColorSpace::ColorSpace(cs) => pdf_cs.primitive(cs.get_ref()),
96+
_ => unreachable!(),
97+
}
9198
}
9299

93100
transparency.finish();

crates/krilla/src/serialize.rs

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,19 @@ use skrifa::raw::TableProvider;
1515
use tiny_skia_path::Size;
1616

1717
use crate::chunk_container::ChunkContainer;
18-
use crate::color::{ColorSpace, ICCBasedColorSpace, ICCProfile, DEVICE_CMYK};
18+
use crate::color::{ColorSpace, ICCBasedColorSpace, ICCProfile};
1919
use crate::destination::{NamedDestination, XyzDestination};
2020
use crate::error::{KrillaError, KrillaResult};
2121
use crate::font::{Font, FontInfo};
2222
#[cfg(feature = "raster-images")]
2323
use crate::image::Image;
2424
use crate::metadata::Metadata;
25-
use crate::object::color::{DEVICE_GRAY, DEVICE_RGB};
2625
use crate::object::font::cid_font::CIDFont;
2726
use crate::object::font::type3_font::Type3FontMapper;
2827
use crate::object::font::{FontContainer, FontIdentifier};
2928
use crate::object::outline::Outline;
3029
use crate::object::page::{InternalPage, PageLabelContainer};
31-
use crate::object::{Cacheable, ChunkContainerFn, Resourceable};
30+
use crate::object::{Cacheable, Resourceable};
3231
use crate::page::PageLabel;
3332
use crate::resource;
3433
use crate::resource::Resource;
@@ -165,6 +164,13 @@ enum StructParentElement {
165164
Annotation(usize, usize),
166165
}
167166

167+
pub(crate) enum MaybeDeviceColorSpace {
168+
DeviceRgb,
169+
DeviceGray,
170+
DeviceCMYK,
171+
ColorSpace(resource::ColorSpace),
172+
}
173+
168174
/// The serializer context is more or less the core piece of krilla. It is passed around
169175
/// throughout pretty much the whole conversion process, and contains all mutable state
170176
/// that is needed when writing a PDF file. This includes for example:
@@ -513,47 +519,20 @@ impl SerializeContext {
513519
}
514520
}
515521

516-
pub(crate) fn register_colorspace(&mut self, cs: ColorSpace) -> resource::ColorSpace {
517-
macro_rules! cs {
518-
($name:ident, $color_name:expr) => {
519-
#[derive(Copy, Clone, Hash)]
520-
struct $name;
521-
522-
impl Cacheable for $name {
523-
fn chunk_container(&self) -> ChunkContainerFn {
524-
Box::new(|cc| &mut cc.color_spaces)
525-
}
526-
527-
fn serialize(self, _: &mut SerializeContext, root_ref: Ref) -> Chunk {
528-
let mut chunk = Chunk::new();
529-
chunk
530-
.indirect(root_ref)
531-
.primitive(Name($color_name.as_bytes()));
532-
chunk
533-
}
534-
}
535-
536-
impl Resourceable for $name {
537-
type Resource = resource::ColorSpace;
538-
}
539-
};
540-
}
541-
542-
cs!(DeviceRgb, DEVICE_RGB);
543-
cs!(DeviceGray, DEVICE_GRAY);
544-
cs!(DeviceCmyk, DEVICE_CMYK);
545-
522+
pub(crate) fn register_colorspace(&mut self, cs: ColorSpace) -> MaybeDeviceColorSpace {
546523
match cs {
547-
ColorSpace::Srgb => self.register_resourceable(ICCBasedColorSpace(
548-
self.serialize_settings.pdf_version.rgb_icc(),
524+
ColorSpace::Srgb => MaybeDeviceColorSpace::ColorSpace(self.register_resourceable(
525+
ICCBasedColorSpace(self.serialize_settings.pdf_version.rgb_icc()),
549526
)),
550-
ColorSpace::Luma => self.register_resourceable(ICCBasedColorSpace(
551-
self.serialize_settings.pdf_version.grey_icc(),
527+
ColorSpace::Luma => MaybeDeviceColorSpace::ColorSpace(self.register_resourceable(
528+
ICCBasedColorSpace(self.serialize_settings.pdf_version.grey_icc()),
552529
)),
553-
ColorSpace::Cmyk(cs) => self.register_resourceable(cs),
554-
ColorSpace::DeviceGray => self.register_resourceable(DeviceGray),
555-
ColorSpace::DeviceRgb => self.register_resourceable(DeviceRgb),
556-
ColorSpace::DeviceCmyk => self.register_resourceable(DeviceCmyk),
530+
ColorSpace::Cmyk(cs) => {
531+
MaybeDeviceColorSpace::ColorSpace(self.register_resourceable(cs))
532+
}
533+
ColorSpace::DeviceGray => MaybeDeviceColorSpace::DeviceGray,
534+
ColorSpace::DeviceRgb => MaybeDeviceColorSpace::DeviceRgb,
535+
ColorSpace::DeviceCmyk => MaybeDeviceColorSpace::DeviceCMYK,
557536
}
558537
}
559538
}

0 commit comments

Comments
 (0)