fix(edit): editor font enumeration reads a face name without building font metrics

This commit is contained in:
Dylan M. Taylor
2026-11-04 03:44:08 +00:00
parent 9ce6d89f46
commit 32376e8d1c
3 changed files with 27 additions and 12 deletions
+2 -2
View File
@@ -2,7 +2,7 @@ use super::blocks::{BlockStyle, TextBlock};
use super::fallback::MAX_TYPE3_GLYPHS;
use super::lines::{page_dict, text_lines};
use super::rewrite::rewrite_block_from;
use super::{DecodeBudget, Document, EditError, Metrics, Object, TextLine, TextSession};
use super::{DecodeBudget, Document, EditError, Object, TextLine, TextSession};
/// An embedded sfnt source face, read only for the inline editor. Bytes stay in memory and
/// are never installed, copied into assets, or used as a font for another document.
@@ -30,7 +30,7 @@ pub fn text_editor_fonts(doc: &Document, page: usize) -> Result<Vec<TextEditorFo
budget -= bytes.len();
result.push(TextEditorFont {
resource: String::from_utf8_lossy(name).into_owned(),
face: Metrics::from_dict(doc, &font).base_font.rsplit('+').next().unwrap_or_default().to_owned(),
face: pdfcraft_fonts::pdf::base_font_name(doc, &font).rsplit('+').next().unwrap_or_default().to_owned(),
bytes,
});
}
+15 -10
View File
@@ -559,6 +559,20 @@ fn number(t: &Tok) -> Option<u32> {
}
}
/// The font's `/BaseFont` (a Type 3 font's descriptor `/FontName` when it has none), read without
/// building its metrics, so it decodes no CMap.
pub fn base_font_name(doc: &Document, font: &Dict) -> String {
font.name(b"BaseFont")
.map(|name| String::from_utf8_lossy(name).into_owned())
.or_else(|| {
(font.name(b"Subtype") == Some(b"Type3"))
.then(|| dict(doc, font.get(b"FontDescriptor")))
.flatten()
.and_then(|d| d.name(b"FontName").map(|name| String::from_utf8_lossy(name).into_owned()))
})
.unwrap_or_default()
}
/// Codespace ranges and CID mappings from an embedded CMap stream, at most
/// [`MAX_CMAP_RANGES`] of each (every glyph searches them).
#[allow(clippy::type_complexity)]
@@ -633,16 +647,7 @@ impl Metrics {
pub fn from_dict(doc: &Document, font: &Dict) -> Self {
let mut m = Self::read_metrics(doc, font);
let base = font
.name(b"BaseFont")
.map(|name| String::from_utf8_lossy(name).into_owned())
.or_else(|| {
(font.name(b"Subtype") == Some(b"Type3"))
.then(|| dict(doc, font.get(b"FontDescriptor")))
.flatten()
.and_then(|d| d.name(b"FontName").map(|name| String::from_utf8_lossy(name).into_owned()))
})
.unwrap_or_default();
let base = base_font_name(doc, font);
m.subset = base.len() > 7 && base.as_bytes()[6] == b'+' && base[..6].bytes().all(|b| b.is_ascii_uppercase());
let (bold, italic) = style_from_name(&base);
m.bold |= bold;
+10
View File
@@ -795,3 +795,13 @@ fn low_codes_resolve_to_the_covering_range() {
assert_eq!(covered, [5, 6, 7, 100, 101]);
assert_eq!((u.covering(0xFFFF).is_some(), u.covering(0x1_0001).is_some(), u.covering(0x1_0002).is_none()), (true, true, true));
}
#[test]
fn base_font_name_matches_the_metrics_name() {
let doc = Document::new_empty();
let mut f = Dict::new();
f.set(b"BaseFont".to_vec(), Object::name("ABCDEF+Body"));
assert_eq!(base_font_name(&doc, &f), "ABCDEF+Body");
assert_eq!(base_font_name(&doc, &f), Metrics::from_dict(&doc, &f).base_font);
assert_eq!(base_font_name(&doc, &Dict::new()), "");
}