1use justpdf_core::color::{Color, ColorSpace};
2use tiny_skia::Mask;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum SoftMaskSubtype {
7 Luminosity,
8 Alpha,
9}
10
11#[derive(Clone)]
14pub struct SoftMask {
15 pub mask: Mask,
16 pub subtype: SoftMaskSubtype,
17}
18
19impl std::fmt::Debug for SoftMask {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 f.debug_struct("SoftMask")
22 .field("subtype", &self.subtype)
23 .field("mask_size", &"<Mask>")
24 .finish()
25 }
26}
27
28#[derive(Debug, Clone, Copy)]
31pub struct Matrix {
32 pub a: f64,
33 pub b: f64,
34 pub c: f64,
35 pub d: f64,
36 pub e: f64,
37 pub f: f64,
38}
39
40impl Matrix {
41 pub fn identity() -> Self {
42 Self {
43 a: 1.0,
44 b: 0.0,
45 c: 0.0,
46 d: 1.0,
47 e: 0.0,
48 f: 0.0,
49 }
50 }
51
52 pub fn concat(&self, other: &Matrix) -> Matrix {
54 Matrix {
55 a: self.a * other.a + self.b * other.c,
56 b: self.a * other.b + self.b * other.d,
57 c: self.c * other.a + self.d * other.c,
58 d: self.c * other.b + self.d * other.d,
59 e: self.e * other.a + self.f * other.c + other.e,
60 f: self.e * other.b + self.f * other.d + other.f,
61 }
62 }
63
64 pub fn transform_point(&self, x: f64, y: f64) -> (f64, f64) {
65 (
66 self.a * x + self.c * y + self.e,
67 self.b * x + self.d * y + self.f,
68 )
69 }
70
71 pub fn translate(tx: f64, ty: f64) -> Self {
72 Self {
73 a: 1.0,
74 b: 0.0,
75 c: 0.0,
76 d: 1.0,
77 e: tx,
78 f: ty,
79 }
80 }
81
82 pub fn scale(sx: f64, sy: f64) -> Self {
83 Self {
84 a: sx,
85 b: 0.0,
86 c: 0.0,
87 d: sy,
88 e: 0.0,
89 f: 0.0,
90 }
91 }
92
93 pub fn to_skia(&self) -> tiny_skia::Transform {
95 tiny_skia::Transform::from_row(
96 self.a as f32,
97 self.b as f32,
98 self.c as f32,
99 self.d as f32,
100 self.e as f32,
101 self.f as f32,
102 )
103 }
104
105 pub fn font_size_scale(&self) -> f64 {
107 (self.b * self.b + self.d * self.d).sqrt()
108 }
109}
110
111#[derive(Debug, Clone)]
113pub struct TextState {
114 pub char_spacing: f64,
115 pub word_spacing: f64,
116 pub horiz_scaling: f64,
117 pub leading: f64,
118 pub font_name: Vec<u8>,
119 pub font_size: f64,
120 pub text_rise: f64,
121 pub render_mode: i64,
122}
123
124impl Default for TextState {
125 fn default() -> Self {
126 Self {
127 char_spacing: 0.0,
128 word_spacing: 0.0,
129 horiz_scaling: 1.0,
130 leading: 0.0,
131 font_name: Vec::new(),
132 font_size: 12.0,
133 text_rise: 0.0,
134 render_mode: 0,
135 }
136 }
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum LineCap {
142 Butt = 0,
143 Round = 1,
144 Square = 2,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum LineJoin {
150 Miter = 0,
151 Round = 1,
152 Bevel = 2,
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum PdfBlendMode {
158 Normal,
159 Multiply,
160 Screen,
161 Overlay,
162 Darken,
163 Lighten,
164 ColorDodge,
165 ColorBurn,
166 HardLight,
167 SoftLight,
168 Difference,
169 Exclusion,
170 Hue,
171 Saturation,
172 Color,
173 Luminosity,
174}
175
176impl PdfBlendMode {
177 pub fn from_name(name: &[u8]) -> Self {
178 match name {
179 b"Multiply" => Self::Multiply,
180 b"Screen" => Self::Screen,
181 b"Overlay" => Self::Overlay,
182 b"Darken" => Self::Darken,
183 b"Lighten" => Self::Lighten,
184 b"ColorDodge" => Self::ColorDodge,
185 b"ColorBurn" => Self::ColorBurn,
186 b"HardLight" => Self::HardLight,
187 b"SoftLight" => Self::SoftLight,
188 b"Difference" => Self::Difference,
189 b"Exclusion" => Self::Exclusion,
190 b"Hue" => Self::Hue,
191 b"Saturation" => Self::Saturation,
192 b"Color" => Self::Color,
193 b"Luminosity" => Self::Luminosity,
194 _ => Self::Normal,
195 }
196 }
197
198 pub fn to_skia(self) -> tiny_skia::BlendMode {
199 match self {
200 Self::Normal => tiny_skia::BlendMode::SourceOver,
201 Self::Multiply => tiny_skia::BlendMode::Multiply,
202 Self::Screen => tiny_skia::BlendMode::Screen,
203 Self::Overlay => tiny_skia::BlendMode::Overlay,
204 Self::Darken => tiny_skia::BlendMode::Darken,
205 Self::Lighten => tiny_skia::BlendMode::Lighten,
206 Self::ColorDodge => tiny_skia::BlendMode::ColorDodge,
207 Self::ColorBurn => tiny_skia::BlendMode::ColorBurn,
208 Self::HardLight => tiny_skia::BlendMode::HardLight,
209 Self::SoftLight => tiny_skia::BlendMode::SoftLight,
210 Self::Difference => tiny_skia::BlendMode::Difference,
211 Self::Exclusion => tiny_skia::BlendMode::Exclusion,
212 Self::Hue => tiny_skia::BlendMode::Hue,
213 Self::Saturation => tiny_skia::BlendMode::Saturation,
214 Self::Color => tiny_skia::BlendMode::Color,
215 Self::Luminosity => tiny_skia::BlendMode::Luminosity,
216 }
217 }
218}
219
220#[derive(Debug, Clone)]
222pub struct GraphicsState {
223 pub ctm: Matrix,
224 pub text: TextState,
225 pub fill_color: Color,
227 pub stroke_color: Color,
228 pub fill_cs: ColorSpace,
229 pub stroke_cs: ColorSpace,
230 pub line_width: f64,
232 pub line_cap: LineCap,
233 pub line_join: LineJoin,
234 pub miter_limit: f64,
235 pub dash_pattern: Vec<f64>,
236 pub dash_phase: f64,
237 pub fill_alpha: f64,
239 pub stroke_alpha: f64,
240 pub blend_mode: PdfBlendMode,
241 pub has_clip: bool,
243 pub soft_mask: Option<SoftMask>,
245 pub fill_pattern_name: Option<Vec<u8>>,
247 pub stroke_pattern_name: Option<Vec<u8>>,
249 pub text_matrix: Matrix,
251 pub text_line_matrix: Matrix,
252}
253
254impl Default for GraphicsState {
255 fn default() -> Self {
256 Self {
257 ctm: Matrix::identity(),
258 text: TextState::default(),
259 fill_color: Color::gray(0.0),
260 stroke_color: Color::gray(0.0),
261 fill_cs: ColorSpace::DeviceGray,
262 stroke_cs: ColorSpace::DeviceGray,
263 line_width: 1.0,
264 line_cap: LineCap::Butt,
265 line_join: LineJoin::Miter,
266 miter_limit: 10.0,
267 dash_pattern: Vec::new(),
268 dash_phase: 0.0,
269 fill_alpha: 1.0,
270 stroke_alpha: 1.0,
271 blend_mode: PdfBlendMode::Normal,
272 has_clip: false,
273 soft_mask: None,
274 fill_pattern_name: None,
275 stroke_pattern_name: None,
276 text_matrix: Matrix::identity(),
277 text_line_matrix: Matrix::identity(),
278 }
279 }
280}
281
282impl GraphicsState {
283 pub fn fill_color_rgba(&self) -> [u8; 4] {
284 let rgb = self.fill_color.to_rgb(&self.fill_cs);
285 [
286 (rgb[0].clamp(0.0, 1.0) * 255.0) as u8,
287 (rgb[1].clamp(0.0, 1.0) * 255.0) as u8,
288 (rgb[2].clamp(0.0, 1.0) * 255.0) as u8,
289 (self.fill_alpha.clamp(0.0, 1.0) * 255.0) as u8,
290 ]
291 }
292
293 pub fn stroke_color_rgba(&self) -> [u8; 4] {
294 let rgb = self.stroke_color.to_rgb(&self.stroke_cs);
295 [
296 (rgb[0].clamp(0.0, 1.0) * 255.0) as u8,
297 (rgb[1].clamp(0.0, 1.0) * 255.0) as u8,
298 (rgb[2].clamp(0.0, 1.0) * 255.0) as u8,
299 (self.stroke_alpha.clamp(0.0, 1.0) * 255.0) as u8,
300 ]
301 }
302}