diff --git a/vendor/krilla/README.craft.md b/vendor/krilla/README.craft.md
--- a/vendor/krilla/README.craft.md
+++ b/vendor/krilla/README.craft.md
@@ -26,3 +26,20 @@ The shared writer's native subprocess regression covers the previously aborting
 cleanup and a successful fresh export afterward. Native and WASM export tests
 cover the unchanged successful path. WASM aborts, aborting panic configurations
 and process-wide allocation failures remain outside unwind recovery.
+
+## Mesh gradients
+
+Krilla 0.8.2 paints only flat colour, linear, radial and sweep gradients and tiling patterns, so
+an app writing a gradient mesh had to cut each patch into flat-colour pieces: a VectorCraft
+document holding an Illustrator file's 22,850 mesh patches became a 197 MB PDF with stepped
+blends. The patch adds `paint::MeshGradient` (and `MeshPatch`): Coons patches with a colour and
+an opacity at each corner, written as one PDF Type 6 shading (ISO 32000-1, 8.7.4.5.7) through
+pdf-writer's `StreamShading`, so the viewer or RIP blends them at its own resolution. Each patch
+is an 8-bit flag (no shared edge), its twelve points as 32-bit coordinates over the mesh's
+bounds (`Decode`) and its four corner colours at 8 bits a component, Flate-compressed. It goes
+through the same path as the other gradients (`GradientProperties::MeshGradient`): a shading
+pattern for the fill, and for corner opacities below 1 a luminosity soft mask holding the same
+mesh in gray (`use_opacities`), as cairo and Illustrator write transparent meshes. Every patch's
+colours must share one colour space, as gradient stops must. Changed files: `src/content.rs`,
+`src/surface.rs`, `src/graphics/mask.rs`, `src/graphics/paint.rs`,
+`src/graphics/shading_function.rs`.
diff --git a/vendor/krilla/SOURCE.json b/vendor/krilla/SOURCE.json
--- a/vendor/krilla/SOURCE.json
+++ b/vendor/krilla/SOURCE.json
@@ -122,7 +122,7 @@
     {
       "path": "src/content.rs",
       "upstream_sha256": "9e59b2c77b73b36caedacfe80894d1cce7fd6fe6f7438ea24fda2019391a980c",
-      "sha256": "9e59b2c77b73b36caedacfe80894d1cce7fd6fe6f7438ea24fda2019391a980c"
+      "sha256": "4d37359345a195ae2328d96e5edb46d510d8c57b8717a161c29325b93634977b"
     },
     {
       "path": "src/data.rs",
@@ -177,7 +177,7 @@
     {
       "path": "src/graphics/mask.rs",
       "upstream_sha256": "d1dbc7d4d28388ee43c43294bfcbeec79136a65ac2816bdaf19eac4b88669661",
-      "sha256": "d1dbc7d4d28388ee43c43294bfcbeec79136a65ac2816bdaf19eac4b88669661"
+      "sha256": "111a6b732d8aedd489fbc4b5295368019f098925de3e12dfa544efe4ec2a5f04"
     },
     {
       "path": "src/graphics/mod.rs",
@@ -187,7 +187,7 @@
     {
       "path": "src/graphics/paint.rs",
       "upstream_sha256": "c3de4b7056f3f952009f39f0e0d0311bc9af54807660f9e46f67c177572edf45",
-      "sha256": "c3de4b7056f3f952009f39f0e0d0311bc9af54807660f9e46f67c177572edf45"
+      "sha256": "2d96935b59ac5c0827ad01a2d45665791003f11d8ebb8302ebce6847f25b1adf"
     },
     {
       "path": "src/graphics/separation.rs",
@@ -197,7 +197,7 @@
     {
       "path": "src/graphics/shading_function.rs",
       "upstream_sha256": "0e0fc8bb1a32272457ef560fa80933972d0f5ef5fb7046424c6de48093cfd978",
-      "sha256": "0e0fc8bb1a32272457ef560fa80933972d0f5ef5fb7046424c6de48093cfd978"
+      "sha256": "054181042b73bb963764d701f8e7837285f35b81b066128a93017c78e0a63bf4"
     },
     {
       "path": "src/graphics/shading_pattern.rs",
@@ -317,7 +317,7 @@
     {
       "path": "src/surface.rs",
       "upstream_sha256": "94de0a0384fc69c9785c6595b3f2c7ed8319635c901b8dc12894e252ab3ef8da",
-      "sha256": "936dfeb978add9d4443658c209f2d72034583a7771179587d8135ab870ded905"
+      "sha256": "a1e8cd42673ef8d3221af6100f1514280c4d6559e32559a30ea3bbd822c216f2"
     },
     {
       "path": "src/text/cid.rs",
diff --git a/vendor/krilla/src/content.rs b/vendor/krilla/src/content.rs
--- a/vendor/krilla/src/content.rs
+++ b/vendor/krilla/src/content.rs
@@ -1114,6 +1114,16 @@ impl ContentBuilder {
                 let (gradient_props, transform) = sg.clone().gradient_properties(bounds);
                 write_gradient(gradient_props, sc, chunk_container, transform, self);
             }
+            InnerPaint::MeshGradient(mg) => {
+                let transform = mg.transform;
+                write_gradient(
+                    GradientProperties::MeshGradient(mg.clone()),
+                    sc,
+                    chunk_container,
+                    transform,
+                    self,
+                );
+            }
             InnerPaint::Pattern(pat) => {
                 let mut pat = Arc::unwrap_or_clone(pat.clone());
                 pat.transform = pattern_transform(pat.transform);
diff --git a/vendor/krilla/src/graphics/mask.rs b/vendor/krilla/src/graphics/mask.rs
--- a/vendor/krilla/src/graphics/mask.rs
+++ b/vendor/krilla/src/graphics/mask.rs
@@ -60,6 +60,15 @@ impl Mask {
                     return None;
                 }
             }
+            GradientProperties::MeshGradient(mg) => {
+                if mg
+                    .patches
+                    .iter()
+                    .all(|p| p.opacities.iter().all(|o| o.get() == 1.0))
+                {
+                    return None;
+                }
+            }
         }
 
         let shading_function = ShadingFunction::new(gradient_properties, true);
diff --git a/vendor/krilla/src/graphics/paint.rs b/vendor/krilla/src/graphics/paint.rs
--- a/vendor/krilla/src/graphics/paint.rs
+++ b/vendor/krilla/src/graphics/paint.rs
@@ -49,6 +49,57 @@ impl Hash for LinearGradient {
     }
 }
 
+/// One Coons patch of a [`MeshGradient`].
+#[derive(Debug, Clone, PartialEq)]
+pub struct MeshPatch {
+    /// The twelve boundary control points, in the order a PDF Type 6 shading lists them
+    /// (ISO 32000-1, 8.7.4.5.7): starting at a corner, three cubic Bézier edges' worth of points
+    /// going one way round the patch, so the corners are `points[0]`, `[3]`, `[6]` and `[9]`.
+    pub points: [(f32, f32); 12],
+    /// The colours at the corners `points[0]`, `[3]`, `[6]` and `[9]`. Every patch of a mesh
+    /// gradient needs its colours in the same colour space.
+    pub colors: [Color; 4],
+    /// The opacities at the same corners.
+    pub opacities: [NormalizedF32; 4],
+}
+
+impl Eq for MeshPatch {}
+
+impl Hash for MeshPatch {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        for (x, y) in &self.points {
+            x.to_bits().hash(state);
+            y.to_bits().hash(state);
+        }
+        self.colors.hash(state);
+        self.opacities.hash(state);
+    }
+}
+
+/// A mesh gradient: Coons patches, each blending its four corner colours across its curved
+/// shape, written as one PDF Type 6 shading so the viewer or RIP blends them smoothly at its
+/// own resolution. Corner opacities below 1 add a soft mask, as for the other gradients.
+/// Patches paint in order (a later one over an earlier one); a fill paints only where they lie.
+#[derive(Debug, Clone, PartialEq)]
+pub struct MeshGradient {
+    /// The patches.
+    pub patches: Vec<MeshPatch>,
+    /// A transform that should be applied to the mesh gradient.
+    pub transform: Transform,
+    /// Whether the gradient should be anti-aliased.
+    pub anti_alias: bool,
+}
+
+impl Eq for MeshGradient {}
+
+impl Hash for MeshGradient {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.patches.hash(state);
+        self.transform.hash(state);
+        self.anti_alias.hash(state);
+    }
+}
+
 /// A radial gradient.
 #[derive(Debug, Clone, PartialEq)]
 pub struct RadialGradient {
@@ -169,6 +220,7 @@ pub(crate) enum InnerPaint {
     LinearGradient(LinearGradient),
     RadialGradient(RadialGradient),
     SweepGradient(SweepGradient),
+    MeshGradient(Arc<MeshGradient>),
     Pattern(Arc<Pattern>),
 }
 
@@ -210,6 +262,12 @@ impl From<cmyk::Color> for Paint {
     }
 }
 
+impl From<MeshGradient> for Paint {
+    fn from(value: MeshGradient) -> Self {
+        Paint(InnerPaint::MeshGradient(Arc::new(value)))
+    }
+}
+
 impl From<Color> for Paint {
     fn from(value: Color) -> Self {
         Paint(InnerPaint::Color(value))
diff --git a/vendor/krilla/src/graphics/shading_function.rs b/vendor/krilla/src/graphics/shading_function.rs
--- a/vendor/krilla/src/graphics/shading_function.rs
+++ b/vendor/krilla/src/graphics/shading_function.rs
@@ -4,6 +4,7 @@ use std::sync::Arc;
 
 use bumpalo::Bump;
 use pdf_writer::types::{FunctionShadingType, PostScriptOp};
+use pdf_writer::writers::StreamShadingType;
 use pdf_writer::{Chunk, Finish, Ref};
 use tiny_skia_path::Point;
 
@@ -12,7 +13,7 @@ use crate::configure::ValidationError;
 use crate::geom::{Rect, Transform};
 use crate::graphics::color::luma;
 use crate::graphics::color::{Color, ColorSpace};
-use crate::graphics::paint::{LinearGradient, RadialGradient, SweepGradient};
+use crate::graphics::paint::{LinearGradient, MeshGradient, RadialGradient, SweepGradient};
 use crate::graphics::paint::{SpreadMethod, Stop};
 use crate::num::NormalizedF32;
 use crate::resource;
@@ -82,6 +83,7 @@ impl Hash for PostScriptGradient {
 pub(crate) enum GradientProperties {
     RadialAxialGradient(RadialAxialGradient),
     PostScriptGradient(PostScriptGradient),
+    MeshGradient(Arc<MeshGradient>),
 }
 
 impl GradientProperties {
@@ -98,6 +100,7 @@ impl GradientProperties {
                     return Some((&psg.stops[0].color, psg.stops[0].opacity));
                 }
             }
+            GradientProperties::MeshGradient(_) => {}
         }
 
         None
@@ -265,6 +268,23 @@ impl Cacheable for ShadingFunction {
                     registered_cs,
                 )
             }
+            GradientProperties::MeshGradient(mg) => {
+                // No patches paints nothing; the shading is still written, as it is referenced.
+                let first = mg
+                    .patches
+                    .first()
+                    .map_or_else(|| luma::Color::black().into(), |p| p.colors[0].clone());
+                let shading_cs = shading_color_space(sc, first, self.0.use_opacities);
+                let registered_cs = sc.register_colorspace(chunk_container, shading_cs);
+                serialize_mesh_shading(
+                    sc,
+                    &mut stream_chunk,
+                    root_ref,
+                    mg,
+                    self.0.use_opacities,
+                    registered_cs,
+                );
+            }
         }
 
         // Note: The stream chunk might be empty.
@@ -284,6 +304,83 @@ fn shading_color_space(sc: &mut SerializeContext, color: Color, use_opacities: b
     }
 }
 
