Skip to content

Commit 0001693

Browse files
codexVelli20
authored andcommitted
pdf-font: centralize simple font dictionary parsing
Move descriptor flag and encoding parsing onto FontFlags and Encoding. Fold TrueType font program loading into read_font_file so embedded and fallback programs follow one path. Co-authored-by: Codex <codex@openai.com>
1 parent 50cab29 commit 0001693

7 files changed

Lines changed: 68 additions & 139 deletions

File tree

crates/pdf-font/src/encoding.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use std::{borrow::Cow, convert::TryFrom};
22

3-
use pdf_object::{object_resolver::ObjectResolver, object_variant::ObjectVariant};
3+
use pdf_object::{
4+
dictionary::Dictionary, object_resolver::ObjectResolver, object_variant::ObjectVariant,
5+
};
46

57
use crate::error::FontError;
68

@@ -127,8 +129,24 @@ impl Encoding {
127129
}
128130

129131
impl Encoding {
132+
/// Parse the optional `/Encoding` entry from a font dictionary.
130133
pub fn from_dictionary(
131-
dictionary: &pdf_object::dictionary::Dictionary,
134+
dictionary: &Dictionary,
135+
objects: &dyn ObjectResolver,
136+
) -> Result<Option<Self>, FontError> {
137+
dictionary
138+
.get("Encoding")
139+
.map(|value| match objects.resolve_object(value)? {
140+
ObjectVariant::Dictionary(encoding_dictionary) => {
141+
Self::from_encoding_dictionary(encoding_dictionary, objects)
142+
}
143+
other => Self::from_base_encoding(FontEncoding::from(other.try_str(objects)?)),
144+
})
145+
.transpose()
146+
}
147+
148+
fn from_encoding_dictionary(
149+
dictionary: &Dictionary,
132150
objects: &dyn ObjectResolver,
133151
) -> Result<Self, FontError> {
134152
let mut encoding = match dictionary.get("BaseEncoding") {

crates/pdf-font/src/fallback.rs

Lines changed: 8 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
11
use pdf_cmap::ToUnicodeCMap;
2-
use pdf_object::{
3-
dictionary::Dictionary, object_lookup::ObjectLookupExt, object_resolver::ObjectResolver,
4-
object_variant::ObjectVariant,
5-
};
2+
use pdf_object::{dictionary::Dictionary, object_resolver::ObjectResolver};
63

74
use crate::{
8-
cid_system_info::CidOrdering,
9-
encoding::{Encoding, FontEncoding},
10-
error::FontError,
11-
flags::FontFlags,
12-
simple_font_glyph_map::SimpleFontGlyphWidthsMap,
13-
standard14::Standard14Font,
5+
cid_system_info::CidOrdering, encoding::Encoding, error::FontError, flags::FontFlags,
6+
simple_font_glyph_map::SimpleFontGlyphWidthsMap, standard14::Standard14Font,
147
true_type_font::TrueTypeFont,
158
};
169

@@ -26,7 +19,7 @@ impl FallbackFontProgram {
2619
dictionary: &Dictionary,
2720
objects: &dyn ObjectResolver,
2821
) -> Result<Self, FontError> {
29-
let flags = descriptor_flags(dictionary, objects)?;
22+
let flags = FontFlags::from_dictionary(dictionary, objects)?;
3023
let standard14 = Standard14Font::from_dictionary(dictionary, objects, flags);
3124
let is_cjk = is_cjk_cid_font(dictionary, objects)?;
3225

@@ -51,7 +44,9 @@ pub(crate) fn fallback_true_type_from_dictionary(
5144
) -> Result<TrueTypeFont, FontError> {
5245
let fallback = FallbackFontProgram::from_dictionary(dictionary, objects)?;
5346
let widths = SimpleFontGlyphWidthsMap::from_dictionary(dictionary, objects)?;
54-
let encoding = simple_font_encoding(dictionary, objects);
47+
let encoding = Encoding::from_dictionary(dictionary, objects)
48+
.ok()
49+
.flatten();
5550
let to_unicode = to_unicode_cmap(dictionary, objects)?;
5651

5752
Ok(TrueTypeFont {
@@ -73,7 +68,7 @@ pub(crate) fn fallback_true_type_from_dictionary_best_effort(
7368
dictionary: &Dictionary,
7469
objects: &dyn ObjectResolver,
7570
) -> TrueTypeFont {
76-
let flags = descriptor_flags(dictionary, objects).unwrap_or_default();
71+
let flags = FontFlags::from_dictionary(dictionary, objects).unwrap_or_default();
7772
let is_cjk = is_cjk_cid_font(dictionary, objects).unwrap_or(false);
7873
let standard14 = Standard14Font::from_dictionary(dictionary, objects, flags);
7974
let fallback = fallback_program(flags, standard14, is_cjk);
@@ -106,55 +101,6 @@ fn fallback_program(
106101
}
107102
}
108103

109-
/// Read font descriptor flags from a font dictionary.
110-
///
111-
/// # Paramaters
112-
///
113-
/// - `dictionary`: The PDF font dictionary that may contain `/FontDescriptor`.
114-
/// - `objects`: The resolver used to dereference indirect PDF objects.
115-
///
116-
/// # Returns
117-
///
118-
/// The parsed descriptor flags, or empty flags when the descriptor or `/Flags`
119-
/// entry is absent.
120-
pub(crate) fn descriptor_flags(
121-
dictionary: &Dictionary,
122-
objects: &dyn ObjectResolver,
123-
) -> Result<FontFlags, FontError> {
124-
let Some(descriptor) = dictionary.optional_dictionary("FontDescriptor", objects)? else {
125-
return Ok(FontFlags::empty());
126-
};
127-
128-
Ok(descriptor
129-
.get("Flags")
130-
.and_then(|obj| obj.try_number::<u32>(objects).ok())
131-
.map(FontFlags::from_bits_truncate)
132-
.unwrap_or_default())
133-
}
134-
135-
/// Parse a simple font encoding from a font dictionary.
136-
///
137-
/// # Paramaters
138-
///
139-
/// - `dictionary`: The PDF font dictionary that may contain `/Encoding`.
140-
/// - `objects`: The resolver used to dereference indirect PDF objects.
141-
///
142-
/// # Returns
143-
///
144-
/// The parsed encoding when `/Encoding` is present and supported.
145-
fn simple_font_encoding(dictionary: &Dictionary, objects: &dyn ObjectResolver) -> Option<Encoding> {
146-
dictionary.get("Encoding").and_then(|enc_obj| {
147-
let resolved = objects.resolve_object(enc_obj).ok()?;
148-
match resolved {
149-
ObjectVariant::Dictionary(d) => Encoding::from_dictionary(d, objects).ok(),
150-
_ => {
151-
let base = FontEncoding::from(resolved.try_str(objects).ok()?);
152-
Encoding::from_base_encoding(base).ok()
153-
}
154-
}
155-
})
156-
}
157-
158104
/// Parse an optional ToUnicode CMap from a font dictionary.
159105
///
160106
/// # Paramaters

crates/pdf-font/src/flags.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
use bitflags::bitflags;
2+
use pdf_object::{
3+
dictionary::Dictionary, object_lookup::ObjectLookupExt, object_resolver::ObjectResolver,
4+
};
5+
6+
use crate::error::FontError;
27

38
bitflags! {
49
/// Font descriptor flags as defined in ISO 32000-1, Table 123.
@@ -20,3 +25,21 @@ bitflags! {
2025
const FORCE_BOLD = 1 << 18; // spec bit 19
2126
}
2227
}
28+
29+
impl FontFlags {
30+
/// Read font descriptor flags from a font dictionary.
31+
pub(crate) fn from_dictionary(
32+
dictionary: &Dictionary,
33+
objects: &dyn ObjectResolver,
34+
) -> Result<Self, FontError> {
35+
let Some(descriptor) = dictionary.optional_dictionary("FontDescriptor", objects)? else {
36+
return Ok(Self::empty());
37+
};
38+
39+
Ok(descriptor
40+
.get("Flags")
41+
.and_then(|value| value.try_number::<u32>(objects).ok())
42+
.map(Self::from_bits_truncate)
43+
.unwrap_or_default())
44+
}
45+
}

crates/pdf-font/src/true_type_font.rs

Lines changed: 12 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,11 @@ use std::collections::HashMap;
33
use pdf_cmap::ToUnicodeCMap;
44
use pdf_object::{
55
dictionary::Dictionary, object_lookup::ObjectLookupExt, object_resolver::ObjectResolver,
6-
object_variant::ObjectVariant,
76
};
87

98
use crate::{
10-
encoding::{Encoding, FontEncoding},
11-
error::FontError,
12-
fallback::FallbackFontProgram,
13-
flags::FontFlags,
14-
font_data::FontData,
15-
simple_font_glyph_map::SimpleFontGlyphWidthsMap,
9+
encoding::Encoding, error::FontError, fallback::FallbackFontProgram, flags::FontFlags,
10+
font_data::FontData, simple_font_glyph_map::SimpleFontGlyphWidthsMap,
1611
standard14::Standard14Font,
1712
};
1813

@@ -37,8 +32,8 @@ pub struct TrueTypeFont {
3732
pub flags: FontFlags,
3833
}
3934

40-
struct TrueTypeFontProgram {
41-
font_file: FontData,
35+
pub(crate) struct TrueTypeFontProgram {
36+
pub(crate) font_file: FontData,
4237
standard14: Option<Standard14Font>,
4338
flags: FontFlags,
4439
}
@@ -66,26 +61,17 @@ impl TrueTypeFont {
6661
dictionary: &Dictionary,
6762
objects: &dyn ObjectResolver,
6863
) -> Result<Self, FontError> {
69-
let program = Self::read_font_program(dictionary, objects)?;
64+
let program = Self::read_font_file(dictionary, objects)?;
7065
// Read the `/Widths` entry.
7166
let widths = SimpleFontGlyphWidthsMap::from_dictionary(dictionary, objects)?;
7267

7368
// Read optional `/Encoding` entry — either a name (base encoding) or a
7469
// dictionary (with optional BaseEncoding + Differences). Errors are
7570
// treated as absent encoding rather than propagated, since TrueType fonts
7671
// often omit or mis-specify this entry.
77-
let encoding: Option<Encoding> = dictionary
78-
.get("Encoding")
79-
.and_then(|enc_obj| {
80-
let resolved = objects.resolve_object(enc_obj).ok()?;
81-
match resolved {
82-
ObjectVariant::Dictionary(d) => Encoding::from_dictionary(d, objects).ok(),
83-
_ => {
84-
let base = FontEncoding::from(resolved.try_str(objects).ok()?);
85-
Encoding::from_base_encoding(base).ok()
86-
}
87-
}
88-
})
72+
let encoding = Encoding::from_dictionary(dictionary, objects)
73+
.ok()
74+
.flatten()
8975
.or_else(|| Self::default_simple_encoding(program.flags, program.standard14));
9076

9177
// Parse optional ToUnicode CMap stream.
@@ -156,28 +142,15 @@ impl TrueTypeFont {
156142
///
157143
/// # Returns
158144
///
159-
/// Returns the font file bytes as [`FontData`] or a [`FontError`] if
160-
/// reading or decompressing the font stream fails or if the font dictionary or its
161-
/// `/FontDescriptor` entry is invalid.
145+
/// Returns the resolved font program and its fallback metadata, or a
146+
/// [`FontError`] if reading the font dictionary or stream fails.
162147
pub(crate) fn read_font_file(
163148
dictionary: &Dictionary,
164149
objects: &dyn ObjectResolver,
165-
) -> Result<(FontData, FontFlags), FontError> {
166-
let program = Self::read_font_program(dictionary, objects)?;
167-
Ok((program.font_file, program.flags))
168-
}
169-
170-
fn read_font_program(
171-
dictionary: &Dictionary,
172-
objects: &dyn ObjectResolver,
173150
) -> Result<TrueTypeFontProgram, FontError> {
174-
if let Some(descriptor) = dictionary.optional_dictionary("FontDescriptor", objects)? {
175-
let flags = descriptor
176-
.get("Flags")
177-
.and_then(|obj| obj.try_number::<u32>(objects).ok())
178-
.map(FontFlags::from_bits_truncate)
179-
.unwrap_or_default();
151+
let flags = FontFlags::from_dictionary(dictionary, objects)?;
180152

153+
if let Some(descriptor) = dictionary.optional_dictionary("FontDescriptor", objects)? {
181154
if let Some(stream) = descriptor.optional_stream("FontFile2", objects)? {
182155
return Ok(TrueTypeFontProgram {
183156
font_file: FontData::shared(stream.shared_data()),

crates/pdf-font/src/type0_font.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ fn read_type0_font_program(
253253
match subtype {
254254
CidFontSubType::Type0 => read_cid_font_type0_program(dictionary, objects),
255255
CidFontSubType::Type2 => Ok(Type0FontProgram {
256-
font_file: TrueTypeFont::read_font_file(dictionary, objects)?.0,
256+
font_file: TrueTypeFont::read_font_file(dictionary, objects)?.font_file,
257257
program_format: Type0FontProgramFormat::TrueType {
258258
cid_to_unicode: false,
259259
},

crates/pdf-font/src/type1_font.rs

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,10 @@ use std::{collections::HashMap, sync::Arc};
33
use pdf_cmap::ToUnicodeCMap;
44
use pdf_object::{
55
dictionary::Dictionary, object_lookup::ObjectLookupExt, object_resolver::ObjectResolver,
6-
object_variant::ObjectVariant,
76
};
87

98
use crate::{
10-
cff_builder::build_cff_font,
11-
encoding::{Encoding, FontEncoding},
12-
error::FontError,
13-
font_data::FontData,
9+
cff_builder::build_cff_font, encoding::Encoding, error::FontError, font_data::FontData,
1410
simple_font_glyph_map::SimpleFontGlyphWidthsMap,
1511
};
1612

@@ -48,21 +44,7 @@ impl Type1Font {
4844
// Read the `/Widths` entry.
4945
let widths = SimpleFontGlyphWidthsMap::from_dictionary(dictionary, objects)?;
5046

51-
// Read optional `/Encoding` entry. This is either a name or a dictionary.
52-
let encoding = if let Some(enc_obj) = dictionary.get("Encoding") {
53-
let enc_obj = objects.resolve_object(enc_obj)?;
54-
match enc_obj {
55-
ObjectVariant::Dictionary(enc_dictionary) => {
56-
Encoding::from_dictionary(enc_dictionary, objects)?
57-
}
58-
_ => {
59-
let base = FontEncoding::from(enc_obj.try_str(objects)?);
60-
Encoding::from_base_encoding(base)?
61-
}
62-
}
63-
} else {
64-
Encoding::default()
65-
};
47+
let encoding = Encoding::from_dictionary(dictionary, objects)?.unwrap_or_default();
6648

6749
// Parse optional ToUnicode CMap stream.
6850
let to_unicode = dictionary

crates/pdf-font/src/type3_font.rs

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,9 @@ use pdf_content_stream::{ContentStream, ContentStreamIdAllocator};
55
use pdf_graphics::{rect::Rect, transform::Transform};
66
use pdf_object::{
77
dictionary::Dictionary, object_lookup::ObjectLookupExt, object_resolver::ObjectResolver,
8-
object_variant::ObjectVariant,
98
};
109

11-
use crate::{
12-
encoding::{Encoding, FontEncoding},
13-
error::FontError,
14-
};
10+
use crate::{encoding::Encoding, error::FontError};
1511

1612
/// Represents a Type 3 font in a PDF document.
1713
///
@@ -52,16 +48,7 @@ impl Type3Font {
5248
bottom,
5349
};
5450

55-
// Read optional `/Encoding` entry. This is either a name or a dictionary.
56-
let encoding = dictionary
57-
.get("Encoding")
58-
.map(|enc_obj| match objects.resolve_object(enc_obj)? {
59-
ObjectVariant::Dictionary(enc_dictionary) => {
60-
Encoding::from_dictionary(enc_dictionary, objects)
61-
}
62-
other => Encoding::from_base_encoding(FontEncoding::from(other.try_str(objects)?)),
63-
})
64-
.transpose()?;
51+
let encoding = Encoding::from_dictionary(dictionary, objects)?;
6552

6653
let char_proc_dictionary = dictionary.required_dictionary("CharProcs", objects)?;
6754

0 commit comments

Comments
 (0)