From 1d9e577e3655f7d4c615feb953b948526a2168b7 Mon Sep 17 00:00:00 2001 From: Tom Main Date: Fri, 30 Oct 2026 19:21:41 -0400 Subject: [PATCH 1/2] font.list: report each face's fsType embedding bits font.list now gives, for the face that sets the text, its raw OS/2 fsType and what the bits mean: restrictedLicence, bitmapOnly, noSubsetting, editable and previewPrint (null when the face is missing). A host that makes print PDFs can then refuse a face whose licence doesn't allow the export, including system fonts and the bundled fonts, without reading the font files itself. PDF export subsets every font, so a caller needs noSubsetting as well as the restricted and bitmap-only bits. FontFace gains fs_type(); restricted_licence() and bitmap_only() read it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01ApvpyStzzHbKyDBNxpzn2Y --- ROADMAP.md | 1 + crates/engine/src/cmd/fonts.rs | 65 ++++++++++++++++++++++++++++++++- crates/fonts/src/fontdb/face.rs | 11 ++++-- docs/target-app-parity.md | 2 +- 4 files changed, 74 insertions(+), 5 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 796ff7306..9d2955ca0 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -388,6 +388,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m Newest first. +- **2026-10-31** · `font.list` reports the embedding licence of the face that sets each font's text: its OS/2 `fsType` and what the bits mean (`restrictedLicence`, `bitmapOnly`, `noSubsetting`, `editable`, `previewPrint`; null when the font is missing). A script or host that makes PDFs can check every face, bundled and installed ones included, without reading font files. Export behaviour is unchanged. No readiness percentage change. - **2026-10-30** · Import warnings about a story have a Show button: it closes the warnings (the status line keeps them) and selects the story's first frame, fitted in the window. `file.open` returns `warningItems`, the frame each IDML or InDesign warning is about (or null), matching the converter's INDD story numbers (`story 30892`, written to IDML as `u78ac`) and IDML `Self` names to the imported stories. No readiness percentage change. - **2026-10-30** · Parent pages: a running-header variable shows its name in angle brackets on the parent itself (it was blank), as InDesign shows it; on the document pages, and in text with no page, it shows its text as before. No readiness percentage change. - **2026-10-30** · Without facing pages, a page of a multi-page spread shows the parent page in its place, as in InDesign: the second page of a two-page spread shows the parent's second page. Every single-sided page showed the parent's first page, so an InDesign book set up without facing pages but laid out in two-page spreads repeated the left parent page's items (a black heading bar) on its right pages and lost the right parent page's. No readiness percentage change. diff --git a/crates/engine/src/cmd/fonts.rs b/crates/engine/src/cmd/fonts.rs index ee80eb914..52ddf4239 100644 --- a/crates/engine/src/cmd/fonts.rs +++ b/crates/engine/src/cmd/fonts.rs @@ -14,7 +14,7 @@ use crate::{Result, Session}; pub fn specs() -> Vec { vec![ - cmd!(query "font.list", "Fonts in Document", [], None, "{} → [{family, style, characters, missing, styleMissing, source: bundled|installed|document|added (null when missing), matchStatus: exact|styleSubstitute|missing, resolvedFamily, resolvedStyle, replacementFamily, replacementStyle}] (missing first)", has_doc, |s, _| { + cmd!(query "font.list", "Fonts in Document", [], None, "{} → [{family, style, characters, missing, styleMissing, source: bundled|installed|document|added (null when missing), matchStatus: exact|styleSubstitute|missing, resolvedFamily, resolvedStyle, replacementFamily, replacementStyle, fsType, restrictedLicence, bitmapOnly, noSubsetting, editable, previewPrint (the OS/2 fsType embedding bits of the face that sets the text and what they mean; null when missing)}] (missing first)", has_doc, |s, _| { Ok(Value::Array(list(&s.doc()?.doc))) }), cmd!( @@ -75,6 +75,18 @@ pub(super) struct UsedFont { pub resolved_style: String, pub replacement_family: String, pub replacement_style: String, + /// The OS/2 `fsType` of the face that sets the text (`resolved_*`); None when missing. + pub fs_type: Option, + /// Restricted License embedding (bit 1 without bits 2–3). + pub restricted_licence: Option, + /// Bitmap embedding only (bit 9). + pub bitmap_only: Option, + /// No subsetting (bit 8): the font may only be embedded whole. + pub no_subsetting: Option, + /// Editable embedding (bit 3). + pub editable: Option, + /// Preview & Print embedding (bit 2). + pub preview_print: Option, } fn list(d: &Document) -> Vec { @@ -124,6 +136,8 @@ pub(super) fn used_fonts(d: &Document) -> Vec { let resolved = db.face(&family, &style); let characters: Vec = characters.into_iter().filter(|c| !('\u{E000}'..='\u{E1FF}').contains(c)).collect(); let replacement = db.replacement_face(&family, &style, &characters); + let fs_type = (!missing).then(|| resolved.fs_type()); + let bit = |mask: u16| fs_type.map(|t| t & mask != 0); let source = (!missing).then(|| match db.face(&family, &style).source { FontSource::Bundled => "bundled", FontSource::Installed(_) => "installed", @@ -148,6 +162,12 @@ pub(super) fn used_fonts(d: &Document) -> Vec { resolved_style: resolved.style.clone(), replacement_family: replacement.family.clone(), replacement_style: replacement.style.clone(), + fs_type, + restricted_licence: (!missing).then(|| resolved.restricted_licence()), + bitmap_only: bit(0x0200), + no_subsetting: bit(0x0100), + editable: bit(0x0008), + preview_print: bit(0x0004), } }) .collect(); @@ -297,6 +317,49 @@ mod tests { assert_eq!(before, s.execute("font.list", &json!({})).unwrap()); } + /// `font.list` reports each face's OS/2 fsType and what its bits allow, so a caller can + /// refuse a face whose licence doesn't allow the export it is about to make. + #[test] + fn font_list_reports_the_embedding_licence_bits() { + use designcraft_fonts::testing::{font_with, with_fs_type}; + const FAMILY: &str = "DocFont List FsType"; + const FS_TYPES: [u16; 3] = [0x0002, 0x0108, 0x0204]; + for fs_type in FS_TYPES { + designcraft_fonts::FontDb::global() + .add_font(with_fs_type(font_with(&format!("{FAMILY} {fs_type:04x}"), &['W']).unwrap(), fs_type).unwrap()); + } + let mut s = Session::new(); + s.execute("file.new", &json!({})).unwrap(); + // "W W W Plain Gone": each W in one of the fonts above, Plain in the default (bundled) + // font, Gone in a family that isn't available. + let r = s.execute("frame.create", &json!({"rect": [72, 72, 400, 300], "content": "text", "text": "W W W Plain Gone"})).unwrap(); + let sid = s.doc().unwrap().doc.item(designcraft_doc::ItemId(r["id"].as_u64().unwrap())).unwrap().text_frame().unwrap().story; + let set = |s: &mut Session, at: usize, end: usize, family: &str| { + s.execute("text.select", &json!({"story": sid.0, "anchor": at, "focus": end})).unwrap(); + s.execute("type.char", &json!({"fontFamily": family, "fontStyle": "Regular"})).unwrap(); + }; + for (i, fs_type) in FS_TYPES.into_iter().enumerate() { + set(&mut s, i * 2, i * 2 + 1, &format!("{FAMILY} {fs_type:04x}")); + } + set(&mut s, 12, 16, "Nonexistent Sans"); + let l = s.execute("font.list", &json!({})).unwrap(); + let rows = l.as_array().unwrap(); + let entry = |family: &str| rows.iter().find(|f| f["family"] == family).unwrap_or_else(|| panic!("{family}: {l}")); + let bits = |e: &Value| ["fsType", "restrictedLicence", "bitmapOnly", "noSubsetting", "editable", "previewPrint"].map(|k| e[k].clone()); + let (t, f) = (json!(true), json!(false)); + assert_eq!(entry(&format!("{FAMILY} 0002"))["source"], "added", "{l}"); + assert_eq!(bits(entry(&format!("{FAMILY} 0002"))), [json!(0x0002), t.clone(), f.clone(), f.clone(), f.clone(), f.clone()], "{l}"); + assert_eq!(bits(entry(&format!("{FAMILY} 0108"))), [json!(0x0108), f.clone(), f.clone(), t.clone(), t.clone(), f.clone()], "{l}"); + assert_eq!(bits(entry(&format!("{FAMILY} 0204"))), [json!(0x0204), f.clone(), t.clone(), f.clone(), f.clone(), t.clone()], "{l}"); + // The bundled fonts are OFL: installable embedding, every bit clear. + let bundled = rows.iter().find(|r| r["source"] == "bundled").unwrap_or_else(|| panic!("no bundled face: {l}")); + assert_eq!(bits(bundled), [json!(0), f.clone(), f.clone(), f.clone(), f.clone(), f], "{l}"); + // A missing face has no file to read. + let missing = entry("Nonexistent Sans"); + assert_eq!(missing["missing"], true, "{l}"); + assert_eq!(bits(missing), [(); 6].map(|()| Value::Null), "{l}"); + } + #[test] fn find_and_replace_font() { let mut s = Session::new(); diff --git a/crates/fonts/src/fontdb/face.rs b/crates/fonts/src/fontdb/face.rs index 058be0c0e..2283682dd 100644 --- a/crates/fonts/src/fontdb/face.rs +++ b/crates/fonts/src/fontdb/face.rs @@ -297,16 +297,21 @@ impl FontFace { /// (bit 1) without a less restrictive bit (Preview & Print, Editable). Fonts without an OS/2 /// table aren't restricted. pub fn restricted_licence(&self) -> bool { - use skrifa::raw::TableProvider; - let fs_type = self.skrifa().and_then(|f| f.os2().ok()).map_or(0, |t| t.fs_type()); + let fs_type = self.fs_type(); fs_type & 0x0002 != 0 && fs_type & 0x000C == 0 } /// Does the font's licence allow embedding bitmaps only? OS/2 `fsType` bit 9: the outlines /// may not be passed on, so the font file can't be packaged whole. pub fn bitmap_only(&self) -> bool { + self.fs_type() & 0x0200 != 0 + } + + /// The font's OS/2 `fsType` (embedding licence bits), 0 (installable) when the font has no + /// OS/2 table. + pub fn fs_type(&self) -> u16 { use skrifa::raw::TableProvider; - self.skrifa().and_then(|f| f.os2().ok()).is_some_and(|t| t.fs_type() & 0x0200 != 0) + self.skrifa().and_then(|f| f.os2().ok()).map_or(0, |t| t.fs_type()) } } diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 9c3bff483..2ac146e1f 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -293,7 +293,7 @@ It is evidence of breadth: each item exists. It is not a claim of InDesign-level - Column guides: with the Selection tool, drag an interior column guide to move its gutter (both guides together, keeping its width, stopped by the neighbouring guides and the margins), snapping to the document grid, guides and objects, or with Shift to the ruler's tick marks; Escape cancels; one undo step. Refused while Lock Column Guides is on (the default). A document page takes the result as its own columns; on a parent page it reaches the pages that follow it. Objects stay where they are (there is no persistent Layout Adjustment setting yet). `layout.moveColumnGuide`. - Links panel: every placed graphic with status (OK / Modified / Missing / Embedded, checked against the file on disk), page and effective PPI (low resolution flagged); Relink…, Go To, Update, Embed; `links.*` commands. - Linked text files: `file.place {link: true}` or Preferences › File Handling › Create Links When Placing Text Files keeps a placed .docx/.rtf/.txt story linked to its file (path, size and hash, and the style map and conflict option used). The Links panel shows it OK / Modified / Missing; Update imports the file again into the same frames and thread with the saved style map, replacing edits made in the layout (the Links panel and menu ask first; the `links.update` command updates and returns a warning; undoable); Relink, Go To and Unlink. Saved in the native format; IDML exports the text unlinked. -- Missing fonts: highlighted pink on screen (never in output), listed by Preflight; Type › Find/Replace Font (fonts in the document, missing first; replace in text, cells, footnotes and styles); `font.list` / `font.replace`. Type › Reload Fonts (`font.reload`) scans the font folders again and reads again the installed font files edited or removed since they were read, then recomposes the open documents: a font installed or edited while DesignCraft runs needs no restart (#328, #547). InDesign notices such changes by itself. +- Missing fonts: highlighted pink on screen (never in output), listed by Preflight; Type › Find/Replace Font (fonts in the document, missing first; replace in text, cells, footnotes and styles); `font.list` (with each face's OS/2 `fsType` embedding bits) / `font.replace`. Type › Reload Fonts (`font.reload`) scans the font folders again and reads again the installed font files edited or removed since they were read, then recomposes the open documents: a font installed or edited while DesignCraft runs needs no restart (#328, #547). InDesign notices such changes by itself. - Missing glyphs, as in InDesign: a character the applied font lacks is drawn as that font's missing-glyph box (its .notdef; a drawn box when the font's is empty), on screen and in PDF (as a path, so PDF/A and PDF/UA exports take it), and Preflight lists the characters by font. Preferences › Composition › Draw Missing Glyphs from Fallback Fonts (`document.preferences {glyphFallback}`, stored with the document) draws them from other fonts instead; it is off in new documents and IDML imports, on in documents saved before it existed. A missing font's substitute always uses fallback fonts. - Damage-region repaint: after an edit the canvas re-renders only the frames whose composed text (or the items that) changed and patches its texture — pixel-identical to a full render; typing in a frame repaints in under a millisecond instead of a full-viewport render (22 ms measured on the sample). - Native engine saves, Save a Copy, snippets, books, libraries, swatches and engine/MCP exports use same-folder atomic replacement through craft-libs/craft-atomic, as do desktop Services writes and CLI raster exports. Failed writes before replacement retain the original, and already-open readers retain the old bytes. On Unix a new file gets the permissions ordinary file creation gives it (the umask applies) and a replaced file keeps its permissions. Parent-folder flushing is best effort, so this is not a power-loss durability guarantee. From 472869598f72a51d3cdf1b18503e270ceb8d9134 Mon Sep 17 00:00:00 2001 From: Tom Main Date: Sun, 1 Nov 2026 13:31:06 -0500 Subject: [PATCH 2/2] font.list: a face without an OS/2 table reports a null fsType A font with no readable OS/2 table doesn't say what its licence allows, and reporting fsType 0 made it look installable. FontFace::fs_type() now returns None for it, and font.list reports fsType and the five flags as null. restricted_licence() and bitmap_only() still treat it as unrestricted, so export behaviour doesn't change. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01ApvpyStzzHbKyDBNxpzn2Y --- ROADMAP.md | 2 +- crates/engine/src/cmd/fonts.rs | 23 +++++++++++++++-------- crates/fonts/src/fontdb/face.rs | 12 ++++++------ 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 9d2955ca0..b1ee7f04b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -388,7 +388,7 @@ list) · [roadmap.md](docs/roadmap.md) (milestones, plan) · [parity-checklist.m Newest first. -- **2026-10-31** · `font.list` reports the embedding licence of the face that sets each font's text: its OS/2 `fsType` and what the bits mean (`restrictedLicence`, `bitmapOnly`, `noSubsetting`, `editable`, `previewPrint`; null when the font is missing). A script or host that makes PDFs can check every face, bundled and installed ones included, without reading font files. Export behaviour is unchanged. No readiness percentage change. +- **2026-10-31** · `font.list` reports the embedding licence of the face that sets each font's text: its OS/2 `fsType` and what the bits mean (`restrictedLicence`, `bitmapOnly`, `noSubsetting`, `editable`, `previewPrint`; null when the font is missing or has no OS/2 table). A script or host that makes PDFs can check every face, bundled and installed ones included, without reading font files. Export behaviour is unchanged. No readiness percentage change. - **2026-10-30** · Import warnings about a story have a Show button: it closes the warnings (the status line keeps them) and selects the story's first frame, fitted in the window. `file.open` returns `warningItems`, the frame each IDML or InDesign warning is about (or null), matching the converter's INDD story numbers (`story 30892`, written to IDML as `u78ac`) and IDML `Self` names to the imported stories. No readiness percentage change. - **2026-10-30** · Parent pages: a running-header variable shows its name in angle brackets on the parent itself (it was blank), as InDesign shows it; on the document pages, and in text with no page, it shows its text as before. No readiness percentage change. - **2026-10-30** · Without facing pages, a page of a multi-page spread shows the parent page in its place, as in InDesign: the second page of a two-page spread shows the parent's second page. Every single-sided page showed the parent's first page, so an InDesign book set up without facing pages but laid out in two-page spreads repeated the left parent page's items (a black heading bar) on its right pages and lost the right parent page's. No readiness percentage change. diff --git a/crates/engine/src/cmd/fonts.rs b/crates/engine/src/cmd/fonts.rs index 52ddf4239..77cebe369 100644 --- a/crates/engine/src/cmd/fonts.rs +++ b/crates/engine/src/cmd/fonts.rs @@ -14,7 +14,7 @@ use crate::{Result, Session}; pub fn specs() -> Vec { vec![ - cmd!(query "font.list", "Fonts in Document", [], None, "{} → [{family, style, characters, missing, styleMissing, source: bundled|installed|document|added (null when missing), matchStatus: exact|styleSubstitute|missing, resolvedFamily, resolvedStyle, replacementFamily, replacementStyle, fsType, restrictedLicence, bitmapOnly, noSubsetting, editable, previewPrint (the OS/2 fsType embedding bits of the face that sets the text and what they mean; null when missing)}] (missing first)", has_doc, |s, _| { + cmd!(query "font.list", "Fonts in Document", [], None, "{} → [{family, style, characters, missing, styleMissing, source: bundled|installed|document|added (null when missing), matchStatus: exact|styleSubstitute|missing, resolvedFamily, resolvedStyle, replacementFamily, replacementStyle, fsType, restrictedLicence, bitmapOnly, noSubsetting, editable, previewPrint (the OS/2 fsType embedding bits of the face that sets the text and what they mean; null when missing or the face has no OS/2 table)}] (missing first)", has_doc, |s, _| { Ok(Value::Array(list(&s.doc()?.doc))) }), cmd!( @@ -75,7 +75,8 @@ pub(super) struct UsedFont { pub resolved_style: String, pub replacement_family: String, pub replacement_style: String, - /// The OS/2 `fsType` of the face that sets the text (`resolved_*`); None when missing. + /// The OS/2 `fsType` of the face that sets the text (`resolved_*`); None when missing or the + /// face has no OS/2 table (as are the bits below). pub fs_type: Option, /// Restricted License embedding (bit 1 without bits 2–3). pub restricted_licence: Option, @@ -136,7 +137,7 @@ pub(super) fn used_fonts(d: &Document) -> Vec { let resolved = db.face(&family, &style); let characters: Vec = characters.into_iter().filter(|c| !('\u{E000}'..='\u{E1FF}').contains(c)).collect(); let replacement = db.replacement_face(&family, &style, &characters); - let fs_type = (!missing).then(|| resolved.fs_type()); + let fs_type = (!missing).then(|| resolved.fs_type()).flatten(); let bit = |mask: u16| fs_type.map(|t| t & mask != 0); let source = (!missing).then(|| match db.face(&family, &style).source { FontSource::Bundled => "bundled", @@ -163,7 +164,7 @@ pub(super) fn used_fonts(d: &Document) -> Vec { replacement_family: replacement.family.clone(), replacement_style: replacement.style.clone(), fs_type, - restricted_licence: (!missing).then(|| resolved.restricted_licence()), + restricted_licence: fs_type.map(|_| resolved.restricted_licence()), bitmap_only: bit(0x0200), no_subsetting: bit(0x0100), editable: bit(0x0008), @@ -321,18 +322,20 @@ mod tests { /// refuse a face whose licence doesn't allow the export it is about to make. #[test] fn font_list_reports_the_embedding_licence_bits() { - use designcraft_fonts::testing::{font_with, with_fs_type}; + use designcraft_fonts::testing::{font_with, with_fs_type, with_table_len}; const FAMILY: &str = "DocFont List FsType"; const FS_TYPES: [u16; 3] = [0x0002, 0x0108, 0x0204]; for fs_type in FS_TYPES { designcraft_fonts::FontDb::global() .add_font(with_fs_type(font_with(&format!("{FAMILY} {fs_type:04x}"), &['W']).unwrap(), fs_type).unwrap()); } + // An OS/2 table cut to nothing can't be read: as if the font had none. + designcraft_fonts::FontDb::global().add_font(with_table_len(font_with(&format!("{FAMILY} no OS/2"), &['W']).unwrap(), b"OS/2", 0).unwrap()); let mut s = Session::new(); s.execute("file.new", &json!({})).unwrap(); - // "W W W Plain Gone": each W in one of the fonts above, Plain in the default (bundled) + // "W W W W Plain Gone": each W in one of the fonts above, Plain in the default (bundled) // font, Gone in a family that isn't available. - let r = s.execute("frame.create", &json!({"rect": [72, 72, 400, 300], "content": "text", "text": "W W W Plain Gone"})).unwrap(); + let r = s.execute("frame.create", &json!({"rect": [72, 72, 400, 300], "content": "text", "text": "W W W W Plain Gone"})).unwrap(); let sid = s.doc().unwrap().doc.item(designcraft_doc::ItemId(r["id"].as_u64().unwrap())).unwrap().text_frame().unwrap().story; let set = |s: &mut Session, at: usize, end: usize, family: &str| { s.execute("text.select", &json!({"story": sid.0, "anchor": at, "focus": end})).unwrap(); @@ -341,7 +344,8 @@ mod tests { for (i, fs_type) in FS_TYPES.into_iter().enumerate() { set(&mut s, i * 2, i * 2 + 1, &format!("{FAMILY} {fs_type:04x}")); } - set(&mut s, 12, 16, "Nonexistent Sans"); + set(&mut s, 6, 7, &format!("{FAMILY} no OS/2")); + set(&mut s, 14, 18, "Nonexistent Sans"); let l = s.execute("font.list", &json!({})).unwrap(); let rows = l.as_array().unwrap(); let entry = |family: &str| rows.iter().find(|f| f["family"] == family).unwrap_or_else(|| panic!("{family}: {l}")); @@ -354,6 +358,9 @@ mod tests { // The bundled fonts are OFL: installable embedding, every bit clear. let bundled = rows.iter().find(|r| r["source"] == "bundled").unwrap_or_else(|| panic!("no bundled face: {l}")); assert_eq!(bits(bundled), [json!(0), f.clone(), f.clone(), f.clone(), f.clone(), f], "{l}"); + // A face without an OS/2 table doesn't say what its licence allows. + assert_eq!(entry(&format!("{FAMILY} no OS/2"))["missing"], false, "{l}"); + assert_eq!(bits(entry(&format!("{FAMILY} no OS/2"))), [(); 6].map(|()| Value::Null), "{l}"); // A missing face has no file to read. let missing = entry("Nonexistent Sans"); assert_eq!(missing["missing"], true, "{l}"); diff --git a/crates/fonts/src/fontdb/face.rs b/crates/fonts/src/fontdb/face.rs index 2283682dd..014220a16 100644 --- a/crates/fonts/src/fontdb/face.rs +++ b/crates/fonts/src/fontdb/face.rs @@ -297,21 +297,21 @@ impl FontFace { /// (bit 1) without a less restrictive bit (Preview & Print, Editable). Fonts without an OS/2 /// table aren't restricted. pub fn restricted_licence(&self) -> bool { - let fs_type = self.fs_type(); + let fs_type = self.fs_type().unwrap_or(0); fs_type & 0x0002 != 0 && fs_type & 0x000C == 0 } /// Does the font's licence allow embedding bitmaps only? OS/2 `fsType` bit 9: the outlines /// may not be passed on, so the font file can't be packaged whole. pub fn bitmap_only(&self) -> bool { - self.fs_type() & 0x0200 != 0 + self.fs_type().is_some_and(|t| t & 0x0200 != 0) } - /// The font's OS/2 `fsType` (embedding licence bits), 0 (installable) when the font has no - /// OS/2 table. - pub fn fs_type(&self) -> u16 { + /// The font's OS/2 `fsType` (embedding licence bits), None when the font has no OS/2 table it + /// can read: the font doesn't say, which isn't the same as 0 (installable). + pub fn fs_type(&self) -> Option { use skrifa::raw::TableProvider; - self.skrifa().and_then(|f| f.os2().ok()).map_or(0, |t| t.fs_type()) + self.skrifa().and_then(|f| f.os2().ok()).map(|t| t.fs_type()) } }