Skip to main content

pdf_interpret/
shading.rs

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