Skip to main content

pdf_interpret/
shading.rs

1//! PDF shadings.
2
3#![allow(clippy::needless_range_loop)]
4
5use crate::CacheKey;
6use crate::cache::Cache;
7use crate::color::{ColorComponents, ColorSpace};
8use crate::function::{Function, Values, interpolate};
9use crate::util::{Float32Ext, PointExt, RectExt};
10use kurbo::{Affine, BezPath, CubicBez, ParamCurve, Point, Shape};
11use log::warn;
12use pdf_syntax::bit_reader::BitReader;
13use pdf_syntax::object::Array;
14use pdf_syntax::object::Dict;
15use pdf_syntax::object::Object;
16use pdf_syntax::object::Rect;
17use pdf_syntax::object::Stream;
18use pdf_syntax::object::dict::keys::{
19    BACKGROUND, BBOX, BITS_PER_COMPONENT, BITS_PER_COORDINATE, BITS_PER_FLAG, COLORSPACE, COORDS,
20    DECODE, DOMAIN, EXTEND, FUNCTION, MATRIX, SHADING_TYPE, VERTICES_PER_ROW,
21};
22use smallvec::{SmallVec, smallvec};
23use std::sync::Arc;
24
25/// The function supplied to a shading.
26#[derive(Debug, Clone)]
27pub enum ShadingFunction {
28    /// A single function, which should be used to evaluate all components of the shading.
29    Single(Function),
30    /// Multiple functions, one for each color component.
31    Multiple(SmallVec<[Function; 4]>),
32}
33
34impl ShadingFunction {
35    /// Evaluate the shading function.
36    pub fn eval(&self, input: &Values) -> Option<Values> {
37        match self {
38            Self::Single(s) => s.eval(input.clone()),
39            Self::Multiple(m) => {
40                // 1-in, 1-out function for each color component.
41
42                let mut out = smallvec![];
43
44                for func in m {
45                    out.push(*func.eval(input.clone())?.first()?);
46                }
47
48                Some(out)
49            }
50        }
51    }
52}
53
54/// A type of shading.
55#[derive(Debug)]
56pub enum ShadingType {
57    /// A function-based shading.
58    FunctionBased {
59        /// The domain of the function.
60        domain: [f32; 4],
61        /// A transform to apply to the shading.
62        matrix: Affine,
63        /// The function that should be used to evaluate the shading.
64        function: ShadingFunction,
65    },
66    /// A radial-axial shading.
67    RadialAxial {
68        /// The coordinates of the shading.
69        ///
70        /// For axial shadings, only the first 4 entries are relevant, representing the x/y coordinates
71        /// of the first point and the coordinates for the second point.
72        ///
73        /// For radial shadings, the coordinates contain the x/y coordinates as well as the radius
74        /// for both circles.
75        coords: [f32; 6],
76        /// The domain of the shading.
77        domain: [f32; 2],
78        /// The function forming the basis of the shading.
79        function: ShadingFunction,
80        /// The extends in the left/right direction of the shading.
81        extend: [bool; 2],
82        /// Whether the shading is axial or radial.
83        axial: bool,
84    },
85    /// A triangle-mesh shading.
86    TriangleMesh {
87        /// The triangles making up the shading.
88        triangles: Vec<Triangle>,
89        /// An optional function used for calculating the sampled color values.
90        function: Option<ShadingFunction>,
91    },
92    /// A coons-patch-mesh shading.
93    CoonsPatchMesh {
94        /// The patches that make up the shading.
95        patches: Vec<CoonsPatch>,
96        /// An optional function used for calculating the sampled color values.
97        function: Option<ShadingFunction>,
98    },
99    /// A tensor-product-patch-mesh shading.
100    TensorProductPatchMesh {
101        /// The patches that make up the shading.
102        patches: Vec<TensorProductPatch>,
103        /// An optional function used for calculating the sampled color values.
104        function: Option<ShadingFunction>,
105    },
106    /// A dummy shading that should just be drawn transparent.
107    Dummy,
108}
109
110/// A PDF shading.
111#[derive(Clone, Debug)]
112pub struct Shading {
113    cache_key: u128,
114    /// The type of shading.
115    pub shading_type: Arc<ShadingType>,
116    /// The color space of the shading.
117    pub color_space: ColorSpace,
118    /// A clip path that should be applied to the shading.
119    pub clip_path: Option<BezPath>,
120    /// The background color of the shading.
121    pub background: Option<SmallVec<[f32; 4]>>,
122}
123
124impl Shading {
125    pub(crate) fn new(dict: &Dict<'_>, stream: Option<&Stream<'_>>, cache: &Cache) -> Option<Self> {
126        let cache_key = dict.cache_key();
127
128        let shading_num = dict.get::<u8>(SHADING_TYPE)?;
129
130        let color_space = ColorSpace::new(dict.get(COLORSPACE)?, cache)?;
131
132        let shading_type = match shading_num {
133            1 => {
134                let domain = dict.get::<[f32; 4]>(DOMAIN).unwrap_or([0.0, 1.0, 0.0, 1.0]);
135                let matrix = dict
136                    .get::<[f64; 6]>(MATRIX)
137                    .map(Affine::new)
138                    .unwrap_or_default();
139                let function = read_function(dict, &color_space)?;
140
141                ShadingType::FunctionBased {
142                    domain,
143                    matrix,
144                    function,
145                }
146            }
147            2 | 3 => {
148                let domain = dict.get::<[f32; 2]>(DOMAIN).unwrap_or([0.0, 1.0]);
149                let function = read_function(dict, &color_space)?;
150                let extend = dict.get::<[bool; 2]>(EXTEND).unwrap_or([false, false]);
151                let (coords, invalid) = if shading_num == 2 {
152                    let read = dict.get::<[f32; 4]>(COORDS)?;
153                    let invalid = (read[0] - read[2]).is_nearly_zero()
154                        && (read[1] - read[3]).is_nearly_zero();
155                    ([read[0], read[1], read[2], read[3], 0.0, 0.0], invalid)
156                } else {
157                    let read = dict.get::<[f32; 6]>(COORDS)?;
158                    let invalid = (read[0] - read[3]).is_nearly_zero()
159                        && (read[1] - read[4]).is_nearly_zero()
160                        && (read[2] - read[5]).is_nearly_zero();
161                    (read, invalid)
162                };
163
164                let axial = shading_num == 2;
165
166                if invalid {
167                    ShadingType::Dummy
168                } else {
169                    ShadingType::RadialAxial {
170                        domain,
171                        function,
172                        extend,
173                        coords,
174                        axial,
175                    }
176                }
177            }
178            4 => {
179                let stream = stream?;
180                let stream_data = stream.decoded().ok()?;
181                let bp_coord = dict.get::<u8>(BITS_PER_COORDINATE)?;
182                let bp_comp = dict.get::<u8>(BITS_PER_COMPONENT)?;
183                let bpf = dict.get::<u8>(BITS_PER_FLAG)?;
184                let function = read_function(dict, &color_space);
185                let decode = dict
186                    .get::<Array<'_>>(DECODE)?
187                    .iter::<f32>()
188                    .collect::<Vec<_>>();
189
190                let triangles = read_free_form_triangles(
191                    stream_data.as_ref(),
192                    bpf,
193                    bp_coord,
194                    bp_comp,
195                    function.is_some(),
196                    &decode,
197                )?;
198
199                ShadingType::TriangleMesh {
200                    triangles,
201                    function,
202                }
203            }
204            5 => {
205                let stream = stream?;
206                let stream_data = stream.decoded().ok()?;
207                let bp_coord = dict.get::<u8>(BITS_PER_COORDINATE)?;
208                let bp_comp = dict.get::<u8>(BITS_PER_COMPONENT)?;
209                let function = read_function(dict, &color_space);
210                let decode = dict
211                    .get::<Array<'_>>(DECODE)?
212                    .iter::<f32>()
213                    .collect::<Vec<_>>();
214                let vertices_per_row = dict.get::<u32>(VERTICES_PER_ROW)?;
215
216                let triangles = read_lattice_triangles(
217                    stream_data.as_ref(),
218                    bp_coord,
219                    bp_comp,
220                    function.is_some(),
221                    vertices_per_row,
222                    &decode,
223                )?;
224
225                ShadingType::TriangleMesh {
226                    triangles,
227                    function,
228                }
229            }
230            6 => {
231                let stream = stream?;
232                let stream_data = stream.decoded().ok()?;
233                let bp_coord = dict.get::<u8>(BITS_PER_COORDINATE)?;
234                let bp_comp = dict.get::<u8>(BITS_PER_COMPONENT)?;
235                let bpf = dict.get::<u8>(BITS_PER_FLAG)?;
236                let function = read_function(dict, &color_space);
237                let decode = dict
238                    .get::<Array<'_>>(DECODE)?
239                    .iter::<f32>()
240                    .collect::<Vec<_>>();
241
242                let patches = read_coons_patch_mesh(
243                    stream_data.as_ref(),
244                    bpf,
245                    bp_coord,
246                    bp_comp,
247                    function.is_some(),
248                    &decode,
249                )?;
250
251                ShadingType::CoonsPatchMesh { patches, function }
252            }
253            7 => {
254                let stream = stream?;
255                let stream_data = stream.decoded().ok()?;
256                let bp_coord = dict.get::<u8>(BITS_PER_COORDINATE)?;
257                let bp_comp = dict.get::<u8>(BITS_PER_COMPONENT)?;
258                let bpf = dict.get::<u8>(BITS_PER_FLAG)?;
259                let function = read_function(dict, &color_space);
260                let decode = dict
261                    .get::<Array<'_>>(DECODE)?
262                    .iter::<f32>()
263                    .collect::<Vec<_>>();
264
265                let patches = read_tensor_product_patch_mesh(
266                    stream_data.as_ref(),
267                    bpf,
268                    bp_coord,
269                    bp_comp,
270                    function.is_some(),
271                    &decode,
272                )?;
273
274                ShadingType::TensorProductPatchMesh { patches, function }
275            }
276            _ => return None,
277        };
278
279        let bbox = dict.get::<Rect>(BBOX).map(|r| r.to_kurbo());
280        let background = dict
281            .get::<Array<'_>>(BACKGROUND)
282            .map(|a| a.iter::<f32>().collect::<SmallVec<_>>());
283
284        Some(Self {
285            cache_key,
286            shading_type: Arc::new(shading_type),
287            color_space,
288            clip_path: bbox.map(|r| r.to_path(0.1)),
289            background,
290        })
291    }
292}
293
294impl CacheKey for Shading {
295    fn cache_key(&self) -> u128 {
296        self.cache_key
297    }
298}
299
300/// A triangle made up of three vertices.
301#[derive(Clone, Debug)]
302pub struct Triangle {
303    /// The first vertex.
304    pub p0: TriangleVertex,
305    /// The second vertex.
306    pub p1: TriangleVertex,
307    /// The third vertex.
308    pub p2: TriangleVertex,
309    kurbo_tri: kurbo::Triangle,
310    d00: f64,
311    d01: f64,
312    d11: f64,
313}
314
315impl Triangle {
316    /// Create a new triangle.
317    pub fn new(p0: TriangleVertex, p1: TriangleVertex, p2: TriangleVertex) -> Self {
318        let v0 = p1.point - p0.point;
319        let v1 = p2.point - p0.point;
320
321        let d00 = v0.dot(v0);
322        let d01 = v0.dot(v1);
323        let d11 = v1.dot(v1);
324
325        let kurbo_tri = kurbo::Triangle::new(p0.point, p1.point, p2.point);
326
327        Self {
328            p0,
329            p1,
330            kurbo_tri,
331            p2,
332            d00,
333            d01,
334            d11,
335        }
336    }
337
338    /// Get the interpolated colors of the point from the triangle.
339    ///
340    /// Returns `None` if the point is not inside of the triangle.
341    pub fn interpolate(&self, pos: Point) -> ColorComponents {
342        let (u, v, w) = self.barycentric_coords(pos);
343
344        let mut result = smallvec![];
345
346        for i in 0..self.p0.colors.len() {
347            let c0 = self.p0.colors[i];
348            let c1 = self.p1.colors[i];
349            let c2 = self.p2.colors[i];
350            result.push(u * c0 + v * c1 + w * c2);
351        }
352
353        result
354    }
355
356    /// Return whether the point is contained within the triangle.
357    pub fn contains_point(&self, pos: Point) -> bool {
358        self.kurbo_tri.winding(pos) != 0
359    }
360
361    /// Return the bounding box of the triangle.
362    pub fn bounding_box(&self) -> kurbo::Rect {
363        self.kurbo_tri.bounding_box()
364    }
365
366    fn barycentric_coords(&self, p: Point) -> (f32, f32, f32) {
367        let (a, b, c) = (self.p0.point, self.p1.point, self.p2.point);
368        let v0 = b - a;
369        let v1 = c - a;
370        let v2 = p - a;
371
372        let d00 = self.d00;
373        let d01 = self.d01;
374        let d11 = self.d11;
375        let d20 = v2.dot(v0);
376        let d21 = v2.dot(v1);
377
378        let denom = d00 * d11 - d01 * d01;
379        let v = (d11 * d20 - d01 * d21) / denom;
380        let w = (d00 * d21 - d01 * d20) / denom;
381        let u = (1.0 - v - w) as f32;
382
383        (u, v as f32, w as f32)
384    }
385}
386
387/// A triangle vertex.
388#[derive(Clone, Debug)]
389pub struct TriangleVertex {
390    flag: u32,
391    /// The position of the vertex.
392    pub point: Point,
393    /// The color component of the vertex.
394    pub colors: ColorComponents,
395}
396
397impl TriangleVertex {
398    /// Create a new triangle vertex (crate-internal constructor for tests).
399    /// Used by `encode.rs::tests` only; allow dead_code under `-D warnings`
400    /// so non-test lib builds still compile.
401    #[allow(dead_code)]
402    pub(crate) fn new(flag: u32, point: Point, colors: ColorComponents) -> Self {
403        Self {
404            flag,
405            point,
406            colors,
407        }
408    }
409}
410
411/// A coons patch.
412#[derive(Clone, Debug)]
413pub struct CoonsPatch {
414    /// The control points of the coons patch.
415    pub control_points: [Point; 12],
416    /// The colors at each corner of the coons patch.
417    pub colors: [ColorComponents; 4],
418}
419
420/// A tensor-product patch.
421#[derive(Clone, Debug)]
422pub struct TensorProductPatch {
423    /// The control points of the tensor-product patch (4x4 grid = 16 points).
424    pub control_points: [Point; 16],
425    /// The colors at each corner of the tensor-product patch.
426    pub colors: [ColorComponents; 4],
427}
428
429impl CoonsPatch {
430    /// Map the point to the coordinates of the coons patch.
431    pub fn map_coordinate(&self, p: Point) -> Point {
432        let (u, v) = (p.x, p.y);
433
434        let cp = &self.control_points;
435
436        let c1 = CubicBez::new(cp[0], cp[11], cp[10], cp[9]);
437        let c2 = CubicBez::new(cp[3], cp[4], cp[5], cp[6]);
438        let d1 = CubicBez::new(cp[0], cp[1], cp[2], cp[3]);
439        let d2 = CubicBez::new(cp[9], cp[8], cp[7], cp[6]);
440
441        let sc = (1.0 - v) * c1.eval(u).to_vec2() + v * c2.eval(u).to_vec2();
442        let sd = (1.0 - u) * d1.eval(v).to_vec2() + u * d2.eval(v).to_vec2();
443        let sb = (1.0 - v) * ((1.0 - u) * c1.eval(0.0).to_vec2() + u * c1.eval(1.0).to_vec2())
444            + v * ((1.0 - u) * c2.eval(0.0).to_vec2() + u * c2.eval(1.0).to_vec2());
445
446        (sc + sd - sb).to_point()
447    }
448
449    /// Approximate the patch by triangles.
450    pub fn to_triangles(&self, buffer: &mut Vec<Triangle>) {
451        generate_patch_triangles(|p| self.map_coordinate(p), |p| self.interpolate(p), buffer);
452    }
453
454    /// Get the interpolated colors of the point from the patch.
455    pub fn interpolate(&self, pos: Point) -> ColorComponents {
456        let (u, v) = (pos.x, pos.y);
457        let (c0, c1, c2, c3) = {
458            (
459                &self.colors[0],
460                &self.colors[1],
461                &self.colors[2],
462                &self.colors[3],
463            )
464        };
465
466        let mut result = SmallVec::new();
467        for i in 0..c0.len() {
468            let val = (1.0 - u) * (1.0 - v) * c0[i] as f64
469                + u * (1.0 - v) * c3[i] as f64
470                + u * v * c2[i] as f64
471                + (1.0 - u) * v * c1[i] as f64;
472            result.push(val as f32);
473        }
474
475        result
476    }
477}
478
479impl TensorProductPatch {
480    /// Evaluate Bernstein polynomial `B_i(t)` for tensor-product patches.
481    fn bernstein(i: usize, t: f64) -> f64 {
482        match i {
483            0 => (1.0 - t).powi(3),
484            1 => 3.0 * t * (1.0 - t).powi(2),
485            2 => 3.0 * t.powi(2) * (1.0 - t),
486            3 => t.powi(3),
487            _ => 0.0,
488        }
489    }
490
491    /// Map the point to the coordinates of the tensor product patch.
492    pub fn map_coordinate(&self, p: Point) -> Point {
493        let (u, v) = (p.x, p.y);
494
495        let mut x = 0.0;
496        let mut y = 0.0;
497
498        fn idx(i: usize, j: usize) -> usize {
499            match (i, j) {
500                (0, 0) => 0,
501                (0, 1) => 1,
502                (0, 2) => 2,
503                (0, 3) => 3,
504                (1, 0) => 11,
505                (1, 1) => 12,
506                (1, 2) => 13,
507                (1, 3) => 4,
508                (2, 0) => 10,
509                (2, 1) => 15,
510                (2, 2) => 14,
511                (2, 3) => 5,
512                (3, 0) => 9,
513                (3, 1) => 8,
514                (3, 2) => 7,
515                (3, 3) => 6,
516                _ => panic!("Invalid index"),
517            }
518        }
519
520        for i in 0..4 {
521            for j in 0..4 {
522                let control_point_idx = idx(i, j);
523                let basis = Self::bernstein(i, u) * Self::bernstein(j, v);
524
525                x += self.control_points[control_point_idx].x * basis;
526                y += self.control_points[control_point_idx].y * basis;
527            }
528        }
529
530        Point::new(x, y)
531    }
532
533    /// Approximate the tensor product patch mesh by triangles.
534    pub fn to_triangles(&self, buffer: &mut Vec<Triangle>) {
535        generate_patch_triangles(|p| self.map_coordinate(p), |p| self.interpolate(p), buffer);
536    }
537
538    /// Get the interpolated colors of the point from the patch.
539    pub fn interpolate(&self, pos: Point) -> ColorComponents {
540        let (u, v) = (pos.x, pos.y);
541        let (c0, c1, c2, c3) = {
542            (
543                &self.colors[0],
544                &self.colors[1],
545                &self.colors[2],
546                &self.colors[3],
547            )
548        };
549
550        let mut result = SmallVec::new();
551        for i in 0..c0.len() {
552            let val = (1.0 - u) * (1.0 - v) * c0[i] as f64
553                + u * (1.0 - v) * c3[i] as f64
554                + u * v * c2[i] as f64
555                + (1.0 - u) * v * c1[i] as f64;
556            result.push(val as f32);
557        }
558
559        result
560    }
561}
562
563fn read_free_form_triangles(
564    data: &[u8],
565    bpf: u8,
566    bp_cord: u8,
567    bp_comp: u8,
568    has_function: bool,
569    decode: &[f32],
570) -> Option<Vec<Triangle>> {
571    let mut triangles = vec![];
572
573    let ([x_min, x_max, y_min, y_max], decode) = split_decode(decode)?;
574    let mut reader = BitReader::new(data);
575    let helpers = InterpolationHelpers::new(bp_cord, bp_comp, x_min, x_max, y_min, y_max);
576
577    let read_single = |reader: &mut BitReader<'_>| -> Option<TriangleVertex> {
578        helpers.read_triangle_vertex(reader, bpf, has_function, decode)
579    };
580
581    let mut a = None;
582    let mut b = None;
583    let mut c = None;
584
585    loop {
586        let Some(first) = read_single(&mut reader) else {
587            break;
588        };
589
590        if first.flag == 0 {
591            let second = read_single(&mut reader)?;
592            let third = read_single(&mut reader)?;
593
594            a = Some(first.clone());
595            b = Some(second.clone());
596            c = Some(third.clone());
597        } else if first.flag == 1 {
598            a = Some(b.clone()?);
599            b = Some(c.clone()?);
600            c = Some(first);
601        } else if first.flag == 2 {
602            b = Some(c.clone()?);
603            c = Some(first);
604        }
605
606        let (p0, p1, p2) = (a.clone()?, b.clone()?, c.clone()?);
607
608        if p0.point.nearly_same(p1.point) || p1.point.nearly_same(p2.point) {
609            continue;
610        }
611
612        triangles.push(Triangle::new(a.clone()?, b.clone()?, c.clone()?));
613    }
614
615    Some(triangles)
616}
617
618/// Common interpolation functions used across different shading types.
619struct InterpolationHelpers {
620    bp_coord: u8,
621    bp_comp: u8,
622    coord_max: f32,
623    comp_max: f32,
624    x_min: f32,
625    x_max: f32,
626    y_min: f32,
627    y_max: f32,
628}
629
630impl InterpolationHelpers {
631    fn new(bp_coord: u8, bp_comp: u8, x_min: f32, x_max: f32, y_min: f32, y_max: f32) -> Self {
632        let coord_max = 2.0_f32.powi(bp_coord as i32) - 1.0;
633        let comp_max = 2.0_f32.powi(bp_comp as i32) - 1.0;
634        Self {
635            bp_coord,
636            bp_comp,
637            coord_max,
638            comp_max,
639            x_min,
640            x_max,
641            y_min,
642            y_max,
643        }
644    }
645
646    fn interpolate_coord(&self, n: u32, d_min: f32, d_max: f32) -> f32 {
647        interpolate(n as f32, 0.0, self.coord_max, d_min, d_max)
648    }
649
650    fn interpolate_comp(&self, n: u32, d_min: f32, d_max: f32) -> f32 {
651        interpolate(n as f32, 0.0, self.comp_max, d_min, d_max)
652    }
653
654    fn read_point(&self, reader: &mut BitReader<'_>) -> Option<Point> {
655        let x = self.interpolate_coord(reader.read(self.bp_coord)?, self.x_min, self.x_max);
656        let y = self.interpolate_coord(reader.read(self.bp_coord)?, self.y_min, self.y_max);
657        Some(Point::new(x as f64, y as f64))
658    }
659
660    fn read_colors(
661        &self,
662        reader: &mut BitReader<'_>,
663        has_function: bool,
664        decode: &[f32],
665    ) -> Option<ColorComponents> {
666        let mut colors = smallvec![];
667        if has_function {
668            colors.push(self.interpolate_comp(
669                reader.read(self.bp_comp)?,
670                *decode.first()?,
671                *decode.get(1)?,
672            ));
673        } else {
674            let num_components = decode.len() / 2;
675            for (_, decode) in (0..num_components).zip(decode.chunks_exact(2)) {
676                colors.push(self.interpolate_comp(
677                    reader.read(self.bp_comp)?,
678                    decode[0],
679                    decode[1],
680                ));
681            }
682        }
683        Some(colors)
684    }
685
686    fn read_triangle_vertex(
687        &self,
688        reader: &mut BitReader<'_>,
689        bpf: u8,
690        has_function: bool,
691        decode: &[f32],
692    ) -> Option<TriangleVertex> {
693        let flag = reader.read(bpf)?;
694        let point = self.read_point(reader)?;
695        let colors = self.read_colors(reader, has_function, decode)?;
696        reader.align();
697
698        Some(TriangleVertex {
699            flag,
700            point,
701            colors,
702        })
703    }
704}
705
706/// Split decode array into coordinate bounds and component decode values.
707fn split_decode(decode: &[f32]) -> Option<([f32; 4], &[f32])> {
708    decode.split_first_chunk::<4>().map(|(a, b)| (*a, b))
709}
710
711/// Generate triangles from a grid of points using a mapping function.
712fn generate_patch_triangles<F, I>(map_coordinate: F, interpolate: I, buffer: &mut Vec<Triangle>)
713where
714    F: Fn(Point) -> Point,
715    I: Fn(Point) -> ColorComponents,
716{
717    const GRID_SIZE: usize = 20;
718    let mut grid = vec![vec![Point::ZERO; GRID_SIZE]; GRID_SIZE];
719
720    // Create grid by mapping unit square coordinates.
721    for i in 0..GRID_SIZE {
722        for j in 0..GRID_SIZE {
723            let u = i as f64 / (GRID_SIZE - 1) as f64; // 0.0 to 1.0 (left to right).
724            let v = j as f64 / (GRID_SIZE - 1) as f64; // 0.0 to 1.0 (top to bottom).
725
726            // Map unit square coordinate to patch coordinate.
727            let unit_point = Point::new(u, v);
728            grid[i][j] = map_coordinate(unit_point);
729        }
730    }
731
732    for i in 0..(GRID_SIZE - 1) {
733        for j in 0..(GRID_SIZE - 1) {
734            let p00 = grid[i][j];
735            let p10 = grid[i + 1][j];
736            let p01 = grid[i][j + 1];
737            let p11 = grid[i + 1][j + 1];
738
739            // Calculate unit square coordinates for color interpolation.
740            let u0 = i as f64 / (GRID_SIZE - 1) as f64;
741            let u1 = (i + 1) as f64 / (GRID_SIZE - 1) as f64;
742            let v0 = j as f64 / (GRID_SIZE - 1) as f64;
743            let v1 = (j + 1) as f64 / (GRID_SIZE - 1) as f64;
744
745            // Create triangle vertices with interpolated colors.
746            let v00 = TriangleVertex {
747                flag: 0,
748                point: p00,
749                colors: interpolate(Point::new(u0, v0)),
750            };
751            let v10 = TriangleVertex {
752                flag: 0,
753                point: p10,
754                colors: interpolate(Point::new(u1, v0)),
755            };
756            let v01 = TriangleVertex {
757                flag: 0,
758                point: p01,
759                colors: interpolate(Point::new(u0, v1)),
760            };
761            let v11 = TriangleVertex {
762                flag: 0,
763                point: p11,
764                colors: interpolate(Point::new(u1, v1)),
765            };
766
767            let inflate_point = |p: Point, mid: Point| -> Point {
768                const INFLATION_FACTOR: f64 = 1.025;
769                mid + (p - mid) * INFLATION_FACTOR
770            };
771
772            // Converting to triangles is lossy and can lead to gaps. To make this a bit better,
773            // we slightly inflate the triangles.
774            let inflate = |mut triangle: Triangle| {
775                let mid = triangle.kurbo_tri.centroid();
776                triangle.p0.point = inflate_point(triangle.p0.point, mid);
777                triangle.p1.point = inflate_point(triangle.p1.point, mid);
778                triangle.p2.point = inflate_point(triangle.p2.point, mid);
779
780                triangle
781            };
782
783            buffer.push(inflate(Triangle::new(
784                v00.clone(),
785                v10.clone(),
786                v01.clone(),
787            )));
788            buffer.push(inflate(Triangle::new(
789                v10.clone(),
790                v11.clone(),
791                v01.clone(),
792            )));
793        }
794    }
795}
796
797fn read_lattice_triangles(
798    data: &[u8],
799    bp_cord: u8,
800    bp_comp: u8,
801    has_function: bool,
802    vertices_per_row: u32,
803    decode: &[f32],
804) -> Option<Vec<Triangle>> {
805    let mut lattices = vec![];
806
807    let ([x_min, x_max, y_min, y_max], decode) = split_decode(decode)?;
808    let mut reader = BitReader::new(data);
809    let helpers = InterpolationHelpers::new(bp_cord, bp_comp, x_min, x_max, y_min, y_max);
810
811    let read_single = |reader: &mut BitReader<'_>| -> Option<TriangleVertex> {
812        let point = helpers.read_point(reader)?;
813        let colors = helpers.read_colors(reader, has_function, decode)?;
814        reader.align();
815
816        Some(TriangleVertex {
817            flag: 0,
818            point,
819            colors,
820        })
821    };
822
823    'outer: loop {
824        let mut single_row = vec![];
825
826        for _ in 0..vertices_per_row {
827            let Some(next) = read_single(&mut reader) else {
828                break 'outer;
829            };
830
831            single_row.push(next);
832        }
833
834        lattices.push(single_row);
835    }
836
837    let mut triangles = vec![];
838
839    for i in 0..lattices.len().saturating_sub(1) {
840        for j in 0..(vertices_per_row as usize).saturating_sub(1) {
841            triangles.push(Triangle::new(
842                lattices[i][j].clone(),
843                lattices[i + 1][j].clone(),
844                lattices[i][j + 1].clone(),
845            ));
846
847            triangles.push(Triangle::new(
848                lattices[i + 1][j + 1].clone(),
849                lattices[i + 1][j].clone(),
850                lattices[i][j + 1].clone(),
851            ));
852        }
853    }
854
855    Some(triangles)
856}
857
858fn read_coons_patch_mesh(
859    data: &[u8],
860    bpf: u8,
861    bp_coord: u8,
862    bp_comp: u8,
863    has_function: bool,
864    decode: &[f32],
865) -> Option<Vec<CoonsPatch>> {
866    read_patch_mesh(
867        data,
868        bpf,
869        bp_coord,
870        bp_comp,
871        has_function,
872        decode,
873        12,
874        |control_points, colors| {
875            let mut coons_points = [Point::ZERO; 12];
876            coons_points.copy_from_slice(&control_points[0..12]);
877            CoonsPatch {
878                control_points: coons_points,
879                colors,
880            }
881        },
882    )
883}
884
885/// Generic patch mesh reading function that works for both Coons and Tensor Product patches.
886#[allow(clippy::too_many_arguments)]
887fn read_patch_mesh<P, F>(
888    data: &[u8],
889    bpf: u8,
890    bp_coord: u8,
891    bp_comp: u8,
892    has_function: bool,
893    decode: &[f32],
894    control_points_count: usize,
895    create_patch: F,
896) -> Option<Vec<P>>
897where
898    F: Fn([Point; 16], [ColorComponents; 4]) -> P,
899{
900    let ([x_min, x_max, y_min, y_max], decode) = split_decode(decode)?;
901    let mut reader = BitReader::new(data);
902    let helpers = InterpolationHelpers::new(bp_coord, bp_comp, x_min, x_max, y_min, y_max);
903
904    let read_colors = |reader: &mut BitReader<'_>| -> Option<ColorComponents> {
905        helpers.read_colors(reader, has_function, decode)
906    };
907
908    let mut prev_patch_points: Option<Vec<Point>> = None;
909    let mut prev_patch_colors: Option<[ColorComponents; 4]> = None;
910    let mut patches = vec![];
911
912    while let Some(flag) = reader.read(bpf) {
913        let mut control_points = vec![Point::ZERO; 16]; // Always allocate 16, use subset as needed.
914        let mut colors = [smallvec![], smallvec![], smallvec![], smallvec![]];
915
916        match flag {
917            0 => {
918                for i in 0..control_points_count {
919                    control_points[i] = helpers.read_point(&mut reader)?;
920                }
921
922                for i in 0..4 {
923                    colors[i] = read_colors(&mut reader)?;
924                }
925
926                prev_patch_points = Some(control_points.clone());
927                prev_patch_colors = Some(colors.clone());
928            }
929            1..=3 => {
930                let prev_points = prev_patch_points.as_ref()?;
931                let prev_colors = prev_patch_colors.as_ref()?;
932
933                copy_patch_control_points(flag, prev_points, &mut control_points);
934
935                match flag {
936                    1 => {
937                        colors[0] = prev_colors[1].clone();
938                        colors[1] = prev_colors[2].clone();
939                    }
940                    2 => {
941                        colors[0] = prev_colors[2].clone();
942                        colors[1] = prev_colors[3].clone();
943                    }
944                    3 => {
945                        colors[0] = prev_colors[3].clone();
946                        colors[1] = prev_colors[0].clone();
947                    }
948                    _ => unreachable!(),
949                }
950
951                for i in 4..control_points_count {
952                    control_points[i] = helpers.read_point(&mut reader)?;
953                }
954
955                colors[2] = read_colors(&mut reader)?;
956                colors[3] = read_colors(&mut reader)?;
957
958                prev_patch_points = Some(control_points.clone());
959                prev_patch_colors = Some(colors.clone());
960            }
961            _ => break,
962        }
963
964        let mut fixed_points = [Point::ZERO; 16];
965        for i in 0..16 {
966            if i < control_points.len() {
967                fixed_points[i] = control_points[i];
968            }
969        }
970
971        patches.push(create_patch(fixed_points, colors));
972    }
973    Some(patches)
974}
975
976fn copy_patch_control_points(
977    flag: u32,
978    prev_control_points: &[Point],
979    control_points: &mut [Point],
980) {
981    match flag {
982        1 => {
983            control_points[0] = prev_control_points[3];
984            control_points[1] = prev_control_points[4];
985            control_points[2] = prev_control_points[5];
986            control_points[3] = prev_control_points[6];
987        }
988        2 => {
989            control_points[0] = prev_control_points[6];
990            control_points[1] = prev_control_points[7];
991            control_points[2] = prev_control_points[8];
992            control_points[3] = prev_control_points[9];
993        }
994        3 => {
995            control_points[0] = prev_control_points[9];
996            control_points[1] = prev_control_points[10];
997            control_points[2] = prev_control_points[11];
998            control_points[3] = prev_control_points[0];
999        }
1000        _ => {}
1001    }
1002}
1003
1004fn read_tensor_product_patch_mesh(
1005    data: &[u8],
1006    bpf: u8,
1007    bp_coord: u8,
1008    bp_comp: u8,
1009    has_function: bool,
1010    decode: &[f32],
1011) -> Option<Vec<TensorProductPatch>> {
1012    read_patch_mesh(
1013        data,
1014        bpf,
1015        bp_coord,
1016        bp_comp,
1017        has_function,
1018        decode,
1019        16,
1020        |control_points, colors| TensorProductPatch {
1021            control_points,
1022            colors,
1023        },
1024    )
1025}
1026
1027fn read_function(dict: &Dict<'_>, color_space: &ColorSpace) -> Option<ShadingFunction> {
1028    if let Some(arr) = dict.get::<Array<'_>>(FUNCTION) {
1029        let arr: Option<SmallVec<_>> = arr
1030            .iter::<Object<'_>>()
1031            .map(|o| Function::new(&o))
1032            .collect();
1033        let arr = arr?;
1034
1035        if arr.len() != color_space.num_components() as usize {
1036            warn!("function array of shading has wrong size");
1037
1038            return None;
1039        }
1040
1041        Some(ShadingFunction::Multiple(arr))
1042    } else if let Some(obj) = dict.get::<Object<'_>>(FUNCTION) {
1043        Some(ShadingFunction::Single(Function::new(&obj)?))
1044    } else {
1045        None
1046    }
1047}