diff --git a/Cargo.lock b/Cargo.lock
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -818,6 +818,7 @@ dependencies = [
  "thiserror 2.0.21",
  "ttf-parser",
  "wasm-bindgen-test",
+ "write-fonts",
 ]
 
 [[package]]
diff --git a/NOTICE b/NOTICE
--- a/NOTICE
+++ b/NOTICE
@@ -91,7 +91,7 @@ The optional writer embeds Compact ICC Profiles by Clinton Ingram
 See licenses/pdf-writer/LICENSE-ICC.txt and ATTRIBUTION.md for sources and hashes.
 The writer adds no font files. Its four existing ICC profiles are retained under vendor/krilla/icc.
 
-Krilla is vendored with a native unwind-cleanup patch; see vendor/krilla/README.craft.md
+Krilla is vendored with a native unwind-cleanup patch and an SVG glyph check fix; see vendor/krilla/README.craft.md
 and vendor/krilla/SOURCE.json. Its upstream MIT/Apache-2.0 licences and CC0 profiles are unchanged.
 
 Optional PDF reader
diff --git a/crates/pdf/Cargo.toml b/crates/pdf/Cargo.toml
--- a/crates/pdf/Cargo.toml
+++ b/crates/pdf/Cargo.toml
@@ -33,6 +33,7 @@ hayro-interpret = { version = "=0.7.0", default-features = false }
 hayro-cmap = "=0.1.0"
 kurbo = "0.13"
 ttf-parser = "=0.25.1"
+write-fonts = "=0.48.1"
 
 [target.'cfg(target_arch = "wasm32")'.dev-dependencies]
 wasm-bindgen-test = "0.3"
