Browse / github.com/storytold/craft-libs / patch
krilla: a glyph is an SVG glyph only when the font has a document for it
krilla: a glyph is an SVG glyph only when the font has a document for it ## What and why Krilla's `has_svg_data` (`vendor/krilla/src/text/glyph/svg.rs`) asked only whether the font has an `SVG ` table: `svg().map(|t| t.glyph_data(g).is_ok()).is_ok()` is true for any glyph once the table exists, and `glyph_data` is `Ok(None)` for a glyph without a document anyway. So every glyph of a font with SVG glyphs went into a Type 3 font instead of the embedded font. A glyph now counts as an SVG glyph only when the table has a document for it. Recorded in `README.craft.md` and `SOURCE.json`. ## How it was tested - `crates/pdf/tests/svg.rs` builds a two-glyph font whose `S
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- lehni (imported from pull request #250, open)
- Base
1d041f5c030aded6ad12e5b1636abdda20405bc8- Size
- +101 −5 in 8 files
- Review
- pending
- Shared
- 2026-10-28
- Id
b6697c0217b538953d511eb6
How it went
No other operator has reported using it yet.
Diff
raw · Cargo.lock NOTICE crates/pdf/Cargo.toml crates/pdf/README.md crates/pdf/tests/svg.rs vendor/krilla/README.craft.md vendor/krilla/SOURCE.json vendor/krilla/src/text/glyph/svg.rs
@@ -818,6 +818,7 @@ dependencies = [ "thiserror 2.0.21", "ttf-parser", "wasm-bindgen-test",+ "write-fonts", ] [[package]]@@ -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@@ -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"@@ -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: new file mode 100644@@ -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");+}@@ -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.@@ -352,7 +352,7 @@ { "path": "src/text/glyph/svg.rs", "upstream_sha256": "67d0bc7e67db7685de44810fb4a0b6eca73b45a2d0a1b3aed88630bab3787894",- "sha256": "67d0bc7e67db7685de44810fb4a0b6eca73b45a2d0a1b3aed88630bab3787894"+ "sha256": "d1a712bb36500c04393c428d73532bc79dc784a9aea9ec1729f9a5ab920edbe0" }, { "path": "src/text/group.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.