+/// A mesh gradient as a Type 6 (Coons patch mesh) shading: per patch an 8-bit edge flag (0, no
+/// shared edge), the twelve points as 32-bit coordinates over the mesh's bounds (`Decode`), and
+/// the four corner colours (with `use_opacities`, the opacities as gray) at 8 bits a component,
+/// Flate-compressed. 32-bit coordinates keep the patches where they are to well under a printer
+/// dot, as Illustrator writes its meshes.
+fn serialize_mesh_shading(
+    sc: &mut SerializeContext,
+    chunk: &mut Chunk,
+    root_ref: Ref,
+    mesh: &MeshGradient,
+    use_opacities: bool,
+    cs: MaybeDeviceColorSpace,
+) {
+    let (mut x0, mut y0, mut x1, mut y1) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
+    for (x, y) in mesh.patches.iter().flat_map(|p| p.points.iter()) {
+        (x0, y0, x1, y1) = (x0.min(*x), y0.min(*y), x1.max(*x), y1.max(*y));
+    }
+    if !(x0.is_finite() && y0.is_finite() && x1.is_finite() && y1.is_finite()) {
+        (x0, y0, x1, y1) = (0.0, 0.0, 1.0, 1.0);
+    }
+    // A zero-size range would divide by zero: widen it by a point either way.
+    if x1 - x0 < f32::EPSILON {
+        (x0, x1) = (x0 - 1.0, x1 + 1.0);
+    }
+    if y1 - y0 < f32::EPSILON {
+        (y0, y1) = (y0 - 1.0, y1 + 1.0);
+    }
+    let components = if use_opacities {
+        1
+    } else {
+        mesh.patches
+            .first()
+            .map_or(1, |p| p.colors[0].to_pdf_color().len())
+    };
+    let coord = |v: f32, lo: f32, hi: f32| {
+        (((f64::from(v) - f64::from(lo)) / (f64::from(hi) - f64::from(lo))).clamp(0.0, 1.0)
+            * f64::from(u32::MAX))
+        .round() as u32
+    };
+    let byte = |v: f32| (v.clamp(0.0, 1.0) * 255.0).round() as u8;
+    let mut data = Vec::with_capacity(mesh.patches.len() * (1 + 12 * 8 + 4 * components));
+    for patch in &mesh.patches {
+        data.push(0u8);
+        for (x, y) in &patch.points {
+            data.extend(coord(*x, x0, x1).to_be_bytes());
+            data.extend(coord(*y, y0, y1).to_be_bytes());
+        }
+        for (color, opacity) in patch.colors.iter().zip(patch.opacities.iter()) {
+            if use_opacities {
+                data.push(byte(opacity.get()));
+            } else {
+                let values = color.to_pdf_color();
+                // A colour from another space can't be written in this one: never misread bytes.
+                for i in 0..components {
+                    data.push(byte(values.get(i).copied().unwrap_or(0.0)));
+                }
+            }
+        }
+    }
+    let filter_stream =
+        FilterStreamBuilder::new_from_binary_data(&data).finish(&sc.serialize_settings());
+    let mut shading = chunk.stream_shading(root_ref, filter_stream.encoded_data());
+    filter_stream.write_filters(shading.deref_mut().deref_mut());
+    shading.shading_type(StreamShadingType::CoonsPatch);
+    set_colorspace(cs, shading.deref_mut().deref_mut());
+    shading.bits_per_coordinate(32);
+    shading.bits_per_component(8);
+    shading.bits_per_flag(8);
+    let mut decode = vec![x0, x1, y0, y1];
+    for _ in 0..components {
+        decode.extend([0.0, 1.0]);
+    }
+    shading.decode(decode);
+    shading.anti_alias(mesh.anti_alias);
+    shading.finish();
+}
+
 fn serialize_postscript_shading(
     sc: &mut SerializeContext,
     chunk: &mut Chunk,
diff --git a/vendor/krilla/src/surface.rs b/vendor/krilla/src/surface.rs
--- a/vendor/krilla/src/surface.rs
+++ b/vendor/krilla/src/surface.rs
@@ -594,6 +594,10 @@ impl<'a> Surface<'a> {
                 r.stops.iter().any(|s| s.opacity != NormalizedF32::ONE)
             }
             InnerPaint::SweepGradient(r) => r.stops.iter().any(|s| s.opacity != NormalizedF32::ONE),
+            InnerPaint::MeshGradient(m) => m
+                .patches
+                .iter()
+                .any(|p| p.opacities.iter().any(|o| *o != NormalizedF32::ONE)),
             InnerPaint::Pattern(_) => false,
         };
 