diff --git a/crates/pdf/README.md b/crates/pdf/README.md
--- a/crates/pdf/README.md
+++ b/crates/pdf/README.md
@@ -103,7 +103,7 @@ that supply shaped glyphs do not need that shaper. Adapters that call `draw_text
 DeckCraft, also need `write-simple-text` when adopting this revision. `write-pdf` adds
 embedded-PDF support independently of text shaping.
 
-The writer pins Krilla 0.8.2, with a local unwind-cleanup patch documented in
+The writer pins Krilla 0.8.2, with a local unwind-cleanup patch and an SVG glyph check fix documented in
 [`vendor/krilla/README.craft.md`](../../vendor/krilla/README.craft.md). Successful
 serialization uses the same backend version as before extraction. It provides:
 
diff --git a/crates/pdf/tests/svg.rs b/crates/pdf/tests/svg.rs
new file mode 100644
--- /dev/null
+++ b/crates/pdf/tests/svg.rs
@@ -0,0 +1,91 @@
+//! OpenType SVG glyphs, drawn from a minimal synthetic font built in the test.
+#![cfg(feature = "write")]
+#![allow(clippy::unwrap_used, clippy::panic)]
+
+use backend::geom::Point;
+use backend::page::PageSettings;
+use backend::text::{GlyphId, KrillaGlyph};
+use craft_pdf::write::{self, Metadata, WriteError, Writer, backend};
+use kurbo::{Rect, Shape};
+use write_fonts::FontBuilder;
+use write_fonts::tables::glyf::{GlyfLocaBuilder, Glyph, SimpleGlyph};
+use write_fonts::tables::head::{Flags, Head, MacStyle};
+use write_fonts::tables::hhea::Hhea;
+use write_fonts::tables::hmtx::{Hmtx, LongMetric};
+use write_fonts::tables::loca::LocaFormat;
+use write_fonts::tables::maxp::Maxp;
+use write_fonts::types::{FWord, Fixed, LongDateTime, Tag, UfWord};
+
+/// The glyph the font's `SVG ` table has a document for.
+const SVG: u16 = 1;
+/// A glyph with an outline only.
+const PLAIN: u16 = 2;
+
+/// A font of two squares whose `SVG ` table holds a document for [`SVG`] alone.
+fn svg_font() -> Vec<u8> {
+    let square = Glyph::Simple(SimpleGlyph::from_bezpath(&Rect::new(0.0, 0.0, 500.0, 700.0).to_path(0.1)).unwrap());
+    let glyphs = [Glyph::Empty, square.clone(), square];
+    let mut glyf_loca = GlyfLocaBuilder::new();
+    for glyph in &glyphs {
+        glyf_loca.add_glyph(glyph).unwrap();
+    }
+    let (glyf, loca, loca_format) = glyf_loca.build();
+    let index_to_loc_format = if loca_format == LocaFormat::Long { 1 } else { 0 };
+    let date = LongDateTime::new(0);
+    let head = Head::new(Fixed::ONE, 0, Flags::empty(), 1000, date, date, 0, 0, 500, 700, MacStyle::empty(), 8, index_to_loc_format);
+    let num_glyphs = glyphs.len() as u16;
+    let hhea = Hhea {
+        ascender: FWord::new(800),
+        descender: FWord::new(-200),
+        advance_width_max: UfWord::new(600),
+        number_of_h_metrics: num_glyphs,
+        ..Default::default()
+    };
+    let hmtx = Hmtx::new(glyphs.iter().map(|_| LongMetric::new(600, 0)).collect(), vec![]);
+
+    // The `SVG ` table: its header (version, the document list's offset, reserved), then the
+    // document list with one record (first and last glyph, the document's offset from the list
+    // and length) and the document.
+    let doc = format!(r#"<svg xmlns="http://www.w3.org/2000/svg"><rect id="glyph{SVG}" width="500" height="700"/></svg>"#);
+    let mut svg = [0u16.to_be_bytes().as_slice(), &10u32.to_be_bytes(), &0u32.to_be_bytes()].concat();
+    svg.extend(1u16.to_be_bytes());
+    svg.extend([SVG.to_be_bytes(), SVG.to_be_bytes()].concat());
+    svg.extend([14u32.to_be_bytes(), (doc.len() as u32).to_be_bytes()].concat());
+    svg.extend(doc.as_bytes());
+
+    let mut builder = FontBuilder::new();
+    builder.add_table(&head).unwrap();
+    builder.add_table(&hhea).unwrap();
+    builder.add_table(&Maxp::new(num_glyphs)).unwrap();
+    builder.add_table(&hmtx).unwrap();
+    builder.add_table(&glyf).unwrap();
+    builder.add_table(&loca).unwrap();
+    builder.add_raw(Tag::new(b"SVG "), svg);
+    builder.build()
+}
+
+/// Export one glyph of the font, uncompressed, behind the writer's unwind guard.
+fn export(glyph: u16) -> Result<String, WriteError> {
+    write::guard(|| {
+        let font = write::font(&svg_font(), 0, None)?;
+        let settings = backend::SerializeSettings { compress_content_streams: false, ..Default::default() };
+        let mut writer = Writer::new(settings, Metadata::default());
+        let mut page = writer.document_mut().start_page_with(PageSettings::from_wh(100.0, 100.0).unwrap());
+        let mut surface = page.surface();
+        let glyphs = [KrillaGlyph::new(GlyphId::new(u32::from(glyph)), 0.6, 0.0, 0.0, 0.0, 0..1, None)];
+        surface.draw_glyphs(Point::from_xy(10.0, 80.0), &glyphs, font, "x", 50.0, false);
+        surface.finish();
+        page.finish();
+        Ok(String::from_utf8_lossy(&writer.finish()?).into_owned())
+    })
+}
+
+#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
+#[cfg_attr(not(target_arch = "wasm32"), test)]
+fn only_glyphs_with_an_svg_document_go_into_a_type3_font() {
+    let pdf = export(SVG).unwrap();
+    assert!(pdf.contains("/Type3"), "the SVG glyph is a Type 3 glyph");
+    let pdf = export(PLAIN).unwrap();
+    assert!(!pdf.contains("/Type3"), "a glyph without a document stays in the font");
+    assert!(pdf.contains("/CIDFontType2"), "as a TrueType glyph");
+}
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
@@ -32,3 +32,7 @@ A PNG whose header implies more than 1 GiB of decoded output is refused by
 fallibly, so a small file (or a bitmap glyph in a font) cannot request an allocation that
 ends the process. The colour and alpha buffers that PNG, GIF and WebP decoding split an
 image into are reserved fallibly too, and a failure there is an image error.
+
+A glyph counts as an SVG glyph only when the font's SVG table has a document for it.
+Upstream's check asked only whether the font has the table, so every glyph of such a font
+went into a Type 3 font instead of the embedded font.
diff --git a/vendor/krilla/SOURCE.json b/vendor/krilla/SOURCE.json
--- a/vendor/krilla/SOURCE.json
+++ b/vendor/krilla/SOURCE.json
@@ -352,7 +352,7 @@
     {
       "path": "src/text/glyph/svg.rs",
       "upstream_sha256": "67d0bc7e67db7685de44810fb4a0b6eca73b45a2d0a1b3aed88630bab3787894",
-      "sha256": "67d0bc7e67db7685de44810fb4a0b6eca73b45a2d0a1b3aed88630bab3787894"
+      "sha256": "d1a712bb36500c04393c428d73532bc79dc784a9aea9ec1729f9a5ab920edbe0"
     },
     {
       "path": "src/text/group.rs",
diff --git a/vendor/krilla/src/text/glyph/svg.rs b/vendor/krilla/src/text/glyph/svg.rs
--- a/vendor/krilla/src/text/glyph/svg.rs
+++ b/vendor/krilla/src/text/glyph/svg.rs
@@ -8,8 +8,7 @@ use crate::text::GlyphId;
 pub(crate) fn has_svg_data(font: &Font, glyph: GlyphId) -> bool {
     font.font_ref()
         .svg()
-        .map(|svg_table| svg_table.glyph_data(glyph.to_skrifa()).is_ok())
-        .is_ok()
+        .is_ok_and(|svg_table| svg_table.glyph_data(glyph.to_skrifa()).is_ok_and(|data| data.is_some()))
 }
 
 /// Draw an SVG-based glyph on a surface.
