Skip to main content

fop_render/pdf/document/
gradient.rs

1//! PDF gradient shading object generation
2//!
3//! Functions to write gradient shading and interpolation function objects into a PDF byte stream.
4
5use fop_types::{ColorStop, Gradient};
6
7/// Write gradient shading objects to PDF
8///
9/// Generates two objects per gradient:
10/// 1. Function object (Type 2 exponential interpolation function)
11/// 2. Shading object (Type 2 for axial/linear, Type 3 for radial)
12///
13/// # Arguments
14/// * `gradient` - The gradient definition
15/// * `function_obj_id` - Object ID for the function
16/// * `shading_obj_id` - Object ID for the shading dictionary
17/// * `bytes` - Output buffer
18/// * `xref_offsets` - Cross-reference table offsets
19pub(super) fn write_gradient_objects(
20    gradient: &Gradient,
21    function_obj_id: usize,
22    shading_obj_id: usize,
23    bytes: &mut Vec<u8>,
24    xref_offsets: &mut Vec<usize>,
25) {
26    match gradient {
27        Gradient::Linear {
28            start_point,
29            end_point,
30            color_stops,
31        } => {
32            // For linear gradients, we create a Type 2 shading (axial)
33            // with a Type 2 function (exponential interpolation) for each color segment
34
35            if color_stops.len() < 2 {
36                return; // Need at least 2 stops
37            }
38
39            // Generate function object(s)
40            // For simplicity, we'll use a stitching function if there are multiple segments
41            write_gradient_function(function_obj_id, color_stops, bytes, xref_offsets);
42
43            // Generate shading object (Type 2 - Axial)
44            xref_offsets.push(bytes.len());
45            bytes.extend_from_slice(format!("{} 0 obj\n", shading_obj_id).as_bytes());
46            bytes.extend_from_slice(b"<<\n");
47            bytes.extend_from_slice(b"/ShadingType 2\n"); // Axial shading
48            bytes.extend_from_slice(b"/ColorSpace /DeviceRGB\n");
49
50            // Coords: [x0 y0 x1 y1] - start and end points in user space
51            // Use normalized coordinates (0-100 range)
52            bytes.extend_from_slice(
53                format!(
54                    "/Coords [{:.3} {:.3} {:.3} {:.3}]\n",
55                    start_point.x.to_pt(),
56                    start_point.y.to_pt(),
57                    end_point.x.to_pt(),
58                    end_point.y.to_pt()
59                )
60                .as_bytes(),
61            );
62
63            bytes.extend_from_slice(format!("/Function {} 0 R\n", function_obj_id).as_bytes());
64            bytes.extend_from_slice(b"/Extend [true true]\n"); // Extend beyond gradient line
65            bytes.extend_from_slice(b">>\n");
66            bytes.extend_from_slice(b"endobj\n");
67        }
68        Gradient::Radial {
69            center,
70            radius,
71            color_stops,
72        } => {
73            // For radial gradients, we create a Type 3 shading (radial)
74            if color_stops.len() < 2 {
75                return;
76            }
77
78            // Generate function object(s)
79            write_gradient_function(function_obj_id, color_stops, bytes, xref_offsets);
80
81            // Generate shading object (Type 3 - Radial)
82            xref_offsets.push(bytes.len());
83            bytes.extend_from_slice(format!("{} 0 obj\n", shading_obj_id).as_bytes());
84            bytes.extend_from_slice(b"<<\n");
85            bytes.extend_from_slice(b"/ShadingType 3\n"); // Radial shading
86            bytes.extend_from_slice(b"/ColorSpace /DeviceRGB\n");
87
88            // Coords: [x0 y0 r0 x1 y1 r1] - start circle and end circle
89            // For a simple radial gradient, start radius is 0
90            let center_x = center.x.to_pt();
91            let center_y = center.y.to_pt();
92            let end_radius = radius * 100.0; // Scale from normalized to user space
93
94            bytes.extend_from_slice(
95                format!(
96                    "/Coords [{:.3} {:.3} 0 {:.3} {:.3} {:.3}]\n",
97                    center_x, center_y, center_x, center_y, end_radius
98                )
99                .as_bytes(),
100            );
101
102            bytes.extend_from_slice(format!("/Function {} 0 R\n", function_obj_id).as_bytes());
103            bytes.extend_from_slice(b"/Extend [true true]\n");
104            bytes.extend_from_slice(b">>\n");
105            bytes.extend_from_slice(b"endobj\n");
106        }
107    }
108}
109
110/// Write gradient interpolation function
111///
112/// Creates either a Type 2 (exponential) function for simple 2-color gradients,
113/// or a Type 3 (stitching) function for multi-stop gradients.
114fn write_gradient_function(
115    function_obj_id: usize,
116    color_stops: &[ColorStop],
117    bytes: &mut Vec<u8>,
118    xref_offsets: &mut Vec<usize>,
119) {
120    xref_offsets.push(bytes.len());
121    bytes.extend_from_slice(format!("{} 0 obj\n", function_obj_id).as_bytes());
122    bytes.extend_from_slice(b"<<\n");
123
124    if color_stops.len() == 2 {
125        // Simple Type 2 function (exponential interpolation)
126        bytes.extend_from_slice(b"/FunctionType 2\n");
127        bytes.extend_from_slice(b"/Domain [0 1]\n");
128        bytes.extend_from_slice(b"/N 1\n"); // Linear interpolation
129
130        let c0 = &color_stops[0].color;
131        let c1 = &color_stops[1].color;
132
133        bytes.extend_from_slice(
134            format!(
135                "/C0 [{:.3} {:.3} {:.3}]\n",
136                c0.r_f32(),
137                c0.g_f32(),
138                c0.b_f32()
139            )
140            .as_bytes(),
141        );
142        bytes.extend_from_slice(
143            format!(
144                "/C1 [{:.3} {:.3} {:.3}]\n",
145                c1.r_f32(),
146                c1.g_f32(),
147                c1.b_f32()
148            )
149            .as_bytes(),
150        );
151    } else {
152        // Type 3 stitching function for multi-stop gradients
153        bytes.extend_from_slice(b"/FunctionType 3\n");
154        bytes.extend_from_slice(b"/Domain [0 1]\n");
155
156        // Build bounds array (boundaries between segments)
157        bytes.extend_from_slice(b"/Bounds [");
158        for stop in color_stops.iter().skip(1).take(color_stops.len() - 2) {
159            bytes.extend_from_slice(format!("{:.3} ", stop.offset).as_bytes());
160        }
161        bytes.extend_from_slice(b"]\n");
162
163        // Build encode array (map domain to function domains)
164        bytes.extend_from_slice(b"/Encode [");
165        for _ in 0..color_stops.len() - 1 {
166            bytes.extend_from_slice(b"0 1 ");
167        }
168        bytes.extend_from_slice(b"]\n");
169
170        // Build functions array (one function per segment)
171        bytes.extend_from_slice(b"/Functions [\n");
172        for i in 0..color_stops.len() - 1 {
173            let c0 = &color_stops[i].color;
174            let c1 = &color_stops[i + 1].color;
175
176            bytes.extend_from_slice(b"  <<\n");
177            bytes.extend_from_slice(b"    /FunctionType 2\n");
178            bytes.extend_from_slice(b"    /Domain [0 1]\n");
179            bytes.extend_from_slice(b"    /N 1\n");
180            bytes.extend_from_slice(
181                format!(
182                    "    /C0 [{:.3} {:.3} {:.3}]\n",
183                    c0.r_f32(),
184                    c0.g_f32(),
185                    c0.b_f32()
186                )
187                .as_bytes(),
188            );
189            bytes.extend_from_slice(
190                format!(
191                    "    /C1 [{:.3} {:.3} {:.3}]\n",
192                    c1.r_f32(),
193                    c1.g_f32(),
194                    c1.b_f32()
195                )
196                .as_bytes(),
197            );
198            bytes.extend_from_slice(b"  >>\n");
199        }
200        bytes.extend_from_slice(b"]\n");
201    }
202
203    bytes.extend_from_slice(b">>\n");
204    bytes.extend_from_slice(b"endobj\n");
205}