diff --git a/crates/pdf-font/src/encoding.rs b/crates/pdf-font/src/encoding.rs index add15b4..214fb2e 100644 --- a/crates/pdf-font/src/encoding.rs +++ b/crates/pdf-font/src/encoding.rs @@ -1,6 +1,8 @@ use std::{borrow::Cow, convert::TryFrom}; -use pdf_object::{object_resolver::ObjectResolver, object_variant::ObjectVariant}; +use pdf_object::{ + dictionary::Dictionary, object_resolver::ObjectResolver, object_variant::ObjectVariant, +}; use crate::error::FontError; @@ -127,8 +129,24 @@ impl Encoding { } impl Encoding { + /// Parse the optional `/Encoding` entry from a font dictionary. pub fn from_dictionary( - dictionary: &pdf_object::dictionary::Dictionary, + dictionary: &Dictionary, + objects: &dyn ObjectResolver, + ) -> Result, FontError> { + dictionary + .get("Encoding") + .map(|value| match objects.resolve_object(value)? { + ObjectVariant::Dictionary(encoding_dictionary) => { + Self::from_encoding_dictionary(encoding_dictionary, objects) + } + other => Self::from_base_encoding(FontEncoding::from(other.try_str(objects)?)), + }) + .transpose() + } + + fn from_encoding_dictionary( + dictionary: &Dictionary, objects: &dyn ObjectResolver, ) -> Result { let mut encoding = match dictionary.get("BaseEncoding") { diff --git a/crates/pdf-font/src/fallback.rs b/crates/pdf-font/src/fallback.rs index dd328f8..1651471 100644 --- a/crates/pdf-font/src/fallback.rs +++ b/crates/pdf-font/src/fallback.rs @@ -1,16 +1,9 @@ use pdf_cmap::ToUnicodeCMap; -use pdf_object::{ - dictionary::Dictionary, object_lookup::ObjectLookupExt, object_resolver::ObjectResolver, - object_variant::ObjectVariant, -}; +use pdf_object::{dictionary::Dictionary, object_resolver::ObjectResolver}; use crate::{ - cid_system_info::CidOrdering, - encoding::{Encoding, FontEncoding}, - error::FontError, - flags::FontFlags, - simple_font_glyph_map::SimpleFontGlyphWidthsMap, - standard14::Standard14Font, + cid_system_info::CidOrdering, encoding::Encoding, error::FontError, flags::FontFlags, + simple_font_glyph_map::SimpleFontGlyphWidthsMap, standard14::Standard14Font, true_type_font::TrueTypeFont, }; @@ -26,7 +19,7 @@ impl FallbackFontProgram { dictionary: &Dictionary, objects: &dyn ObjectResolver, ) -> Result { - let flags = descriptor_flags(dictionary, objects)?; + let flags = FontFlags::from_dictionary(dictionary, objects)?; let standard14 = Standard14Font::from_dictionary(dictionary, objects, flags); let is_cjk = is_cjk_cid_font(dictionary, objects)?; @@ -51,7 +44,9 @@ pub(crate) fn fallback_true_type_from_dictionary( ) -> Result { let fallback = FallbackFontProgram::from_dictionary(dictionary, objects)?; let widths = SimpleFontGlyphWidthsMap::from_dictionary(dictionary, objects)?; - let encoding = simple_font_encoding(dictionary, objects); + let encoding = Encoding::from_dictionary(dictionary, objects) + .ok() + .flatten(); let to_unicode = to_unicode_cmap(dictionary, objects)?; Ok(TrueTypeFont { @@ -73,7 +68,7 @@ pub(crate) fn fallback_true_type_from_dictionary_best_effort( dictionary: &Dictionary, objects: &dyn ObjectResolver, ) -> TrueTypeFont { - let flags = descriptor_flags(dictionary, objects).unwrap_or_default(); + let flags = FontFlags::from_dictionary(dictionary, objects).unwrap_or_default(); let is_cjk = is_cjk_cid_font(dictionary, objects).unwrap_or(false); let standard14 = Standard14Font::from_dictionary(dictionary, objects, flags); let fallback = fallback_program(flags, standard14, is_cjk); @@ -106,55 +101,6 @@ fn fallback_program( } } -/// Read font descriptor flags from a font dictionary. -/// -/// # Paramaters -/// -/// - `dictionary`: The PDF font dictionary that may contain `/FontDescriptor`. -/// - `objects`: The resolver used to dereference indirect PDF objects. -/// -/// # Returns -/// -/// The parsed descriptor flags, or empty flags when the descriptor or `/Flags` -/// entry is absent. -pub(crate) fn descriptor_flags( - dictionary: &Dictionary, - objects: &dyn ObjectResolver, -) -> Result { - let Some(descriptor) = dictionary.optional_dictionary("FontDescriptor", objects)? else { - return Ok(FontFlags::empty()); - }; - - Ok(descriptor - .get("Flags") - .and_then(|obj| obj.try_number::(objects).ok()) - .map(FontFlags::from_bits_truncate) - .unwrap_or_default()) -} - -/// Parse a simple font encoding from a font dictionary. -/// -/// # Paramaters -/// -/// - `dictionary`: The PDF font dictionary that may contain `/Encoding`. -/// - `objects`: The resolver used to dereference indirect PDF objects. -/// -/// # Returns -/// -/// The parsed encoding when `/Encoding` is present and supported. -fn simple_font_encoding(dictionary: &Dictionary, objects: &dyn ObjectResolver) -> Option { - dictionary.get("Encoding").and_then(|enc_obj| { - let resolved = objects.resolve_object(enc_obj).ok()?; - match resolved { - ObjectVariant::Dictionary(d) => Encoding::from_dictionary(d, objects).ok(), - _ => { - let base = FontEncoding::from(resolved.try_str(objects).ok()?); - Encoding::from_base_encoding(base).ok() - } - } - }) -} - /// Parse an optional ToUnicode CMap from a font dictionary. /// /// # Paramaters diff --git a/crates/pdf-font/src/flags.rs b/crates/pdf-font/src/flags.rs index b08fb09..638fe74 100644 --- a/crates/pdf-font/src/flags.rs +++ b/crates/pdf-font/src/flags.rs @@ -1,4 +1,9 @@ use bitflags::bitflags; +use pdf_object::{ + dictionary::Dictionary, object_lookup::ObjectLookupExt, object_resolver::ObjectResolver, +}; + +use crate::error::FontError; bitflags! { /// Font descriptor flags as defined in ISO 32000-1, Table 123. @@ -20,3 +25,21 @@ bitflags! { const FORCE_BOLD = 1 << 18; // spec bit 19 } } + +impl FontFlags { + /// Read font descriptor flags from a font dictionary. + pub(crate) fn from_dictionary( + dictionary: &Dictionary, + objects: &dyn ObjectResolver, + ) -> Result { + let Some(descriptor) = dictionary.optional_dictionary("FontDescriptor", objects)? else { + return Ok(Self::empty()); + }; + + Ok(descriptor + .get("Flags") + .and_then(|value| value.try_number::(objects).ok()) + .map(Self::from_bits_truncate) + .unwrap_or_default()) + } +} diff --git a/crates/pdf-font/src/true_type_font.rs b/crates/pdf-font/src/true_type_font.rs index e4ab911..5d7fe9d 100644 --- a/crates/pdf-font/src/true_type_font.rs +++ b/crates/pdf-font/src/true_type_font.rs @@ -3,16 +3,11 @@ use std::collections::HashMap; use pdf_cmap::ToUnicodeCMap; use pdf_object::{ dictionary::Dictionary, object_lookup::ObjectLookupExt, object_resolver::ObjectResolver, - object_variant::ObjectVariant, }; use crate::{ - encoding::{Encoding, FontEncoding}, - error::FontError, - fallback::FallbackFontProgram, - flags::FontFlags, - font_data::FontData, - simple_font_glyph_map::SimpleFontGlyphWidthsMap, + encoding::Encoding, error::FontError, fallback::FallbackFontProgram, flags::FontFlags, + font_data::FontData, simple_font_glyph_map::SimpleFontGlyphWidthsMap, standard14::Standard14Font, }; @@ -37,8 +32,8 @@ pub struct TrueTypeFont { pub flags: FontFlags, } -struct TrueTypeFontProgram { - font_file: FontData, +pub(crate) struct TrueTypeFontProgram { + pub(crate) font_file: FontData, standard14: Option, flags: FontFlags, } @@ -66,7 +61,7 @@ impl TrueTypeFont { dictionary: &Dictionary, objects: &dyn ObjectResolver, ) -> Result { - let program = Self::read_font_program(dictionary, objects)?; + let program = Self::read_font_file(dictionary, objects)?; // Read the `/Widths` entry. let widths = SimpleFontGlyphWidthsMap::from_dictionary(dictionary, objects)?; @@ -74,18 +69,9 @@ impl TrueTypeFont { // dictionary (with optional BaseEncoding + Differences). Errors are // treated as absent encoding rather than propagated, since TrueType fonts // often omit or mis-specify this entry. - let encoding: Option = dictionary - .get("Encoding") - .and_then(|enc_obj| { - let resolved = objects.resolve_object(enc_obj).ok()?; - match resolved { - ObjectVariant::Dictionary(d) => Encoding::from_dictionary(d, objects).ok(), - _ => { - let base = FontEncoding::from(resolved.try_str(objects).ok()?); - Encoding::from_base_encoding(base).ok() - } - } - }) + let encoding = Encoding::from_dictionary(dictionary, objects) + .ok() + .flatten() .or_else(|| Self::default_simple_encoding(program.flags, program.standard14)); // Parse optional ToUnicode CMap stream. @@ -156,28 +142,15 @@ impl TrueTypeFont { /// /// # Returns /// - /// Returns the font file bytes as [`FontData`] or a [`FontError`] if - /// reading or decompressing the font stream fails or if the font dictionary or its - /// `/FontDescriptor` entry is invalid. + /// Returns the resolved font program and its fallback metadata, or a + /// [`FontError`] if reading the font dictionary or stream fails. pub(crate) fn read_font_file( dictionary: &Dictionary, objects: &dyn ObjectResolver, - ) -> Result<(FontData, FontFlags), FontError> { - let program = Self::read_font_program(dictionary, objects)?; - Ok((program.font_file, program.flags)) - } - - fn read_font_program( - dictionary: &Dictionary, - objects: &dyn ObjectResolver, ) -> Result { - if let Some(descriptor) = dictionary.optional_dictionary("FontDescriptor", objects)? { - let flags = descriptor - .get("Flags") - .and_then(|obj| obj.try_number::(objects).ok()) - .map(FontFlags::from_bits_truncate) - .unwrap_or_default(); + let flags = FontFlags::from_dictionary(dictionary, objects)?; + if let Some(descriptor) = dictionary.optional_dictionary("FontDescriptor", objects)? { if let Some(stream) = descriptor.optional_stream("FontFile2", objects)? { return Ok(TrueTypeFontProgram { font_file: FontData::shared(stream.shared_data()), diff --git a/crates/pdf-font/src/type0_font.rs b/crates/pdf-font/src/type0_font.rs index b6e5805..a4838d5 100644 --- a/crates/pdf-font/src/type0_font.rs +++ b/crates/pdf-font/src/type0_font.rs @@ -253,7 +253,7 @@ fn read_type0_font_program( match subtype { CidFontSubType::Type0 => read_cid_font_type0_program(dictionary, objects), CidFontSubType::Type2 => Ok(Type0FontProgram { - font_file: TrueTypeFont::read_font_file(dictionary, objects)?.0, + font_file: TrueTypeFont::read_font_file(dictionary, objects)?.font_file, program_format: Type0FontProgramFormat::TrueType { cid_to_unicode: false, }, diff --git a/crates/pdf-font/src/type1_font.rs b/crates/pdf-font/src/type1_font.rs index f8b0e70..364778f 100644 --- a/crates/pdf-font/src/type1_font.rs +++ b/crates/pdf-font/src/type1_font.rs @@ -3,14 +3,10 @@ use std::{collections::HashMap, sync::Arc}; use pdf_cmap::ToUnicodeCMap; use pdf_object::{ dictionary::Dictionary, object_lookup::ObjectLookupExt, object_resolver::ObjectResolver, - object_variant::ObjectVariant, }; use crate::{ - cff_builder::build_cff_font, - encoding::{Encoding, FontEncoding}, - error::FontError, - font_data::FontData, + cff_builder::build_cff_font, encoding::Encoding, error::FontError, font_data::FontData, simple_font_glyph_map::SimpleFontGlyphWidthsMap, }; @@ -48,21 +44,7 @@ impl Type1Font { // Read the `/Widths` entry. let widths = SimpleFontGlyphWidthsMap::from_dictionary(dictionary, objects)?; - // Read optional `/Encoding` entry. This is either a name or a dictionary. - let encoding = if let Some(enc_obj) = dictionary.get("Encoding") { - let enc_obj = objects.resolve_object(enc_obj)?; - match enc_obj { - ObjectVariant::Dictionary(enc_dictionary) => { - Encoding::from_dictionary(enc_dictionary, objects)? - } - _ => { - let base = FontEncoding::from(enc_obj.try_str(objects)?); - Encoding::from_base_encoding(base)? - } - } - } else { - Encoding::default() - }; + let encoding = Encoding::from_dictionary(dictionary, objects)?.unwrap_or_default(); // Parse optional ToUnicode CMap stream. let to_unicode = dictionary diff --git a/crates/pdf-font/src/type3_font.rs b/crates/pdf-font/src/type3_font.rs index bf68805..82f082c 100644 --- a/crates/pdf-font/src/type3_font.rs +++ b/crates/pdf-font/src/type3_font.rs @@ -5,13 +5,9 @@ use pdf_content_stream::{ContentStream, ContentStreamIdAllocator}; use pdf_graphics::{rect::Rect, transform::Transform}; use pdf_object::{ dictionary::Dictionary, object_lookup::ObjectLookupExt, object_resolver::ObjectResolver, - object_variant::ObjectVariant, }; -use crate::{ - encoding::{Encoding, FontEncoding}, - error::FontError, -}; +use crate::{encoding::Encoding, error::FontError}; /// Represents a Type 3 font in a PDF document. /// @@ -52,16 +48,7 @@ impl Type3Font { bottom, }; - // Read optional `/Encoding` entry. This is either a name or a dictionary. - let encoding = dictionary - .get("Encoding") - .map(|enc_obj| match objects.resolve_object(enc_obj)? { - ObjectVariant::Dictionary(enc_dictionary) => { - Encoding::from_dictionary(enc_dictionary, objects) - } - other => Encoding::from_base_encoding(FontEncoding::from(other.try_str(objects)?)), - }) - .transpose()?; + let encoding = Encoding::from_dictionary(dictionary, objects)?; let char_proc_dictionary = dictionary.required_dictionary("CharProcs", objects)?;