Integration: #823 a linear gradient across a straight line runs along its length, on screen and in PDF alike

This commit is contained in:
aiongg
2026-10-13 19:49:32 +00:00
parent 9b605cecd9
commit 72d7ea2e82
4 changed files with 25 additions and 20 deletions
+15
View File
@@ -54,6 +54,21 @@ impl Fill {
}
}
/// A linear gradient's axis on an object whose bounds (in its own space) are `b`: the
/// Gradient Swatch tool's vector, or the box's extent along the gradient's angle through its
/// centre. A box with no extent along that angle (a straight line, across it) takes its longer
/// side.
pub fn linear_axis(&self, b: Rect) -> (Point, Point) {
if let Some([x0, y0, x1, y1]) = self.gradient_vector {
return (Point::new(x0, y0), Point::new(x1, y1));
}
let a = self.gradient_angle.unwrap_or(0.0).to_radians();
let half = (b.width() * a.cos().abs() + b.height() * a.sin().abs()) / 2.0;
let half = if half > 1e-9 { half } else { b.width().max(b.height()).max(1e-3) / 2.0 };
let d = designcraft_geom::Vec2::new(a.cos(), -a.sin()) * half;
(b.center() - d, b.center() + d)
}
pub fn none() -> Self {
Fill { swatch: designcraft_color::swatch::NONE.into(), tint: 1.0, gradient_angle: None, gradient_vector: None, overprint: false }
}
+3 -1
View File
@@ -2950,7 +2950,9 @@ mod gradient_stroke_tests {
let (screen, pdf) =
line_with_stroke_gradient(json!({"angle": 90, "stops": [{"location": 0, "color": "#ff0000"}, {"location": 100, "color": "#0000ff"}]}));
let (s, p) = (screen.pixel(100, 60), pdf.pixel(100, 60));
assert!(s[1] < 60 && s[0].max(s[2]) > 150, "screen: the stroke is painted, {s:?}");
// The line's box has no height: the gradient runs across the line's length, so the stroke
// shows its middle.
assert!(s[1] < 60 && s[0] > 80 && s[2] > 80, "screen: the stroke is painted, {s:?}");
assert!((0..3).all(|i| s[i].abs_diff(p[i]) < 40), "screen {s:?}, pdf {p:?}");
}
}
+2 -10
View File
@@ -1046,7 +1046,7 @@ impl Exporter<'_> {
/// [`Self::fill_paint`] for a gradient laid out in a space that `xf` maps to the current one
/// (text runs: the frame's text space under the run's origin).
pub(crate) fn fill_paint_in(&mut self, fill: &designcraft_doc::Fill, bounds: Rect, xf: Affine) -> Option<krilla::paint::Paint> {
let (swatch, tint, angle) = (fill.swatch.as_str(), fill.tint, fill.gradient_angle);
let (swatch, tint) = (fill.swatch.as_str(), fill.tint);
if let Some(g) = designcraft_color::swatch::resolve_gradient(&self.doc.swatches, swatch) {
let kind = g.kind;
let expanded = g.expanded_stops();
@@ -1079,8 +1079,6 @@ impl Exporter<'_> {
if stops.is_empty() {
return None;
}
let c = bounds.center();
let v = fill.gradient_vector;
return Some(match kind {
GradientKind::Radial => {
// A circle stretched into the object's ellipse, as on screen.
@@ -1103,13 +1101,7 @@ impl Exporter<'_> {
.into()
}
_ => {
let a = angle.unwrap_or(0.0).to_radians();
let half = ((bounds.width() * a.cos().abs() + bounds.height() * a.sin().abs()) / 2.0).max(1e-3);
let d = Vec2::new(a.cos(), -a.sin()) * half;
let (p0, p1) = match v {
Some([x0, y0, x1, y1]) => (designcraft_geom::Point::new(x0, y0), designcraft_geom::Point::new(x1, y1)),
None => (c - d, c + d),
};
let (p0, p1) = fill.linear_axis(bounds);
LinearGradient {
x1: p0.x as f32,
y1: p0.y as f32,
+5 -9
View File
@@ -949,27 +949,23 @@ pub fn color_of(c: &designcraft_color::Color, alpha: f32) -> peniko::Color {
/// Set a swatch fill (solid or gradient) as the paint. Returns false for [None]/unknown.
fn set_fill_paint(ctx: &mut RenderContext, doc: &Document, fill: &designcraft_doc::Fill, bounds: Rect) -> bool {
let (swatch, tint, angle) = (fill.swatch.as_str(), fill.tint, fill.gradient_angle);
let (swatch, tint) = (fill.swatch.as_str(), fill.tint);
if let Some(g) = designcraft_color::swatch::resolve_gradient(&doc.swatches, swatch) {
let stops: Vec<peniko::ColorStop> = g.expanded_stops().iter().map(|(o, c, a)| peniko::ColorStop::from((*o, color_of(c, *a)))).collect();
if stops.is_empty() {
return false;
}
let c = bounds.center();
let grad = match (g.kind, fill.gradient_vector) {
let grad = match g.kind {
// Radial: a circle stretched into the object's ellipse (the paint transform).
(designcraft_color::GradientKind::Radial, _) => {
designcraft_color::GradientKind::Radial => {
let (c, rx, ry) = fill.radial_ellipse(bounds);
let rx = rx.max(1e-3);
ctx.set_paint_transform(Affine::translate(c.to_vec2()) * Affine::scale_non_uniform(1.0, ry / rx) * Affine::translate(-c.to_vec2()));
peniko::Gradient::new_radial(c, rx as f32).with_stops(stops.as_slice())
}
(_, Some([x0, y0, x1, y1])) => peniko::Gradient::new_linear((x0, y0), (x1, y1)).with_stops(stops.as_slice()),
_ => {
let a = angle.unwrap_or(0.0).to_radians();
let half = (bounds.width() * a.cos().abs() + bounds.height() * a.sin().abs()) / 2.0;
let d = Vec2::new(a.cos(), -a.sin()) * half;
peniko::Gradient::new_linear(c - d, c + d).with_stops(stops.as_slice())
let (a, b) = fill.linear_axis(bounds);
peniko::Gradient::new_linear(a, b).with_stops(stops.as_slice())
}
};
ctx.set_paint(grad);