diff --git a/crates/pdf-font/src/lib.rs b/crates/pdf-font/src/lib.rs index c5cb5e6d..2997d757 100644 --- a/crates/pdf-font/src/lib.rs +++ b/crates/pdf-font/src/lib.rs @@ -38,7 +38,7 @@ pub use font::{ }; pub use font_registry::FontRegistry; pub use pdf::{ - CharProcedures, CidFontKind, CidFontSpec, CidSystemInfo, CidToGidMap, + CharProcedures, CidFontKind, CidFontSpec, CidSystemInfo, CidToGidMap, CidToUnicode, PDF_GLYPH_SPACE_UNITS_PER_EM, PdfCode, PdfFontDescriptor, PdfMetrics, SimpleEncoding, SimpleFontSpec, ToUnicodeMap, Type0FontSpec, Type3FontSpec, }; diff --git a/crates/pdf-font/src/pdf.rs b/crates/pdf-font/src/pdf.rs index 8e80b141..d1522ffc 100644 --- a/crates/pdf-font/src/pdf.rs +++ b/crates/pdf-font/src/pdf.rs @@ -11,7 +11,9 @@ use crate::glyph_widths_map::GlyphWidthsMap; pub use crate::pdf_font_spec::PdfFontSpec; pub use crate::simple_font_spec::SimpleFontSpec; pub use crate::standard14::Standard14Font; -pub use crate::type0_font_spec::{CidFontKind, CidFontSpec, CidToGidMap, Type0FontSpec}; +pub use crate::type0_font_spec::{ + CidFontKind, CidFontSpec, CidToGidMap, CidToUnicode, Type0FontSpec, +}; pub use crate::type3_font_spec::{CharProcedures, Type3FontSpec}; pub use pdf_cmap::{PdfCode, ToUnicodeMap}; diff --git a/crates/pdf-font/src/pdf_font_descriptor.rs b/crates/pdf-font/src/pdf_font_descriptor.rs index 417e86b3..049be1e7 100644 --- a/crates/pdf-font/src/pdf_font_descriptor.rs +++ b/crates/pdf-font/src/pdf_font_descriptor.rs @@ -1,9 +1,12 @@ //! Best-effort font descriptor metadata and CID collection information. -use crate::pdf::{CidSystemInfo, PdfFontDescriptor}; +use crate::{ + pdf::{CidSystemInfo, PdfFontDescriptor}, + type0_font_spec::CidToUnicode, +}; use pdf_cmap::predefined::PredefinedCMap; use pdf_object_reader::{FromPdfObject, ObjectAccess, ObjectContext, ReadResult}; -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; impl FromPdfObject for PdfFontDescriptor { /// Reads a FontDescriptor dictionary, not the enclosing font dictionary, tolerating @@ -44,8 +47,8 @@ impl FromPdfObject for CidSystemInfo { } impl CidSystemInfo { - /// Finds the existing collection-based Unicode fallback when the ordering is known. - pub(crate) fn cid_to_unicode_map(&self) -> Option>> { + /// Finds the collection-based Unicode fallback when the ordering is known. + pub(crate) fn cid_to_unicode_map(&self) -> Option { let cmap_name: &[u8] = match self.ordering.as_ref() { b"Japan1" => b"UniJIS-UCS2-HW-H", b"GB1" => b"UniGB-UCS2-H", @@ -54,6 +57,8 @@ impl CidSystemInfo { _ => return None, }; let cmap = PredefinedCMap::from_name(cmap_name).ok()??; - Some(Arc::new(cmap.cid_to_unicode_map())) + Some(CidToUnicode::Collection(Arc::new( + cmap.cid_to_unicode_map(), + ))) } } diff --git a/crates/pdf-font/src/pdf_font_spec.rs b/crates/pdf-font/src/pdf_font_spec.rs index 5278bd5c..7cc0b0c6 100644 --- a/crates/pdf-font/src/pdf_font_spec.rs +++ b/crates/pdf-font/src/pdf_font_spec.rs @@ -198,6 +198,9 @@ impl PdfFontSpec { PdfFontSpec::Type1(font) | PdfFontSpec::MultipleMasterType1(font) | PdfFontSpec::TrueType(font) => font.standard14.unwrap_or_default(), + PdfFontSpec::Type0(font) => { + standard14::from_base_font_name(&font.base_font).unwrap_or_default() + } _ => Standard14Font::Helvetica, } } diff --git a/crates/pdf-font/src/standard14.rs b/crates/pdf-font/src/standard14.rs index dbfd020b..88e7ba11 100644 --- a/crates/pdf-font/src/standard14.rs +++ b/crates/pdf-font/src/standard14.rs @@ -186,6 +186,13 @@ pub(crate) fn from_flags(flags: FontFlags) -> Standard14Font { } impl Standard14Font { + /// Returns whether the TrueType core font behind this identity, such as Arial for Helvetica, + /// keeps the standard Macintosh glyph order. The symbol fonts do not. + #[must_use] + pub fn has_standard_glyph_order(self) -> bool { + !matches!(self, Self::Symbol | Self::ZapfDingbats) + } + /// Returns the advance width, in 1/1000 em, of a named glyph in this font's AFM metrics. /// /// Courier is monospaced, so every glyph name is 600 units wide. Other fonts return `None` diff --git a/crates/pdf-font/src/type0_font_spec.rs b/crates/pdf-font/src/type0_font_spec.rs index 4a7caa8c..6e6156ec 100644 --- a/crates/pdf-font/src/type0_font_spec.rs +++ b/crates/pdf-font/src/type0_font_spec.rs @@ -5,11 +5,13 @@ use crate::{ font::{FontSource, GlyphId}, pdf::{CidSystemInfo, PdfFontDescriptor, PdfMetrics, ToUnicodeMap}, pdf_font_program::{CidType0Program, TrueTypeProgram}, + standard14::{Standard14Font, from_base_font_name}, }; use pdf_cmap::{Cid, PdfCMap, Type0EncodingCMap}; use pdf_object_reader::{ FromPdfObject, ObjectAccess, ObjectContext, ReadResult, object_variant::ObjectVariant, }; +use read_fonts::{ps::agl, tables::post::DEFAULT_GLYPH_NAMES}; use std::{collections::HashMap, sync::Arc}; /// Descendant subtype used by a Type 0 composite font. @@ -36,8 +38,8 @@ pub struct CidFontSpec { pub metrics: PdfMetrics, /// CID-to-glyph identifier mapping, used by CIDFontType2 only. pub cid_to_gid: CidToGidMap, - /// Best-effort CID-to-Unicode mapping for collection-backed font substitution. - pub cid_to_unicode: Option>>, + /// Best-effort CID-to-Unicode mapping for font substitution. + pub cid_to_unicode: Option, } /// Normalized Type 0 composite font. @@ -75,6 +77,33 @@ impl CidToGidMap { } } +/// Best-effort CID-to-Unicode mapping used when a CID font has no `/ToUnicode` entry. +#[derive(Clone)] +pub enum CidToUnicode { + /// Inverted predefined Unicode CMap of a CJK character collection. + Collection(Arc>), + /// CIDs are glyph indices in the standard Macintosh glyph order. + /// + /// TrueType core fonts such as Arial, Times New Roman, and Courier New keep this order for + /// their first 258 glyphs. A non-embedded Identity CIDFontType2 font naming one of them + /// selects glyphs of that font by index, so its CIDs follow this order. + StandardGlyphOrder, +} + +impl CidToUnicode { + /// Returns the Unicode value of `cid`, if known. + #[must_use] + pub fn get(&self, cid: u16) -> Option { + match self { + Self::Collection(map) => map.get(&cid).copied(), + Self::StandardGlyphOrder => DEFAULT_GLYPH_NAMES + .get(usize::from(cid)) + .copied() + .and_then(agl::name_to_char), + } + } +} + impl FromPdfObject for CidToGidMap { /// Decodes a stream into its `u16` table, ignoring a trailing odd byte. fn from_pdf_object(context: ObjectContext<'_, impl ObjectAccess + ?Sized>) -> ReadResult { @@ -127,8 +156,18 @@ impl FromPdfObject for CidFontSpec { let system_info = context .optional::(b"CIDSystemInfo")? .unwrap_or_default(); - let cid_to_unicode = system_info.cid_to_unicode_map(); - let cid_to_gid = context.lenient(b"CIDToGIDMap").unwrap_or_default(); + let cid_to_gid: CidToGidMap = context.lenient(b"CIDToGIDMap").unwrap_or_default(); + // A substituted TrueType core font without its own glyph map is indexed by CID. + let indexes_core_font = kind == CidFontKind::Type2 + && program.is_none() + && matches!(cid_to_gid, CidToGidMap::Identity) + && base_font + .as_deref() + .and_then(from_base_font_name) + .is_some_and(Standard14Font::has_standard_glyph_order); + let cid_to_unicode = system_info + .cid_to_unicode_map() + .or_else(|| indexes_core_font.then_some(CidToUnicode::StandardGlyphOrder)); Ok(Self { kind, descriptor, diff --git a/crates/pdf-text-engine/src/pdf_text_decoder.rs b/crates/pdf-text-engine/src/pdf_text_decoder.rs index e6a385d0..08f4be75 100644 --- a/crates/pdf-text-engine/src/pdf_text_decoder.rs +++ b/crates/pdf-text-engine/src/pdf_text_decoder.rs @@ -135,7 +135,7 @@ fn type0_selector(descendant: &CidFontSpec, cid: Cid) -> GlyphSelector<'static> /// Resolves extraction Unicode for one composite-font mapping. /// -/// The source-code `/ToUnicode` map is authoritative. A best-effort collection CID map is used only +/// The source-code `/ToUnicode` map is authoritative. The descendant's best-effort CID map is used only /// when `/ToUnicode` has no entry and the CID fits that map's 16-bit key space. fn type0_unicode(font: &Type0FontSpec, mapping: CidMapping) -> UnicodeSequence { font.to_unicode @@ -146,8 +146,7 @@ fn type0_unicode(font: &Type0FontSpec, mapping: CidMapping) -> UnicodeSequence { font.descendant .cid_to_unicode .as_ref()? - .get(&cid) - .copied() + .get(cid) .map(Into::into) }) .unwrap_or_default()