Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crates/pdf-font/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,14 @@ pub trait FontFace: Send + Sync {
/// Resolves a PostScript glyph name when supported by the face.
fn glyph_for_name(&self, name: &GlyphName) -> Option<GlyphId>;

/// Resolves a one-byte simple-font character code through the face's code-keyed cmap
/// subtables, as symbolic TrueType fonts require.
///
/// Faces without such subtables return `None`.
fn glyph_for_code(&self, _code: u8) -> Option<GlyphId> {
None
}

/// Returns the glyph's horizontal advance in font design units when available.
///
/// PDF layout uses this only when an unrelated fallback face supplies the glyph. Native PDF
Expand Down
33 changes: 32 additions & 1 deletion crates/pdf-font/src/true_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
use std::sync::Arc;

use bytes::Bytes;
use read_fonts::{TableProvider, tables::post::DEFAULT_GLYPH_NAMES, types::Version16Dot16};
use read_fonts::{
TableProvider,
tables::{cmap::PlatformId, post::DEFAULT_GLYPH_NAMES},
types::Version16Dot16,
};
use self_cell::self_cell;
use skrifa::{
FontRef, GlyphNames, MetadataProvider,
Expand Down Expand Up @@ -52,6 +56,22 @@ impl<'a> TrueTypeProgramRef<'a> {
outlines,
})
}

/// Looks up a character code in the cmap subtable with the given platform and encoding.
///
/// Glyph zero is `.notdef`, so it counts as a miss.
fn code_glyph(&self, platform: PlatformId, encoding: u16, code: u32) -> Option<GlyphId> {
let cmap = self.font.cmap().ok()?;
let record = cmap
.encoding_records()
.iter()
.find(|record| record.platform_id() == platform && record.encoding_id() == encoding)?;
let glyph = record
.subtable(cmap.offset_data())
.ok()?
.map_codepoint(code)?;
(glyph.to_u32() != 0).then(|| GlyphId(glyph.to_u32()))
}
}

self_cell!(
Expand Down Expand Up @@ -230,6 +250,17 @@ impl FontFace for TrueTypeFontFace {
self.resolve_glyph_name(name)
}

/// Looks the code up in a (3,0) Microsoft Symbol subtable, directly and in the `0xF000`
/// private-use range, then in a (1,0) Macintosh Roman subtable.
fn glyph_for_code(&self, code: u8) -> Option<GlyphId> {
let program = self.program.borrow_dependent();
let code = u32::from(code);
program
.code_glyph(PlatformId::Windows, 0, code)
.or_else(|| program.code_glyph(PlatformId::Windows, 0, 0xF000 | code))
.or_else(|| program.code_glyph(PlatformId::Macintosh, 0, code))
}

fn horizontal_advance(&self, glyph: GlyphId) -> Result<Option<f32>, FontError> {
Ok(self
.program
Expand Down
34 changes: 22 additions & 12 deletions crates/pdf-text-engine/src/pdf_text_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,7 @@ fn resolve_glyph(
cache: &mut FallbackCache,
decoded: &DecodedGlyph<'_>,
) -> Result<ResolvedGlyph, TextError> {
if let Some(glyph_id) = select_glyph(
font.primary().as_ref(),
&decoded.selector,
decoded.unicode.as_slice(),
font.uses_substitute(),
) {
if let Some(glyph_id) = select_glyph(font.primary().as_ref(), decoded, font.uses_substitute()) {
return Ok(ResolvedGlyph {
face: ResolvedFace::Primary,
glyph_id,
Expand Down Expand Up @@ -366,26 +361,41 @@ fn fallback_fit(
///
/// Whole-font substitutes prefer Unicode because embedded glyph IDs, names, and CIDs generally do
/// not address the substitute's glyph space. Native faces try their explicit PDF selector first,
/// then use the first Unicode scalar as a recovery path.
/// then use the first Unicode scalar as a recovery path. Without an explicit selector, a one-byte
/// code is looked up in the face's code-keyed cmap subtables: before Unicode for a symbolic font,
/// whose codes address those subtables directly, and after it otherwise.
fn select_glyph(
face: &dyn FontFace,
selector: &GlyphSelector<'_>,
unicode: &[char],
decoded: &DecodedGlyph<'_>,
prefer_unicode: bool,
) -> Option<GlyphId> {
let unicode_glyph = || {
unicode
decoded
.unicode
.as_slice()
.first()
.and_then(|character| face.glyph_for_char(*character))
};
if prefer_unicode && let Some(glyph) = unicode_glyph() {
return Some(glyph);
}
let selected = match selector {
let selected = match &decoded.selector {
GlyphSelector::GlyphId(glyph) => Some(*glyph),
GlyphSelector::GlyphName(name) => face.glyph_for_name(name),
GlyphSelector::Cid(Cid(cid)) => Some(GlyphId(*cid)),
GlyphSelector::Unspecified => None,
GlyphSelector::Unspecified => {
let code_glyph = || {
(decoded.source_code.byte_len() == 1)
.then(|| u8::try_from(decoded.source_code.value()).ok())
.flatten()
.and_then(|code| face.glyph_for_code(code))
};
return if face.metadata().symbolic {
code_glyph().or_else(unicode_glyph)
} else {
unicode_glyph().or_else(code_glyph)
};
}
};
selected.or_else(unicode_glyph)
}
Expand Down
Loading