blitz-pdf 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +266 -12
- package/THIRD_PARTY_NOTICES.md +4 -2
- package/dist/browser.d.ts +1 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/document-options.d.ts +36 -0
- package/dist/document-options.d.ts.map +1 -0
- package/dist/document-options.js +236 -0
- package/dist/document-options.js.map +1 -0
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/options.d.ts +35 -1
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +21 -14
- package/dist/options.js.map +1 -1
- package/dist/types.d.ts +71 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts +2 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +7 -0
- package/dist/validation.js.map +1 -0
- package/examples/zugferd.mjs +41 -0
- package/licenses/RUST_DEPENDENCIES.txt +27 -0
- package/package.json +3 -2
- package/src/browser.ts +1 -1
- package/src/document-options.ts +218 -0
- package/src/node.ts +1 -1
- package/src/options.ts +18 -13
- package/src/types.ts +76 -1
- package/src/validation.ts +6 -0
- package/vendor/README.md +27 -2
- package/vendor/krilla/.cargo-ok +1 -0
- package/vendor/krilla/.cargo_vcs_info.json +6 -0
- package/vendor/krilla/Cargo.lock +1357 -0
- package/vendor/krilla/Cargo.toml +165 -0
- package/vendor/krilla/LICENSE_APACHE +202 -0
- package/vendor/krilla/LICENSE_MIT +21 -0
- package/vendor/krilla/README.md +156 -0
- package/vendor/krilla/examples/basic.rs +117 -0
- package/vendor/krilla/examples/empty_document.rs +25 -0
- package/vendor/krilla/examples/parley.rs +174 -0
- package/vendor/krilla/examples/simple_text.rs +118 -0
- package/vendor/krilla/examples/stream_builder.rs +93 -0
- package/vendor/krilla/icc/LICENSE +116 -0
- package/vendor/krilla/icc/README.md +1 -0
- package/vendor/krilla/icc/sGrey-v2-magic.icc +0 -0
- package/vendor/krilla/icc/sGrey-v4.icc +0 -0
- package/vendor/krilla/icc/sRGB-v2-magic.icc +0 -0
- package/vendor/krilla/icc/sRGB-v4.icc +0 -0
- package/vendor/krilla/src/chunk_container.rs +503 -0
- package/vendor/krilla/src/configure/PDF_A1.md +227 -0
- package/vendor/krilla/src/configure/PDF_A2.md +269 -0
- package/vendor/krilla/src/configure/PDF_A3.md +31 -0
- package/vendor/krilla/src/configure/PDF_A4.md +211 -0
- package/vendor/krilla/src/configure/PDF_UA1.md +202 -0
- package/vendor/krilla/src/configure/README.md +23 -0
- package/vendor/krilla/src/configure/mod.rs +197 -0
- package/vendor/krilla/src/configure/validate.rs +1244 -0
- package/vendor/krilla/src/configure/version.rs +134 -0
- package/vendor/krilla/src/content.rs +1352 -0
- package/vendor/krilla/src/data.rs +49 -0
- package/vendor/krilla/src/document.rs +156 -0
- package/vendor/krilla/src/error.rs +170 -0
- package/vendor/krilla/src/geom.rs +341 -0
- package/vendor/krilla/src/graphics/blend.rs +46 -0
- package/vendor/krilla/src/graphics/color.rs +552 -0
- package/vendor/krilla/src/graphics/graphic.rs +30 -0
- package/vendor/krilla/src/graphics/graphics_state.rs +251 -0
- package/vendor/krilla/src/graphics/icc.rs +218 -0
- package/vendor/krilla/src/graphics/image.rs +835 -0
- package/vendor/krilla/src/graphics/mask.rs +128 -0
- package/vendor/krilla/src/graphics/mod.rs +14 -0
- package/vendor/krilla/src/graphics/paint.rs +434 -0
- package/vendor/krilla/src/graphics/separation.rs +86 -0
- package/vendor/krilla/src/graphics/shading_function.rs +865 -0
- package/vendor/krilla/src/graphics/shading_pattern.rs +60 -0
- package/vendor/krilla/src/graphics/tiling_pattern.rs +119 -0
- package/vendor/krilla/src/graphics/xobject.rs +150 -0
- package/vendor/krilla/src/interactive/action.rs +68 -0
- package/vendor/krilla/src/interactive/annotation.rs +283 -0
- package/vendor/krilla/src/interactive/destination.rs +289 -0
- package/vendor/krilla/src/interactive/mod.rs +3 -0
- package/vendor/krilla/src/interchange/embed.rs +240 -0
- package/vendor/krilla/src/interchange/metadata.rs +564 -0
- package/vendor/krilla/src/interchange/mod.rs +4 -0
- package/vendor/krilla/src/interchange/outline.rs +263 -0
- package/vendor/krilla/src/interchange/tagging/fmt.rs +733 -0
- package/vendor/krilla/src/interchange/tagging/generate.toml +474 -0
- package/vendor/krilla/src/interchange/tagging/generated.rs +4754 -0
- package/vendor/krilla/src/interchange/tagging/mod.rs +1324 -0
- package/vendor/krilla/src/interchange/tagging/tag.rs +686 -0
- package/vendor/krilla/src/lib.rs +179 -0
- package/vendor/krilla/src/num.rs +26 -0
- package/vendor/krilla/src/page.rs +612 -0
- package/vendor/krilla/src/pdf.rs +313 -0
- package/vendor/krilla/src/resource.rs +436 -0
- package/vendor/krilla/src/serialize.rs +1101 -0
- package/vendor/krilla/src/stream.rs +368 -0
- package/vendor/krilla/src/surface.rs +664 -0
- package/vendor/krilla/src/text/cid.rs +488 -0
- package/vendor/krilla/src/text/font.rs +351 -0
- package/vendor/krilla/src/text/glyph/bitmap.rs +81 -0
- package/vendor/krilla/src/text/glyph/colr.rs +581 -0
- package/vendor/krilla/src/text/glyph/mod.rs +205 -0
- package/vendor/krilla/src/text/glyph/outline.rs +72 -0
- package/vendor/krilla/src/text/glyph/svg.rs +34 -0
- package/vendor/krilla/src/text/group.rs +463 -0
- package/vendor/krilla/src/text/mod.rs +232 -0
- package/vendor/krilla/src/text/shape.rs +102 -0
- package/vendor/krilla/src/text/type3.rs +514 -0
- package/vendor/krilla/src/util.rs +286 -0
- package/vendor/stylo/properties/longhands.toml +0 -1
- package/vendor/stylo/stylesheets/rule_parser.rs +1 -1
- package/wasm/blitz_pdf.js +10 -5
- package/wasm/blitz_pdf_bg.wasm +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
//! Alpha and luminosity masks.
|
|
2
|
+
|
|
3
|
+
use pdf_writer::{Finish, Name, Ref};
|
|
4
|
+
|
|
5
|
+
use crate::chunk_container::ChunkContainer;
|
|
6
|
+
use crate::geom::{Rect, Transform};
|
|
7
|
+
use crate::graphics::shading_function::{GradientProperties, ShadingFunction};
|
|
8
|
+
use crate::graphics::xobject::XObject;
|
|
9
|
+
use crate::resource;
|
|
10
|
+
use crate::resource::Resourceable;
|
|
11
|
+
use crate::serialize::{Cacheable, SerializeContext};
|
|
12
|
+
use crate::stream::Stream;
|
|
13
|
+
use crate::stream::StreamBuilder;
|
|
14
|
+
|
|
15
|
+
/// A mask. Can be a luminance mask or an alpha mask.
|
|
16
|
+
///
|
|
17
|
+
/// IMPORTANT: Note that you must only use a mask in the document that you created it with!
|
|
18
|
+
/// If you use it in a different document, you will end up with an invalid PDF file.
|
|
19
|
+
// TODO: Maybe we can enfore the above somehow?
|
|
20
|
+
#[derive(PartialEq, Eq, Debug, Hash)]
|
|
21
|
+
pub struct Mask {
|
|
22
|
+
/// The stream of the mask.
|
|
23
|
+
stream: Stream,
|
|
24
|
+
/// The type of the mask.
|
|
25
|
+
mask_type: MaskType,
|
|
26
|
+
/// A custom bbox of the mask. The only reason we need this is that for gradients with
|
|
27
|
+
/// transparencies, we create a custom mask where we call the shading operator. In this case,
|
|
28
|
+
/// we want to manually set the bbox of the underlying XObject to match the shape that the
|
|
29
|
+
/// gradient is being applied to.
|
|
30
|
+
custom_bbox: Option<Rect>,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl Mask {
|
|
34
|
+
/// Create a new mask. `stream` contains the content description
|
|
35
|
+
/// of the mask, and `mask_type` indicates the type of mask.
|
|
36
|
+
pub fn new(stream: Stream, mask_type: MaskType) -> Self {
|
|
37
|
+
Self {
|
|
38
|
+
stream,
|
|
39
|
+
mask_type,
|
|
40
|
+
custom_bbox: None,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Create a new mask for a shading to encode the opacity channels.
|
|
45
|
+
pub(crate) fn new_from_shading(
|
|
46
|
+
gradient_properties: GradientProperties,
|
|
47
|
+
shading_transform: Transform,
|
|
48
|
+
bbox: Rect,
|
|
49
|
+
serializer_context: &mut SerializeContext,
|
|
50
|
+
chunk_container: &mut ChunkContainer,
|
|
51
|
+
) -> Option<Self> {
|
|
52
|
+
match &gradient_properties {
|
|
53
|
+
GradientProperties::RadialAxialGradient(rag) => {
|
|
54
|
+
if rag.stops.iter().all(|s| s.opacity.get() == 1.0) {
|
|
55
|
+
return None;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
GradientProperties::PostScriptGradient(psg) => {
|
|
59
|
+
if psg.stops.iter().all(|s| s.opacity.get() == 1.0) {
|
|
60
|
+
return None;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let shading_function = ShadingFunction::new(gradient_properties, true);
|
|
66
|
+
|
|
67
|
+
let shading_stream = {
|
|
68
|
+
let mut builder = StreamBuilder::new(serializer_context, chunk_container);
|
|
69
|
+
let mut surface = builder.surface();
|
|
70
|
+
surface.push_transform(&shading_transform);
|
|
71
|
+
surface.draw_shading(&shading_function);
|
|
72
|
+
surface.pop();
|
|
73
|
+
surface.finish();
|
|
74
|
+
builder.finish()
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
Some(Self {
|
|
78
|
+
stream: shading_stream,
|
|
79
|
+
mask_type: MaskType::Luminosity,
|
|
80
|
+
custom_bbox: Some(bbox),
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// A mask type.
|
|
86
|
+
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
|
87
|
+
pub enum MaskType {
|
|
88
|
+
/// A luminosity mask.
|
|
89
|
+
Luminosity,
|
|
90
|
+
/// An alpha mask.
|
|
91
|
+
Alpha,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
impl MaskType {
|
|
95
|
+
/// Return the PDF name of the mask type.
|
|
96
|
+
pub(crate) fn to_name(self) -> Name<'static> {
|
|
97
|
+
match self {
|
|
98
|
+
MaskType::Alpha => Name(b"Alpha"),
|
|
99
|
+
MaskType::Luminosity => Name(b"Luminosity"),
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
impl Cacheable for Mask {
|
|
105
|
+
fn serialize(
|
|
106
|
+
self,
|
|
107
|
+
sc: &mut SerializeContext,
|
|
108
|
+
chunk_container: &mut ChunkContainer,
|
|
109
|
+
root_ref: Ref,
|
|
110
|
+
) {
|
|
111
|
+
let x_object = sc.register_cacheable(
|
|
112
|
+
chunk_container,
|
|
113
|
+
XObject::new(self.stream, false, true, self.custom_bbox),
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
let chunk = &mut chunk_container.non_stream.masks;
|
|
117
|
+
let mut dict = chunk.indirect(root_ref).dict();
|
|
118
|
+
dict.pair(Name(b"Type"), Name(b"Mask"));
|
|
119
|
+
dict.pair(Name(b"S"), self.mask_type.to_name());
|
|
120
|
+
dict.pair(Name(b"G"), x_object);
|
|
121
|
+
|
|
122
|
+
dict.finish();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
impl Resourceable for Mask {
|
|
127
|
+
type Resource = resource::XObject;
|
|
128
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
pub mod blend;
|
|
2
|
+
pub mod color;
|
|
3
|
+
pub mod graphic;
|
|
4
|
+
pub(crate) mod graphics_state;
|
|
5
|
+
pub mod icc;
|
|
6
|
+
#[cfg(feature = "raster-images")]
|
|
7
|
+
pub mod image;
|
|
8
|
+
pub mod mask;
|
|
9
|
+
pub mod paint;
|
|
10
|
+
pub(crate) mod separation;
|
|
11
|
+
pub(crate) mod shading_function;
|
|
12
|
+
pub(crate) mod shading_pattern;
|
|
13
|
+
pub(crate) mod tiling_pattern;
|
|
14
|
+
pub(crate) mod xobject;
|
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
//! Paints that can be used for filling and stroking text or paths.
|
|
2
|
+
|
|
3
|
+
use std::hash::{Hash, Hasher};
|
|
4
|
+
use std::sync::Arc;
|
|
5
|
+
|
|
6
|
+
use pdf_writer::types::{LineCapStyle, LineJoinStyle};
|
|
7
|
+
|
|
8
|
+
use crate::color::SpecialColor;
|
|
9
|
+
use crate::geom::Transform;
|
|
10
|
+
use crate::graphics::color::{cmyk, luma, rgb, Color};
|
|
11
|
+
use crate::num::NormalizedF32;
|
|
12
|
+
use crate::stream::Stream;
|
|
13
|
+
|
|
14
|
+
/// A linear gradient.
|
|
15
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
16
|
+
pub struct LinearGradient {
|
|
17
|
+
/// The x coordinate of the first point.
|
|
18
|
+
pub x1: f32,
|
|
19
|
+
/// The y coordinate of the first point.
|
|
20
|
+
pub y1: f32,
|
|
21
|
+
/// The x coordinate of the second point.
|
|
22
|
+
pub x2: f32,
|
|
23
|
+
/// The y coordinate of the second point.
|
|
24
|
+
pub y2: f32,
|
|
25
|
+
/// A transform that should be applied to the linear gradient.
|
|
26
|
+
pub transform: Transform,
|
|
27
|
+
/// The spread method of the linear gradient.
|
|
28
|
+
pub spread_method: SpreadMethod,
|
|
29
|
+
/// The color stops of the linear gradient.
|
|
30
|
+
///
|
|
31
|
+
/// Note that all stops need to be in the same color space.
|
|
32
|
+
pub stops: Vec<Stop>,
|
|
33
|
+
/// Whether the gradient should be anti-aliased.
|
|
34
|
+
pub anti_alias: bool,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl Eq for LinearGradient {}
|
|
38
|
+
|
|
39
|
+
impl Hash for LinearGradient {
|
|
40
|
+
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
41
|
+
self.x1.to_bits().hash(state);
|
|
42
|
+
self.y1.to_bits().hash(state);
|
|
43
|
+
self.x2.to_bits().hash(state);
|
|
44
|
+
self.y2.to_bits().hash(state);
|
|
45
|
+
self.transform.hash(state);
|
|
46
|
+
self.spread_method.hash(state);
|
|
47
|
+
self.stops.hash(state);
|
|
48
|
+
self.anti_alias.hash(state);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// A radial gradient.
|
|
53
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
54
|
+
pub struct RadialGradient {
|
|
55
|
+
/// The x coordinate of the start circle.
|
|
56
|
+
pub fx: f32,
|
|
57
|
+
/// The y coordinate of the start circle.
|
|
58
|
+
pub fy: f32,
|
|
59
|
+
/// The radius of the start circle.
|
|
60
|
+
pub fr: f32,
|
|
61
|
+
/// The x coordinate of the end circle.
|
|
62
|
+
pub cx: f32,
|
|
63
|
+
/// The y coordinate of the end circle.
|
|
64
|
+
pub cy: f32,
|
|
65
|
+
/// The radius of the end circle.
|
|
66
|
+
pub cr: f32,
|
|
67
|
+
/// A transform that should be applied to the radial gradient.
|
|
68
|
+
pub transform: Transform,
|
|
69
|
+
/// The spread method of the radial gradient.
|
|
70
|
+
///
|
|
71
|
+
/// _Note_: The spread methods `Repeat`/`Reflect` are currently not supported
|
|
72
|
+
/// for radial gradients, and will fall back to `Pad`.
|
|
73
|
+
pub spread_method: SpreadMethod,
|
|
74
|
+
/// The color stops of the radial gradient.
|
|
75
|
+
///
|
|
76
|
+
/// Note that all stops need to be in the same color space.
|
|
77
|
+
pub stops: Vec<Stop>,
|
|
78
|
+
/// Whether the gradient should be anti-aliased.
|
|
79
|
+
pub anti_alias: bool,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
impl Eq for RadialGradient {}
|
|
83
|
+
|
|
84
|
+
impl Hash for RadialGradient {
|
|
85
|
+
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
86
|
+
self.fx.to_bits().hash(state);
|
|
87
|
+
self.fy.to_bits().hash(state);
|
|
88
|
+
self.fr.to_bits().hash(state);
|
|
89
|
+
self.cx.to_bits().hash(state);
|
|
90
|
+
self.cy.to_bits().hash(state);
|
|
91
|
+
self.cr.to_bits().hash(state);
|
|
92
|
+
self.transform.hash(state);
|
|
93
|
+
self.spread_method.hash(state);
|
|
94
|
+
self.stops.hash(state);
|
|
95
|
+
self.anti_alias.hash(state);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// A sweep gradient.
|
|
100
|
+
///
|
|
101
|
+
/// Angles start from the right and go counter-clockwise with increasing values.
|
|
102
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
103
|
+
pub struct SweepGradient {
|
|
104
|
+
/// The x coordinate of the center.
|
|
105
|
+
pub cx: f32,
|
|
106
|
+
/// The y coordinate of the center.
|
|
107
|
+
pub cy: f32,
|
|
108
|
+
/// The start angle.
|
|
109
|
+
pub start_angle: f32,
|
|
110
|
+
/// The end angle.
|
|
111
|
+
pub end_angle: f32,
|
|
112
|
+
/// A transform that should be applied to the sweep gradient.
|
|
113
|
+
pub transform: Transform,
|
|
114
|
+
/// The spread method of the sweep gradient.
|
|
115
|
+
pub spread_method: SpreadMethod,
|
|
116
|
+
/// The color stops of the sweep gradient.
|
|
117
|
+
///
|
|
118
|
+
/// Note that all stops need to be in the same color space.
|
|
119
|
+
pub stops: Vec<Stop>,
|
|
120
|
+
/// Whether the gradient should be anti-aliased.
|
|
121
|
+
pub anti_alias: bool,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
impl Eq for SweepGradient {}
|
|
125
|
+
|
|
126
|
+
impl Hash for SweepGradient {
|
|
127
|
+
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
128
|
+
self.cx.to_bits().hash(state);
|
|
129
|
+
self.cy.to_bits().hash(state);
|
|
130
|
+
self.start_angle.to_bits().hash(state);
|
|
131
|
+
self.end_angle.to_bits().hash(state);
|
|
132
|
+
self.transform.hash(state);
|
|
133
|
+
self.spread_method.hash(state);
|
|
134
|
+
self.stops.hash(state);
|
|
135
|
+
self.anti_alias.hash(state);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/// A pattern.
|
|
140
|
+
///
|
|
141
|
+
/// IMPORTANT: Note that you must only use a mask in the document that you created it with!
|
|
142
|
+
/// If you use it in a different document, you will end up with an invalid PDF file.
|
|
143
|
+
#[derive(Debug, PartialEq, Clone)]
|
|
144
|
+
pub struct Pattern {
|
|
145
|
+
/// The stream of the pattern.
|
|
146
|
+
pub stream: Stream,
|
|
147
|
+
/// A transform that should be applied to the pattern.
|
|
148
|
+
pub transform: Transform,
|
|
149
|
+
/// The width of the pattern.
|
|
150
|
+
pub width: f32,
|
|
151
|
+
/// The height of the pattern.
|
|
152
|
+
pub height: f32,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
impl Eq for Pattern {}
|
|
156
|
+
|
|
157
|
+
impl Hash for Pattern {
|
|
158
|
+
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
159
|
+
self.stream.hash(state);
|
|
160
|
+
self.transform.hash(state);
|
|
161
|
+
self.width.to_bits().hash(state);
|
|
162
|
+
self.height.to_bits().hash(state);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
|
167
|
+
pub(crate) enum InnerPaint {
|
|
168
|
+
Color(Color),
|
|
169
|
+
LinearGradient(LinearGradient),
|
|
170
|
+
RadialGradient(RadialGradient),
|
|
171
|
+
SweepGradient(SweepGradient),
|
|
172
|
+
Pattern(Arc<Pattern>),
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// A paint.
|
|
176
|
+
///
|
|
177
|
+
/// You cannot construct this type directly, but instead can convert
|
|
178
|
+
/// into it by calling `into` on the various types of paint, such as linear
|
|
179
|
+
/// gradients and patterns.
|
|
180
|
+
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
|
181
|
+
pub struct Paint(pub(crate) InnerPaint);
|
|
182
|
+
|
|
183
|
+
impl Paint {
|
|
184
|
+
pub(crate) fn as_rgb(&self) -> Option<rgb::Color> {
|
|
185
|
+
match &self.0 {
|
|
186
|
+
InnerPaint::Color(c) => match c {
|
|
187
|
+
Color::Regular(c) => c.as_rgb(),
|
|
188
|
+
Color::Special(SpecialColor::Separation(c)) => c.space.fallback.as_rgb(),
|
|
189
|
+
},
|
|
190
|
+
_ => None,
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
impl From<rgb::Color> for Paint {
|
|
196
|
+
fn from(value: rgb::Color) -> Self {
|
|
197
|
+
Paint(InnerPaint::Color(value.into()))
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
impl From<luma::Color> for Paint {
|
|
202
|
+
fn from(value: luma::Color) -> Self {
|
|
203
|
+
Paint(InnerPaint::Color(value.into()))
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
impl From<cmyk::Color> for Paint {
|
|
208
|
+
fn from(value: cmyk::Color) -> Self {
|
|
209
|
+
Paint(InnerPaint::Color(value.into()))
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
impl From<Color> for Paint {
|
|
214
|
+
fn from(value: Color) -> Self {
|
|
215
|
+
Paint(InnerPaint::Color(value))
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
impl From<LinearGradient> for Paint {
|
|
220
|
+
fn from(value: LinearGradient) -> Self {
|
|
221
|
+
Paint(InnerPaint::LinearGradient(value))
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
impl From<RadialGradient> for Paint {
|
|
226
|
+
fn from(value: RadialGradient) -> Self {
|
|
227
|
+
Paint(InnerPaint::RadialGradient(value))
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
impl From<SweepGradient> for Paint {
|
|
232
|
+
fn from(value: SweepGradient) -> Self {
|
|
233
|
+
Paint(InnerPaint::SweepGradient(value))
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
impl From<Pattern> for Paint {
|
|
238
|
+
fn from(value: Pattern) -> Self {
|
|
239
|
+
Paint(InnerPaint::Pattern(Arc::new(value)))
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/// A spread method.
|
|
244
|
+
#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone, Default)]
|
|
245
|
+
pub enum SpreadMethod {
|
|
246
|
+
/// The pad spread method.
|
|
247
|
+
#[default]
|
|
248
|
+
Pad,
|
|
249
|
+
/// The reflect spread method.
|
|
250
|
+
Reflect,
|
|
251
|
+
/// The repeat spread method.
|
|
252
|
+
Repeat,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/// A color stop in a gradient.
|
|
256
|
+
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
|
257
|
+
#[allow(private_bounds)]
|
|
258
|
+
pub struct Stop {
|
|
259
|
+
/// The normalized offset of the stop.
|
|
260
|
+
pub offset: NormalizedF32,
|
|
261
|
+
/// The color of the stop.
|
|
262
|
+
pub color: Color,
|
|
263
|
+
/// The opacity of the stop.
|
|
264
|
+
pub opacity: NormalizedF32,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/// A line cap.
|
|
268
|
+
#[derive(Eq, PartialEq, Debug, Clone, Copy, Default, Hash)]
|
|
269
|
+
pub enum LineCap {
|
|
270
|
+
/// The butt line cap.
|
|
271
|
+
#[default]
|
|
272
|
+
Butt,
|
|
273
|
+
/// The round line cap.
|
|
274
|
+
Round,
|
|
275
|
+
/// The square line cap.
|
|
276
|
+
Square,
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
impl LineCap {
|
|
280
|
+
pub(crate) fn to_pdf_line_cap(self) -> LineCapStyle {
|
|
281
|
+
match self {
|
|
282
|
+
LineCap::Butt => LineCapStyle::ButtCap,
|
|
283
|
+
LineCap::Round => LineCapStyle::RoundCap,
|
|
284
|
+
LineCap::Square => LineCapStyle::ProjectingSquareCap,
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/// A line join.
|
|
290
|
+
#[derive(PartialEq, Eq, Debug, Clone, Copy, Default, Hash)]
|
|
291
|
+
pub enum LineJoin {
|
|
292
|
+
/// The miter line join.
|
|
293
|
+
#[default]
|
|
294
|
+
Miter,
|
|
295
|
+
/// The round line join.
|
|
296
|
+
Round,
|
|
297
|
+
/// The bevel line join.
|
|
298
|
+
Bevel,
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
impl LineJoin {
|
|
302
|
+
pub(crate) fn to_pdf_line_join(self) -> LineJoinStyle {
|
|
303
|
+
match self {
|
|
304
|
+
LineJoin::Miter => LineJoinStyle::MiterJoin,
|
|
305
|
+
LineJoin::Round => LineJoinStyle::RoundJoin,
|
|
306
|
+
LineJoin::Bevel => LineJoinStyle::BevelJoin,
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/// A stroke dash.
|
|
312
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
313
|
+
pub struct StrokeDash {
|
|
314
|
+
/// The dash array.
|
|
315
|
+
pub array: Vec<f32>,
|
|
316
|
+
/// The offset of the dash.
|
|
317
|
+
pub offset: f32,
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
impl Eq for StrokeDash {}
|
|
321
|
+
|
|
322
|
+
impl Hash for StrokeDash {
|
|
323
|
+
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
324
|
+
for el in &self.array {
|
|
325
|
+
el.to_bits().hash(state);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
self.offset.to_bits().hash(state);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/// A stroke.
|
|
333
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
334
|
+
pub struct Stroke {
|
|
335
|
+
/// The paint of the stroke.
|
|
336
|
+
pub paint: Paint,
|
|
337
|
+
/// The width of the stroke.
|
|
338
|
+
pub width: f32,
|
|
339
|
+
/// The miter limit of the stroke.
|
|
340
|
+
pub miter_limit: f32,
|
|
341
|
+
/// The line cap of the stroke.
|
|
342
|
+
pub line_cap: LineCap,
|
|
343
|
+
/// The line join of the stroke.
|
|
344
|
+
pub line_join: LineJoin,
|
|
345
|
+
/// The opacity of the stroke.
|
|
346
|
+
pub opacity: NormalizedF32,
|
|
347
|
+
/// The (optional) dash of the stroke.
|
|
348
|
+
pub dash: Option<StrokeDash>,
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
impl Eq for Stroke {}
|
|
352
|
+
|
|
353
|
+
impl Hash for Stroke {
|
|
354
|
+
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
355
|
+
self.paint.hash(state);
|
|
356
|
+
self.width.to_bits().hash(state);
|
|
357
|
+
self.miter_limit.to_bits().hash(state);
|
|
358
|
+
self.line_cap.hash(state);
|
|
359
|
+
self.line_join.hash(state);
|
|
360
|
+
self.opacity.hash(state);
|
|
361
|
+
self.dash.hash(state);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
impl Default for Stroke {
|
|
366
|
+
fn default() -> Self {
|
|
367
|
+
Stroke {
|
|
368
|
+
paint: luma::Color::black().into(),
|
|
369
|
+
width: 1.0,
|
|
370
|
+
miter_limit: 10.0,
|
|
371
|
+
line_cap: LineCap::default(),
|
|
372
|
+
line_join: LineJoin::default(),
|
|
373
|
+
opacity: NormalizedF32::ONE,
|
|
374
|
+
dash: None,
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
impl Stroke {
|
|
380
|
+
pub(crate) fn into_tiny_skia(self) -> tiny_skia_path::Stroke {
|
|
381
|
+
let mut stroke = tiny_skia_path::Stroke {
|
|
382
|
+
width: self.width,
|
|
383
|
+
miter_limit: self.miter_limit,
|
|
384
|
+
line_cap: match self.line_cap {
|
|
385
|
+
LineCap::Butt => tiny_skia_path::LineCap::Butt,
|
|
386
|
+
LineCap::Round => tiny_skia_path::LineCap::Round,
|
|
387
|
+
LineCap::Square => tiny_skia_path::LineCap::Square,
|
|
388
|
+
},
|
|
389
|
+
line_join: match self.line_join {
|
|
390
|
+
LineJoin::Miter => tiny_skia_path::LineJoin::Miter,
|
|
391
|
+
LineJoin::Round => tiny_skia_path::LineJoin::Round,
|
|
392
|
+
LineJoin::Bevel => tiny_skia_path::LineJoin::Bevel,
|
|
393
|
+
},
|
|
394
|
+
dash: None,
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
if let Some(stroke_dash) = self.dash {
|
|
398
|
+
stroke.dash = tiny_skia_path::StrokeDash::new(stroke_dash.array, stroke_dash.offset);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
stroke
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/// A fill rule.
|
|
406
|
+
#[derive(Debug, Eq, PartialEq, Clone, Copy, Hash, Default)]
|
|
407
|
+
pub enum FillRule {
|
|
408
|
+
/// The `non-zero` fill rule.
|
|
409
|
+
#[default]
|
|
410
|
+
NonZero,
|
|
411
|
+
/// The `even-odd` fill rule.
|
|
412
|
+
EvenOdd,
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/// A fill.
|
|
416
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
417
|
+
pub struct Fill {
|
|
418
|
+
/// The paint of the fill.
|
|
419
|
+
pub paint: Paint,
|
|
420
|
+
/// The opacity of the fill.
|
|
421
|
+
pub opacity: NormalizedF32,
|
|
422
|
+
/// The fill rule that should be used when applying the fill.
|
|
423
|
+
pub rule: FillRule,
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
impl Default for Fill {
|
|
427
|
+
fn default() -> Self {
|
|
428
|
+
Fill {
|
|
429
|
+
paint: luma::Color::black().into(),
|
|
430
|
+
opacity: NormalizedF32::ONE,
|
|
431
|
+
rule: FillRule::default(),
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
use pdf_writer::writers::ExponentialFunction;
|
|
2
|
+
use pdf_writer::{Finish, Name, Ref, Writer};
|
|
3
|
+
|
|
4
|
+
use crate::chunk_container::ChunkContainer;
|
|
5
|
+
use crate::color::separation::SeparationSpace;
|
|
6
|
+
use crate::color::{DEVICE_CMYK, DEVICE_GRAY, DEVICE_RGB};
|
|
7
|
+
use crate::resource::{self, Resource, Resourceable};
|
|
8
|
+
use crate::serialize::{Cacheable, MaybeDeviceColorSpace, SerializeContext};
|
|
9
|
+
|
|
10
|
+
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
|
|
11
|
+
pub(crate) struct SeparationColorSpace {
|
|
12
|
+
space: SeparationSpace,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
impl SeparationColorSpace {
|
|
16
|
+
pub fn new(space: SeparationSpace) -> Self {
|
|
17
|
+
Self { space }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl Cacheable for SeparationColorSpace {
|
|
22
|
+
fn serialize(
|
|
23
|
+
self,
|
|
24
|
+
sc: &mut SerializeContext,
|
|
25
|
+
chunk_container: &mut ChunkContainer,
|
|
26
|
+
root_ref: Ref,
|
|
27
|
+
) {
|
|
28
|
+
// Delegate fallback color space registration to existing logic
|
|
29
|
+
let fallback_cs = self.space.fallback.color_space(sc);
|
|
30
|
+
let fallback_cs_resource = sc.register_colorspace(chunk_container, fallback_cs.into());
|
|
31
|
+
|
|
32
|
+
// Get fallback color components for tint function
|
|
33
|
+
let fallback_color = crate::color::Color::from(self.space.fallback).to_pdf_color();
|
|
34
|
+
let num_components = fallback_color.len();
|
|
35
|
+
|
|
36
|
+
// Determine C0 based on whether the fallback is subtractive or additive
|
|
37
|
+
// - Additive (RGB, Luma): tint 0.0 = white = [1.0, 1.0, 1.0]
|
|
38
|
+
// - Subtractive (CMYK): tint 0.0 = white = [0.0, 0.0, 0.0, 0.0]
|
|
39
|
+
let c0_value = if self.space.fallback.is_subtractive() {
|
|
40
|
+
0.0
|
|
41
|
+
} else {
|
|
42
|
+
1.0
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if let Err(validation_error) = sc.validation_store().validate_separation(&self.space) {
|
|
46
|
+
sc.register_validation_error(validation_error);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let chunk = &mut chunk_container.non_stream.color_spaces;
|
|
50
|
+
|
|
51
|
+
// Write Separation color space array: [/Separation name alternateSpace tintTransform]
|
|
52
|
+
let mut array = chunk.indirect(root_ref).array();
|
|
53
|
+
array.item(Name(b"Separation"));
|
|
54
|
+
|
|
55
|
+
// Colorant name
|
|
56
|
+
array.item(self.space.colorant.to_pdf());
|
|
57
|
+
|
|
58
|
+
// Fallback color space - write as name for device CS, or ref for others
|
|
59
|
+
match fallback_cs_resource {
|
|
60
|
+
MaybeDeviceColorSpace::DeviceRgb => array.item(Name(DEVICE_RGB.as_bytes())),
|
|
61
|
+
MaybeDeviceColorSpace::DeviceGray => array.item(Name(DEVICE_GRAY.as_bytes())),
|
|
62
|
+
MaybeDeviceColorSpace::DeviceCMYK => array.item(Name(DEVICE_CMYK.as_bytes())),
|
|
63
|
+
MaybeDeviceColorSpace::ColorSpace(cs) => {
|
|
64
|
+
// Use get_ref() to extract the underlying Ref
|
|
65
|
+
array.item(cs.get_ref())
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Write Type 2 (Exponential) function for tint transform
|
|
70
|
+
// Maps tint [0.0-1.0] from white (no ink) to fallback color (full ink)
|
|
71
|
+
ExponentialFunction::start(array.push())
|
|
72
|
+
.domain([0.0, 1.0])
|
|
73
|
+
.range([0.0, 1.0].repeat(num_components))
|
|
74
|
+
// C0: white/no ink (tint = 0.0) - value depends on color space
|
|
75
|
+
.c0(vec![c0_value; num_components])
|
|
76
|
+
// C1: fallback color (tint = 1.0)
|
|
77
|
+
.c1(fallback_color)
|
|
78
|
+
.n(1.0);
|
|
79
|
+
|
|
80
|
+
array.finish();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
impl Resourceable for SeparationColorSpace {
|
|
85
|
+
type Resource = resource::ColorSpace;
|
|
86
|
+
}
|