Repository: typst/typst
File: crates/typst-pdf/src/gradient.rs (Rust)

Code before:

    let cp2 = [
        u16::quantize(cp2.x.to_f32(), [0.0, 1.0]).to_be(),
        u16::quantize(cp2.y.to_f32(), [0.0, 1.0]).to_be(),
    ];

    // Push the points
    target.extend_from_slice(bytemuck::cast_slice(&[
        p1, p1, p2, p2, cp1, cp2, p3, p3, p1, p1, p1, p1,
    ]));

    // Push the colors.
    // TODO: check if it is correct
    target.extend_from_slice(
        &c0.iter()
            .flat_map(|arg0: &u16| u16::to_be_bytes(*arg0))
            .cycle()
            .take(4 * c0.len())
            .chain(
                c1.iter()
                    .flat_map(|arg0: &u16| u16::to_be_bytes(*arg0))
                    .cycle()
                    .take(4 * c1.len()),
            )
            .collect::<SmallVec<[u8; 32]>>(),
    );
}

fn control_point(c: Point, r: f32, angle_start: f32, angle_end: f32) -> (Point, Point) {
    let n = (TAU / (angle_end - angle_start)).abs();
    let f = ((angle_end - angle_start) / n).tan() * 4.0 / 3.0;

    let p1 = c + Point::new(
        Abs::pt((r * angle_start.cos() - f * r * angle_start.sin()) as f64),
        Abs::pt((r * angle_start.sin() + f * r * angle_start.cos()) as f64),
    );

    let p2 = c + Point::new(
        Abs::pt((r * angle_end.cos() + f * r * angle_end.sin()) as f64),
        Abs::pt((r * angle_end.sin() - f * r * angle_end.cos()) as f64),
    );

    (p1, p2)
}

#[comemo::memoize]
fn compute_vertex_stream(gradient: &Gradient, aspect_ratio: Ratio) -> Arc<Vec<u8>> {
    let Gradient::Conic(conic) = gradient else { unreachable!() };

    // Generated vertices for the Coons patches
    let mut vertices = Vec::new();

Review comment (suggestion):
Similar approach, but simpler. I'm not sure if the `collect` is really necessary as `Vec::extend` can also just consume the iterator, but maybe it has to then resize more often as I don't think that there's a size hint.
```rs
let colors = [c0, c0, c1, c1]
    .into_iter()
    .flat_map(|c| c.iter().copied().map(u16::to_be_bytes))
    .flatten()
    .collect::<SmallVec<[u8; 32]>>();

target.extend(colors);
```

Code after:
    // Push the colors.
    let colors = [c0, c0, c1, c1]
        .into_iter()
        .flat_map(|c| c.iter().copied().map(u16::to_be_bytes))
        .flatten();

    target.extend(colors);
}

fn control_point(c: Point, r: f32, angle_start: f32, angle_end: f32) -> (Point, Point) {
    let n = (TAU / (angle_end - angle_start)).abs();
    let f = ((angle_end - angle_start) / n).tan() * 4.0 / 3.0;

    let p1 = c + Point::new(
        Abs::pt((r * angle_start.cos() - f * r * angle_start.sin()) as f64),
        Abs::pt((r * angle_start.sin() + f * r * angle_start.cos()) as f64),
    );

    let p2 = c + Point::new(
        Abs::pt((r * angle_end.cos() + f * r * angle_end.sin()) as f64),
        Abs::pt((r * angle_end.sin() - f * r * angle_end.cos()) as f64),
    );

    (p1, p2)
}

#[comemo::memoize]
fn compute_vertex_stream(gradient: &Gradient, aspect_ratio: Ratio) -> Arc<Vec<u8>> {
    let Gradient::Conic(conic) = gradient else { unreachable!() };

    // Generated vertices for the Coons patches
    let mut vertices = Vec::new();

    // Correct the gradient's angle
    let angle = Gradient::correct_aspect_ratio(conic.angle, aspect_ratio);

    for window in conic.stops.windows(2) {
        let ((c0, t0), (c1, t1)) = (window[0], window[1]);

        // Precision:
        // - On an even color, insert a stop every 90deg
        // - For a hue-based color space, insert 200 stops minimum
        // - On any other, insert 20 stops minimum
        let max_dt = if c0 == c1 {
            0.25
        } else if conic.space.hue_index().is_some() {
            0.005
        } else {
            0.05
        };
        let encode_space = conic