diff --git a/crates/typst/src/export/svg.rs b/crates/typst/src/export/svg.rs index 2b99c3ffe..03e121810 100644 --- a/crates/typst/src/export/svg.rs +++ b/crates/typst/src/export/svg.rs @@ -666,16 +666,27 @@ impl SVGRenderer { self.xml.write_attribute("y2", &y2); for window in linear.stops.windows(2) { - let (_, start_t) = window[0]; - let (_, end_t) = window[1]; + let (start_c, start_t) = window[0]; + let (end_c, end_t) = window[1]; + + self.xml.start_element("stop"); + self.xml + .write_attribute_fmt("offset", format_args!("{start_t:?}")); + self.xml.write_attribute("stop-color", &start_c.to_hex()); + self.xml.end_element(); // Generate (256 / len) stops between the two stops. // This is a workaround for a bug in many readers: // They tend to just ignore the color space of the gradient. // The goal is to have smooth gradients but not to balloon the file size // too much if there are already a lot of stops as in most presets. - let len = (256 / linear.stops.len() as u32).max(1); - for i in 0..len { + let len = if gradient.anti_alias() { + (256 / linear.stops.len() as u32).max(2) + } else { + 2 + }; + + for i in 1..(len - 1) { let t0 = i as f64 / (len - 1) as f64; let t = start_t + (end_t - start_t) * t0; let c = gradient.sample(RatioOrAngle::Ratio(t)); @@ -685,6 +696,11 @@ impl SVGRenderer { self.xml.write_attribute("stop-color", &c.to_hex()); self.xml.end_element(); } + + self.xml.start_element("stop"); + self.xml.write_attribute_fmt("offset", format_args!("{end_t:?}")); + self.xml.write_attribute("stop-color", &end_c.to_hex()); + self.xml.end_element() } self.xml.end_element(); diff --git a/crates/typst/src/geom/gradient.rs b/crates/typst/src/geom/gradient.rs index 8f920f3b0..387636267 100644 --- a/crates/typst/src/geom/gradient.rs +++ b/crates/typst/src/geom/gradient.rs @@ -445,7 +445,7 @@ impl Gradient { angle: grad.angle, space: grad.space, relative: grad.relative, - anti_alias: true, + anti_alias: grad.anti_alias, })), }) } @@ -762,7 +762,7 @@ fn sample_stops(stops: &[(Color, Ratio)], mixing_space: ColorSpace, t: f64) -> C let hue_0 = if hue_0 < hue_1 { hue_0 + 360.0 } else { hue_0 }; let hue_1 = if hue_1 < hue_0 { hue_1 + 360.0 } else { hue_1 }; - let hue = (hue_0 * (1.0 - t as f32) + hue_1 * t as f32).rem_euclid(360.0); + let hue = hue_0 * (1.0 - t as f32) + hue_1 * t as f32; if mixing_space == ColorSpace::Hsl { let [_, saturation, lightness, alpha] = out.to_hsl().to_vec4(); diff --git a/tests/ref/visualize/gradient-sharp.png b/tests/ref/visualize/gradient-sharp.png index 8e50f597e..30e6fb666 100644 Binary files a/tests/ref/visualize/gradient-sharp.png and b/tests/ref/visualize/gradient-sharp.png differ diff --git a/tests/typ/visualize/gradient-sharp.typ b/tests/typ/visualize/gradient-sharp.typ index e19df58fb..424beb8b7 100644 --- a/tests/typ/visualize/gradient-sharp.typ +++ b/tests/typ/visualize/gradient-sharp.typ @@ -1,4 +1,13 @@ // Test sharp gradients. --- -#square(size: 100pt, fill: gradient.linear(..color.map.rainbow).sharp(10)) +#square( + size: 100pt, + fill: gradient.linear(..color.map.rainbow, space: color.hsl).sharp(10), +) + +--- +#square( + size: 100pt, + fill: gradient.linear(..color.map.rainbow, space: color.hsl).sharp(10, smoothness: 40%), +)