hayro_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::{FloatExt, PointExt};
10use hayro_syntax::bit_reader::{BitReader, BitSize};
11use hayro_syntax::object::Array;
12use hayro_syntax::object::Dict;
13use hayro_syntax::object::Object;
14use hayro_syntax::object::Rect;
15use hayro_syntax::object::Stream;
16use hayro_syntax::object::dict::keys::{
17    BACKGROUND, BBOX, BITS_PER_COMPONENT, BITS_PER_COORDINATE, BITS_PER_FLAG, COLORSPACE, COORDS,
18    DECODE, DOMAIN, EXTEND, FUNCTION, MATRIX, SHADING_TYPE, VERTICES_PER_ROW,
19};
20use kurbo::{Affine, BezPath, CubicBez, ParamCurve, Point, Shape};
21use log::warn;
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            ShadingFunction::Single(s) => s.eval(input.clone()),
39            ShadingFunction::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.get::<Array>(DECODE)?.iter::<f32>().collect::<Vec<_>>();
186
187                let triangles = read_free_form_triangles(
188                    stream_data.as_ref(),
189                    bpf,
190                    bp_coord,
191                    bp_comp,
192                    function.is_some(),
193                    &decode,
194                )?;
195
196                ShadingType::TriangleMesh {
197                    triangles,
198                    function,
199                }
200            }
201            5 => {
202                let stream = stream?;
203                let stream_data = stream.decoded().ok()?;
204                let bp_coord = dict.get::<u8>(BITS_PER_COORDINATE)?;
205                let bp_comp = dict.get::<u8>(BITS_PER_COMPONENT)?;
206                let function = read_function(dict, &color_space);
207                let decode = dict.get::<Array>(DECODE)?.iter::<f32>().collect::<Vec<_>>();
208                let vertices_per_row = dict.get::<u32>(VERTICES_PER_ROW)?;
209
210                let triangles = read_lattice_triangles(
211                    stream_data.as_ref(),
212                    bp_coord,
213                    bp_comp,
214                    function.is_some(),
215                    vertices_per_row,
216                    &decode,
217                )?;
218
219                ShadingType::TriangleMesh {
220                    triangles,
221                    function,
222                }
223            }
224            6 => {
225                let stream = stream?;
226                let stream_data = stream.decoded().ok()?;
227                let bp_coord = dict.get::<u8>(BITS_PER_COORDINATE)?;
228                let bp_comp = dict.get::<u8>(BITS_PER_COMPONENT)?;
229                let bpf = dict.get::<u8>(BITS_PER_FLAG)?;
230                let function = read_function(dict, &color_space);
231                let decode = dict.get::<Array>(DECODE)?.iter::<f32>().collect::<Vec<_>>();
232
233                let patches = read_coons_patch_mesh(
234                    stream_data.as_ref(),
235                    bpf,
236                    bp_coord,
237                    bp_comp,
238                    function.is_some(),
239                    &decode,
240                )?;
241
242                ShadingType::CoonsPatchMesh { patches, function }
243            }
244            7 => {
245                let stream = stream?;
246                let stream_data = stream.decoded().ok()?;
247                let bp_coord = dict.get::<u8>(BITS_PER_COORDINATE)?;
248                let bp_comp = dict.get::<u8>(BITS_PER_COMPONENT)?;
249                let bpf = dict.get::<u8>(BITS_PER_FLAG)?;
250                let function = read_function(dict, &color_space);
251                let decode = dict.get::<Array>(DECODE)?.iter::<f32>().collect::<Vec<_>>();
252
253                let patches = read_tensor_product_patch_mesh(
254                    stream_data.as_ref(),
255                    bpf,
256                    bp_coord,
257                    bp_comp,
258                    function.is_some(),
259                    &decode,
260                )?;
261
262                ShadingType::TensorProductPatchMesh { patches, function }
263            }
264            _ => return None,
265        };
266
267        let bbox = dict.get::<Rect>(BBOX);
268        let background = dict
269            .get::<Array>(BACKGROUND)
270            .map(|a| a.iter::<f32>().collect::<SmallVec<_>>());
271
272        Some(Self {
273            cache_key,
274            shading_type: Arc::new(shading_type),
275            color_space,
276            clip_path: bbox.map(|r| r.to_path(0.1)),
277            background,
278        })
279    }
280}
281
282impl CacheKey for Shading {
283    fn cache_key(&self) -> u128 {
284        self.cache_key
285    }
286}
287
288/// A triangle made up of three vertices.
289#[derive(Clone, Debug)]
290pub struct Triangle {
291    /// The first vertex.
292    pub p0: TriangleVertex,
293    /// The second vertex.
294    pub p1: TriangleVertex,
295    /// The third vertex.
296    pub p2: TriangleVertex,
297    kurbo_tri: kurbo::Triangle,
298    d00: f64,
299    d01: f64,
300    d11: f64,
301}
302
303impl Triangle {
304    /// Create a new triangle.
305    pub fn new(p0: TriangleVertex, p1: TriangleVertex, p2: TriangleVertex) -> Self {
306        let v0 = p1.point - p0.point;
307        let v1 = p2.point - p0.point;
308
309        let d00 = v0.dot(v0);
310        let d01 = v0.dot(v1);
311        let d11 = v1.dot(v1);
312
313        let kurbo_tri = kurbo::Triangle::new(p0.point, p1.point, p2.point);
314
315        Self {
316            p0,
317            p1,
318            kurbo_tri,
319            p2,
320            d00,
321            d01,
322            d11,
323        }
324    }
325
326    /// Get the interpolated colors of the point from the triangle.
327    ///
328    /// Returns `None` if the point is not inside of the triangle.
329    pub fn interpolate(&self, pos: Point) -> ColorComponents {
330        let (u, v, w) = self.barycentric_coords(pos);
331
332        let mut result = smallvec![];
333
334        for i in 0..self.p0.colors.len() {
335            let c0 = self.p0.colors[i];
336            let c1 = self.p1.colors[i];
337            let c2 = self.p2.colors[i];
338            result.push(u * c0 + v * c1 + w * c2);
339        }
340
341        result
342    }
343
344    /// Return whether the point is contained within the triangle.
345    pub fn contains_point(&self, pos: Point) -> bool {
346        self.kurbo_tri.winding(pos) != 0
347    }
348
349    /// Return the bounding box of the triangle.
350    pub fn bounding_box(&self) -> kurbo::Rect {
351        self.kurbo_tri.bounding_box()
352    }
353
354    fn barycentric_coords(&self, p: Point) -> (f32, f32, f32) {
355        let (a, b, c) = (self.p0.point, self.p1.point, self.p2.point);
356        let v0 = b - a;
357        let v1 = c - a;
358        let v2 = p - a;
359
360        let d00 = self.d00;
361        let d01 = self.d01;
362        let d11 = self.d11;
363        let d20 = v2.dot(v0);
364        let d21 = v2.dot(v1);
365
366        let denom = d00 * d11 - d01 * d01;
367        let v = (d11 * d20 - d01 * d21) / denom;
368        let w = (d00 * d21 - d01 * d20) / denom;
369        let u = (1.0 - v - w) as f32;
370
371        (u, v as f32, w as f32)
372    }
373}
374
375/// A triangle vertex.
376#[derive(Clone, Debug)]
377pub struct TriangleVertex {
378    flag: u32,
379    /// The position of the vertex.
380    pub point: Point,
381    /// The color component of the vertex.
382    pub colors: ColorComponents,
383}
384
385/// A coons patch.
386#[derive(Clone, Debug)]
387pub struct CoonsPatch {
388    /// The control points of the coons patch.
389    pub control_points: [Point; 12],
390    /// The colors at each corner of the coons patch.
391    pub colors: [ColorComponents; 4],
392}
393
394/// A tensor-product patch.
395#[derive(Clone, Debug)]
396pub struct TensorProductPatch {
397    /// The control points of the tensor-product patch (4x4 grid = 16 points).
398    pub control_points: [Point; 16],
399    /// The colors at each corner of the tensor-product patch.
400    pub colors: [ColorComponents; 4],
401}
402
403impl CoonsPatch {
404    /// Map the point to the coordinates of the coons patch.
405    pub fn map_coordinate(&self, p: Point) -> Point {
406        let (u, v) = (p.x, p.y);
407
408        let cp = &self.control_points;
409
410        let c1 = CubicBez::new(cp[0], cp[11], cp[10], cp[9]);
411        let c2 = CubicBez::new(cp[3], cp[4], cp[5], cp[6]);
412        let d1 = CubicBez::new(cp[0], cp[1], cp[2], cp[3]);
413        let d2 = CubicBez::new(cp[9], cp[8], cp[7], cp[6]);
414
415        let sc = (1.0 - v) * c1.eval(u).to_vec2() + v * c2.eval(u).to_vec2();
416        let sd = (1.0 - u) * d1.eval(v).to_vec2() + u * d2.eval(v).to_vec2();
417        let sb = (1.0 - v) * ((1.0 - u) * c1.eval(0.0).to_vec2() + u * c1.eval(1.0).to_vec2())
418            + v * ((1.0 - u) * c2.eval(0.0).to_vec2() + u * c2.eval(1.0).to_vec2());
419
420        (sc + sd - sb).to_point()
421    }
422
423    /// Approximate the patch by triangles.
424    pub fn to_triangles(&self) -> Vec<Triangle> {
425        generate_patch_triangles(|p| self.map_coordinate(p), |p| self.interpolate(p))
426    }
427
428    /// Get the interpolated colors of the point from the patch.
429    pub fn interpolate(&self, pos: Point) -> ColorComponents {
430        let (u, v) = (pos.x, pos.y);
431        let (c0, c1, c2, c3) = {
432            (
433                &self.colors[0],
434                &self.colors[1],
435                &self.colors[2],
436                &self.colors[3],
437            )
438        };
439
440        let mut result = SmallVec::new();
441        for i in 0..c0.len() {
442            let val = (1.0 - u) * (1.0 - v) * c0[i] as f64
443                + u * (1.0 - v) * c3[i] as f64
444                + u * v * c2[i] as f64
445                + (1.0 - u) * v * c1[i] as f64;
446            result.push(val as f32);
447        }
448
449        result
450    }
451}
452
453impl TensorProductPatch {
454    /// Evaluate Bernstein polynomial B_i(t) for tensor-product patches.
455    fn bernstein(i: usize, t: f64) -> f64 {
456        match i {
457            0 => (1.0 - t).powi(3),
458            1 => 3.0 * t * (1.0 - t).powi(2),
459            2 => 3.0 * t.powi(2) * (1.0 - t),
460            3 => t.powi(3),
461            _ => 0.0,
462        }
463    }
464
465    /// Map the point to the coordinates of the tensor product patch.
466    pub fn map_coordinate(&self, p: Point) -> Point {
467        let (u, v) = (p.x, p.y);
468
469        let mut x = 0.0;
470        let mut y = 0.0;
471
472        fn idx(i: usize, j: usize) -> usize {
473            match (i, j) {
474                (0, 0) => 0,
475                (0, 1) => 1,
476                (0, 2) => 2,
477                (0, 3) => 3,
478                (1, 0) => 11,
479                (1, 1) => 12,
480                (1, 2) => 13,
481                (1, 3) => 4,
482                (2, 0) => 10,
483                (2, 1) => 15,
484                (2, 2) => 14,
485                (2, 3) => 5,
486                (3, 0) => 9,
487                (3, 1) => 8,
488                (3, 2) => 7,
489                (3, 3) => 6,
490                _ => panic!("Invalid index"),
491            }
492        }
493
494        for i in 0..4 {
495            for j in 0..4 {
496                let control_point_idx = idx(i, j);
497                let basis = Self::bernstein(i, u) * Self::bernstein(j, v);
498
499                x += self.control_points[control_point_idx].x * basis;
500                y += self.control_points[control_point_idx].y * basis;
501            }
502        }
503
504        Point::new(x, y)
505    }
506
507    /// Approximate the tensor product patch mesh by triangles.
508    pub fn to_triangles(&self) -> Vec<Triangle> {
509        generate_patch_triangles(|p| self.map_coordinate(p), |p| self.interpolate(p))
510    }
511
512    /// Get the interpolated colors of the point from the patch.
513    pub fn interpolate(&self, pos: Point) -> ColorComponents {
514        let (u, v) = (pos.x, pos.y);
515        let (c0, c1, c2, c3) = {
516            (
517                &self.colors[0],
518                &self.colors[1],
519                &self.colors[2],
520                &self.colors[3],
521            )
522        };
523
524        let mut result = SmallVec::new();
525        for i in 0..c0.len() {
526            let val = (1.0 - u) * (1.0 - v) * c0[i] as f64
527                + u * (1.0 - v) * c3[i] as f64
528                + u * v * c2[i] as f64
529                + (1.0 - u) * v * c1[i] as f64;
530            result.push(val as f32);
531        }
532
533        result
534    }
535}
536
537fn read_free_form_triangles(
538    data: &[u8],
539    bpf: u8,
540    bp_cord: u8,
541    bp_comp: u8,
542    has_function: bool,
543    decode: &[f32],
544) -> Option<Vec<Triangle>> {
545    let bpf = BitSize::from_u8(bpf)?;
546    let bp_cord = BitSize::from_u8(bp_cord)?;
547    let bp_comp = BitSize::from_u8(bp_comp)?;
548
549    let mut triangles = vec![];
550
551    let ([x_min, x_max, y_min, y_max], decode) = split_decode(decode)?;
552    let mut reader = BitReader::new(data);
553    let helpers = InterpolationHelpers::new(bp_cord, bp_comp, x_min, x_max, y_min, y_max);
554
555    let read_single = |reader: &mut BitReader| -> Option<TriangleVertex> {
556        helpers.read_triangle_vertex(reader, bpf, has_function, decode)
557    };
558
559    let mut a = None;
560    let mut b = None;
561    let mut c = None;
562
563    loop {
564        let Some(first) = read_single(&mut reader) else {
565            break;
566        };
567
568        if first.flag == 0 {
569            let second = read_single(&mut reader)?;
570            let third = read_single(&mut reader)?;
571
572            a = Some(first.clone());
573            b = Some(second.clone());
574            c = Some(third.clone());
575        } else if first.flag == 1 {
576            a = Some(b.clone()?);
577            b = Some(c.clone()?);
578            c = Some(first);
579        } else if first.flag == 2 {
580            b = Some(c.clone()?);
581            c = Some(first);
582        }
583
584        let (p0, p1, p2) = (a.clone()?, b.clone()?, c.clone()?);
585
586        if p0.point.nearly_same(p1.point) || p1.point.nearly_same(p2.point) {
587            continue;
588        }
589
590        triangles.push(Triangle::new(a.clone()?, b.clone()?, c.clone()?));
591    }
592
593    Some(triangles)
594}
595
596/// Common interpolation functions used across different shading types.
597struct InterpolationHelpers {
598    bp_coord: BitSize,
599    bp_comp: BitSize,
600    coord_max: f32,
601    comp_max: f32,
602    x_min: f32,
603    x_max: f32,
604    y_min: f32,
605    y_max: f32,
606}
607
608impl InterpolationHelpers {
609    fn new(
610        bp_coord: BitSize,
611        bp_comp: BitSize,
612        x_min: f32,
613        x_max: f32,
614        y_min: f32,
615        y_max: f32,
616    ) -> Self {
617        let coord_max = 2.0f32.powi(bp_coord.bits() as i32) - 1.0;
618        let comp_max = 2.0f32.powi(bp_comp.bits() as i32) - 1.0;
619        Self {
620            bp_coord,
621            bp_comp,
622            coord_max,
623            comp_max,
624            x_min,
625            x_max,
626            y_min,
627            y_max,
628        }
629    }
630
631    fn interpolate_coord(&self, n: u32, d_min: f32, d_max: f32) -> f32 {
632        interpolate(n as f32, 0.0, self.coord_max, d_min, d_max)
633    }
634
635    fn interpolate_comp(&self, n: u32, d_min: f32, d_max: f32) -> f32 {
636        interpolate(n as f32, 0.0, self.comp_max, d_min, d_max)
637    }
638
639    fn read_point(&self, reader: &mut BitReader) -> Option<Point> {
640        let x = self.interpolate_coord(reader.read(self.bp_coord)?, self.x_min, self.x_max);
641        let y = self.interpolate_coord(reader.read(self.bp_coord)?, self.y_min, self.y_max);
642        Some(Point::new(x as f64, y as f64))
643    }
644
645    fn read_colors(
646        &self,
647        reader: &mut BitReader,
648        has_function: bool,
649        decode: &[f32],
650    ) -> Option<ColorComponents> {
651        let mut colors = smallvec![];
652        if has_function {
653            colors.push(self.interpolate_comp(
654                reader.read(self.bp_comp)?,
655                *decode.first()?,
656                *decode.get(1)?,
657            ));
658        } else {
659            let num_components = decode.len() / 2;
660            for (_, decode) in (0..num_components).zip(decode.chunks_exact(2)) {
661                colors.push(self.interpolate_comp(
662                    reader.read(self.bp_comp)?,
663                    decode[0],
664                    decode[1],
665                ));
666            }
667        }
668        Some(colors)
669    }
670
671    fn read_triangle_vertex(
672        &self,
673        reader: &mut BitReader,
674        bpf: BitSize,
675        has_function: bool,
676        decode: &[f32],
677    ) -> Option<TriangleVertex> {
678        let flag = reader.read(bpf)?;
679        let point = self.read_point(reader)?;
680        let colors = self.read_colors(reader, has_function, decode)?;
681        reader.align();
682
683        Some(TriangleVertex {
684            flag,
685            point,
686            colors,
687        })
688    }
689}
690
691/// Split decode array into coordinate bounds and component decode values.
692fn split_decode(decode: &[f32]) -> Option<([f32; 4], &[f32])> {
693    decode.split_first_chunk::<4>().map(|(a, b)| (*a, b))
694}
695
696/// Generate triangles from a grid of points using a mapping function.
697fn generate_patch_triangles<F, I>(map_coordinate: F, interpolate: I) -> Vec<Triangle>
698where
699    F: Fn(Point) -> Point,
700    I: Fn(Point) -> ColorComponents,
701{
702    const GRID_SIZE: usize = 20;
703    let mut grid = vec![vec![Point::ZERO; GRID_SIZE]; GRID_SIZE];
704
705    // Create grid by mapping unit square coordinates.
706    for i in 0..GRID_SIZE {
707        for j in 0..GRID_SIZE {
708            let u = i as f64 / (GRID_SIZE - 1) as f64; // 0.0 to 1.0 (left to right).
709            let v = j as f64 / (GRID_SIZE - 1) as f64; // 0.0 to 1.0 (top to bottom).
710
711            // Map unit square coordinate to patch coordinate.
712            let unit_point = Point::new(u, v);
713            grid[i][j] = map_coordinate(unit_point);
714        }
715    }
716
717    // Create triangles from adjacent grid points.
718    let mut triangles = vec![];
719
720    for i in 0..(GRID_SIZE - 1) {
721        for j in 0..(GRID_SIZE - 1) {
722            let p00 = grid[i][j];
723            let p10 = grid[i + 1][j];
724            let p01 = grid[i][j + 1];
725            let p11 = grid[i + 1][j + 1];
726
727            // Calculate unit square coordinates for color interpolation.
728            let u0 = i as f64 / (GRID_SIZE - 1) as f64;
729            let u1 = (i + 1) as f64 / (GRID_SIZE - 1) as f64;
730            let v0 = j as f64 / (GRID_SIZE - 1) as f64;
731            let v1 = (j + 1) as f64 / (GRID_SIZE - 1) as f64;
732
733            // Create triangle vertices with interpolated colors.
734            let v00 = TriangleVertex {
735                flag: 0,
736                point: p00,
737                colors: interpolate(Point::new(u0, v0)),
738            };
739            let v10 = TriangleVertex {
740                flag: 0,
741                point: p10,
742                colors: interpolate(Point::new(u1, v0)),
743            };
744            let v01 = TriangleVertex {
745                flag: 0,
746                point: p01,
747                colors: interpolate(Point::new(u0, v1)),
748            };
749            let v11 = TriangleVertex {
750                flag: 0,
751                point: p11,
752                colors: interpolate(Point::new(u1, v1)),
753            };
754
755            triangles.push(Triangle::new(v00.clone(), v10.clone(), v01.clone()));
756            triangles.push(Triangle::new(v10.clone(), v11.clone(), v01.clone()));
757        }
758    }
759
760    triangles
761}
762
763fn read_lattice_triangles(
764    data: &[u8],
765    bp_cord: u8,
766    bp_comp: u8,
767    has_function: bool,
768    vertices_per_row: u32,
769    decode: &[f32],
770) -> Option<Vec<Triangle>> {
771    let bp_cord = BitSize::from_u8(bp_cord)?;
772    let bp_comp = BitSize::from_u8(bp_comp)?;
773
774    let mut lattices = vec![];
775
776    let ([x_min, x_max, y_min, y_max], decode) = split_decode(decode)?;
777    let mut reader = BitReader::new(data);
778    let helpers = InterpolationHelpers::new(bp_cord, bp_comp, x_min, x_max, y_min, y_max);
779
780    let read_single = |reader: &mut BitReader| -> Option<TriangleVertex> {
781        let point = helpers.read_point(reader)?;
782        let colors = helpers.read_colors(reader, has_function, decode)?;
783        reader.align();
784
785        Some(TriangleVertex {
786            flag: 0,
787            point,
788            colors,
789        })
790    };
791
792    'outer: loop {
793        let mut single_row = vec![];
794
795        for _ in 0..vertices_per_row {
796            let Some(next) = read_single(&mut reader) else {
797                break 'outer;
798            };
799
800            single_row.push(next);
801        }
802
803        lattices.push(single_row);
804    }
805
806    let mut triangles = vec![];
807
808    for i in 0..(lattices.len() - 1) {
809        for j in 0..(vertices_per_row as usize - 1) {
810            triangles.push(Triangle::new(
811                lattices[i][j].clone(),
812                lattices[i + 1][j].clone(),
813                lattices[i][j + 1].clone(),
814            ));
815
816            triangles.push(Triangle::new(
817                lattices[i + 1][j + 1].clone(),
818                lattices[i + 1][j].clone(),
819                lattices[i][j + 1].clone(),
820            ));
821        }
822    }
823
824    Some(triangles)
825}
826
827fn read_coons_patch_mesh(
828    data: &[u8],
829    bpf: u8,
830    bp_coord: u8,
831    bp_comp: u8,
832    has_function: bool,
833    decode: &[f32],
834) -> Option<Vec<CoonsPatch>> {
835    read_patch_mesh(
836        data,
837        bpf,
838        bp_coord,
839        bp_comp,
840        has_function,
841        decode,
842        12,
843        |control_points, colors| {
844            let mut coons_points = [Point::ZERO; 12];
845            coons_points.copy_from_slice(&control_points[0..12]);
846            CoonsPatch {
847                control_points: coons_points,
848                colors,
849            }
850        },
851    )
852}
853
854/// Generic patch mesh reading function that works for both Coons and Tensor Product patches.
855#[allow(clippy::too_many_arguments)]
856fn read_patch_mesh<P, F>(
857    data: &[u8],
858    bpf: u8,
859    bp_coord: u8,
860    bp_comp: u8,
861    has_function: bool,
862    decode: &[f32],
863    control_points_count: usize,
864    create_patch: F,
865) -> Option<Vec<P>>
866where
867    F: Fn([Point; 16], [ColorComponents; 4]) -> P,
868{
869    let bpf = BitSize::from_u8(bpf)?;
870    let bp_coord = BitSize::from_u8(bp_coord)?;
871    let bp_comp = BitSize::from_u8(bp_comp)?;
872
873    let ([x_min, x_max, y_min, y_max], decode) = split_decode(decode)?;
874    let mut reader = BitReader::new(data);
875    let helpers = InterpolationHelpers::new(bp_coord, bp_comp, x_min, x_max, y_min, y_max);
876
877    let read_colors = |reader: &mut BitReader| -> Option<ColorComponents> {
878        helpers.read_colors(reader, has_function, decode)
879    };
880
881    let mut prev_patch_points: Option<Vec<Point>> = None;
882    let mut prev_patch_colors: Option<[ColorComponents; 4]> = None;
883    let mut patches = vec![];
884
885    while let Some(flag) = reader.read(bpf) {
886        let mut control_points = vec![Point::ZERO; 16]; // Always allocate 16, use subset as needed.
887        let mut colors = [smallvec![], smallvec![], smallvec![], smallvec![]];
888
889        match flag {
890            0 => {
891                for i in 0..control_points_count {
892                    control_points[i] = helpers.read_point(&mut reader)?;
893                }
894
895                for i in 0..4 {
896                    colors[i] = read_colors(&mut reader)?;
897                }
898
899                prev_patch_points = Some(control_points.clone());
900                prev_patch_colors = Some(colors.clone());
901            }
902            1..=3 => {
903                let prev_points = prev_patch_points.as_ref()?;
904                let prev_colors = prev_patch_colors.as_ref()?;
905
906                copy_patch_control_points(flag, prev_points, &mut control_points);
907
908                match flag {
909                    1 => {
910                        colors[0] = prev_colors[1].clone();
911                        colors[1] = prev_colors[2].clone();
912                    }
913                    2 => {
914                        colors[0] = prev_colors[2].clone();
915                        colors[1] = prev_colors[3].clone();
916                    }
917                    3 => {
918                        colors[0] = prev_colors[3].clone();
919                        colors[1] = prev_colors[0].clone();
920                    }
921                    _ => unreachable!(),
922                }
923
924                for i in 4..control_points_count {
925                    control_points[i] = helpers.read_point(&mut reader)?;
926                }
927
928                colors[2] = read_colors(&mut reader)?;
929                colors[3] = read_colors(&mut reader)?;
930
931                prev_patch_points = Some(control_points.clone());
932                prev_patch_colors = Some(colors.clone());
933            }
934            _ => break,
935        }
936
937        let mut fixed_points = [Point::ZERO; 16];
938        for i in 0..16 {
939            if i < control_points.len() {
940                fixed_points[i] = control_points[i];
941            }
942        }
943
944        patches.push(create_patch(fixed_points, colors));
945    }
946    Some(patches)
947}
948
949fn copy_patch_control_points(
950    flag: u32,
951    prev_control_points: &[Point],
952    control_points: &mut [Point],
953) {
954    match flag {
955        1 => {
956            control_points[0] = prev_control_points[3];
957            control_points[1] = prev_control_points[4];
958            control_points[2] = prev_control_points[5];
959            control_points[3] = prev_control_points[6];
960        }
961        2 => {
962            control_points[0] = prev_control_points[6];
963            control_points[1] = prev_control_points[7];
964            control_points[2] = prev_control_points[8];
965            control_points[3] = prev_control_points[9];
966        }
967        3 => {
968            control_points[0] = prev_control_points[9];
969            control_points[1] = prev_control_points[10];
970            control_points[2] = prev_control_points[11];
971            control_points[3] = prev_control_points[0];
972        }
973        _ => {}
974    }
975}
976
977fn read_tensor_product_patch_mesh(
978    data: &[u8],
979    bpf: u8,
980    bp_coord: u8,
981    bp_comp: u8,
982    has_function: bool,
983    decode: &[f32],
984) -> Option<Vec<TensorProductPatch>> {
985    read_patch_mesh(
986        data,
987        bpf,
988        bp_coord,
989        bp_comp,
990        has_function,
991        decode,
992        16,
993        |control_points, colors| TensorProductPatch {
994            control_points,
995            colors,
996        },
997    )
998}
999
1000fn read_function(dict: &Dict, color_space: &ColorSpace) -> Option<ShadingFunction> {
1001    if let Some(arr) = dict.get::<Array>(FUNCTION) {
1002        let arr: Option<SmallVec<_>> = arr.iter::<Object>().map(|o| Function::new(&o)).collect();
1003        let arr = arr?;
1004
1005        if arr.len() != color_space.num_components() as usize {
1006            warn!("function array of shading has wrong size");
1007
1008            return None;
1009        }
1010
1011        Some(ShadingFunction::Multiple(arr))
1012    } else if let Some(obj) = dict.get::<Object>(FUNCTION) {
1013        Some(ShadingFunction::Single(Function::new(&obj)?))
1014    } else {
1015        None
1016    }
1017}