diff --git a/Cargo.toml b/Cargo.toml index ab8c4bcbd..4841454cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ image = { version = "0.23", default-features = false, features = ["jpeg", "png"] miniz_oxide = "0.3" pdf-writer = { path = "../pdf-writer" } rustybuzz = { git = "https://github.com/laurmaedje/rustybuzz" } -ttf-parser = "0.9" +ttf-parser = "0.12" unicode-bidi = "0.3" unicode-xid = "0.2" xi-unicode = "0.3" diff --git a/src/font.rs b/src/font.rs index a816ed066..1ac8fea37 100644 --- a/src/font.rs +++ b/src/font.rs @@ -10,8 +10,7 @@ use crate::geom::Length; pub struct FaceBuf { data: Box<[u8]>, index: u32, - ttf: ttf_parser::Face<'static>, - buzz: rustybuzz::Face<'static>, + inner: rustybuzz::Face<'static>, units_per_em: f64, ascender: f64, cap_height: f64, @@ -30,23 +29,16 @@ impl FaceBuf { self.index } - /// Get a reference to the underlying ttf-parser face. - pub fn ttf(&self) -> &ttf_parser::Face<'_> { + /// Get a reference to the underlying ttf-parser/rustybuzz face. + pub fn ttf(&self) -> &rustybuzz::Face<'_> { // We can't implement Deref because that would leak the internal 'static // lifetime. - &self.ttf + &self.inner } - /// Get a reference to the underlying rustybuzz face. - pub fn buzz(&self) -> &rustybuzz::Face<'_> { - // We can't implement Deref because that would leak the internal 'static - // lifetime. - &self.buzz - } - - /// Look up a vertical metric at a given font size. - pub fn vertical_metric(&self, size: Length, metric: VerticalFontMetric) -> Length { - self.convert(size, match metric { + /// Look up a vertical metric. + pub fn vertical_metric(&self, metric: VerticalFontMetric) -> EmLength { + self.convert(match metric { VerticalFontMetric::Ascender => self.ascender, VerticalFontMetric::CapHeight => self.cap_height, VerticalFontMetric::XHeight => self.x_height, @@ -55,9 +47,9 @@ impl FaceBuf { }) } - /// Convert from font units to a length at a given font size. - pub fn convert(&self, size: Length, units: impl Into) -> Length { - units.into() / self.units_per_em * size + /// Convert from font units to an em length length. + pub fn convert(&self, units: impl Into) -> EmLength { + EmLength(units.into() / self.units_per_em) } } @@ -70,21 +62,19 @@ impl FaceFromVec for FaceBuf { let slice: &'static [u8] = unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) }; - let ttf = ttf_parser::Face::from_slice(slice, index).ok()?; - let buzz = rustybuzz::Face::from_slice(slice, index)?; + let inner = rustybuzz::Face::from_slice(slice, index)?; // Look up some metrics we may need often. - let units_per_em = ttf.units_per_em().unwrap_or(1000); - let ascender = ttf.typographic_ascender().unwrap_or(ttf.ascender()); - let cap_height = ttf.capital_height().filter(|&h| h > 0).unwrap_or(ascender); - let x_height = ttf.x_height().filter(|&h| h > 0).unwrap_or(ascender); - let descender = ttf.typographic_descender().unwrap_or(ttf.descender()); + let units_per_em = inner.units_per_em(); + let ascender = inner.typographic_ascender().unwrap_or(inner.ascender()); + let cap_height = inner.capital_height().filter(|&h| h > 0).unwrap_or(ascender); + let x_height = inner.x_height().filter(|&h| h > 0).unwrap_or(ascender); + let descender = inner.typographic_descender().unwrap_or(inner.descender()); Some(Self { data, index, - ttf, - buzz, + inner, units_per_em: f64::from(units_per_em), ascender: f64::from(ascender), cap_height: f64::from(cap_height), @@ -94,6 +84,22 @@ impl FaceFromVec for FaceBuf { } } +/// A length in resolved em units. +#[derive(Default, Debug, Copy, Clone, PartialEq, PartialOrd)] +pub struct EmLength(f64); + +impl EmLength { + /// Convert to a length at the given font size. + pub fn scale(self, size: Length) -> Length { + self.0 * size + } + + /// Get the number of em units. + pub fn get(self) -> f64 { + self.0 + } +} + /// Identifies a vertical metric of a font. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum VerticalFontMetric { diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs index b062f602d..14dd79b10 100644 --- a/src/layout/shaping.rs +++ b/src/layout/shaping.rs @@ -39,7 +39,7 @@ pub struct ShapedGlyph { /// The font face the glyph is contained in. pub face_id: FaceId, /// The glyph's ID in the face. - pub id: GlyphId, + pub glyph_id: GlyphId, /// The advance width of the glyph. pub x_advance: i32, /// The horizontal offset of the glyph. @@ -59,8 +59,6 @@ impl<'a> ShapedText<'a> { let mut x = Length::ZERO; for (face_id, group) in self.glyphs.as_ref().group_by_key(|g| g.face_id) { - let face = loader.face(face_id); - let pos = Point::new(x, self.baseline); let mut text = Text { face_id, @@ -69,10 +67,11 @@ impl<'a> ShapedText<'a> { glyphs: vec![], }; + let face = loader.face(face_id); for glyph in group { - let x_advance = face.convert(self.props.size, glyph.x_advance); - let x_offset = face.convert(self.props.size, glyph.x_offset); - text.glyphs.push(Glyph { id: glyph.id, x_advance, x_offset }); + let x_advance = face.convert(glyph.x_advance).scale(self.props.size); + let x_offset = face.convert(glyph.x_offset).scale(self.props.size); + text.glyphs.push(Glyph { id: glyph.glyph_id, x_advance, x_offset }); x += x_advance; } @@ -218,7 +217,7 @@ fn shape_segment<'a>( }); // Shape! - let buffer = rustybuzz::shape(loader.face(face_id).buzz(), &[], buffer); + let buffer = rustybuzz::shape(loader.face(face_id).ttf(), &[], buffer); let infos = buffer.glyph_infos(); let pos = buffer.glyph_positions(); @@ -233,7 +232,7 @@ fn shape_segment<'a>( // TODO: Don't ignore y_advance and y_offset. glyphs.push(ShapedGlyph { face_id, - id: GlyphId(info.codepoint as u16), + glyph_id: GlyphId(info.codepoint as u16), x_advance: pos[i].x_advance, x_offset: pos[i].x_offset, text_index: base + cluster, @@ -306,8 +305,8 @@ fn measure( let mut bottom = Length::ZERO; let mut width = Length::ZERO; let mut vertical = |face: &FaceBuf| { - top = top.max(face.vertical_metric(props.size, props.top_edge)); - bottom = bottom.max(-face.vertical_metric(props.size, props.bottom_edge)); + top = top.max(face.vertical_metric(props.top_edge).scale(props.size)); + bottom = bottom.max(-face.vertical_metric(props.bottom_edge).scale(props.size)); }; if glyphs.is_empty() { @@ -325,7 +324,7 @@ fn measure( vertical(face); for glyph in group { - width += face.convert(props.size, glyph.x_advance); + width += face.convert(glyph.x_advance).scale(props.size); } } } diff --git a/src/pdf/mod.rs b/src/pdf/mod.rs index 656635fce..365275444 100644 --- a/src/pdf/mod.rs +++ b/src/pdf/mod.rs @@ -15,6 +15,7 @@ use ttf_parser::{name_id, GlyphId}; use crate::color::Color; use crate::env::{Env, ImageResource, ResourceId}; +use crate::font::{EmLength, VerticalFontMetric}; use crate::geom::{self, Length, Size}; use crate::layout::{Element, Fill, Frame, Image, Shape}; @@ -234,24 +235,18 @@ impl<'a> PdfExporter<'a> { flags.insert(FontFlags::SYMBOLIC); flags.insert(FontFlags::SMALL_CAP); - // Convert from OpenType font units to PDF glyph units. - let em_per_unit = 1.0 / ttf.units_per_em().unwrap_or(1000) as f32; - let convert = |font_unit: f32| (1000.0 * em_per_unit * font_unit).round(); - let convert_i16 = |font_unit: i16| convert(font_unit as f32); - let convert_u16 = |font_unit: u16| convert(font_unit as f32); - let global_bbox = ttf.global_bounding_box(); let bbox = Rect::new( - convert_i16(global_bbox.x_min), - convert_i16(global_bbox.y_min), - convert_i16(global_bbox.x_max), - convert_i16(global_bbox.y_max), + face.convert(global_bbox.x_min).to_pdf(), + face.convert(global_bbox.y_min).to_pdf(), + face.convert(global_bbox.x_max).to_pdf(), + face.convert(global_bbox.y_max).to_pdf(), ); let italic_angle = ttf.italic_angle().unwrap_or(0.0); - let ascender = convert_i16(ttf.typographic_ascender().unwrap_or(0)); - let descender = convert_i16(ttf.typographic_descender().unwrap_or(0)); - let cap_height = ttf.capital_height().map(convert_i16); + let ascender = face.vertical_metric(VerticalFontMetric::Ascender).to_pdf(); + let descender = face.vertical_metric(VerticalFontMetric::Descender).to_pdf(); + let cap_height = face.vertical_metric(VerticalFontMetric::CapHeight).to_pdf(); let stem_v = 10.0 + 0.244 * (f32::from(ttf.weight().to_number()) - 50.0); // Write the base font object referencing the CID font. @@ -272,8 +267,8 @@ impl<'a> PdfExporter<'a> { .individual(0, { let num_glyphs = ttf.number_of_glyphs(); (0 .. num_glyphs).map(|g| { - let advance = ttf.glyph_hor_advance(GlyphId(g)); - convert_u16(advance.unwrap_or(0)) + let x = ttf.glyph_hor_advance(GlyphId(g)).unwrap_or(0); + face.convert(x).to_pdf() }) }); @@ -286,7 +281,7 @@ impl<'a> PdfExporter<'a> { .italic_angle(italic_angle) .ascent(ascender) .descent(descender) - .cap_height(cap_height.unwrap_or(ascender)) + .cap_height(cap_height) .stem_v(stem_v) .font_file2(refs.data); @@ -571,3 +566,15 @@ where self.to_layout.iter().copied() } } + +/// Additional methods for [`EmLength`]. +trait EmLengthExt { + /// Convert an em length to a number of PDF font units. + fn to_pdf(self) -> f32; +} + +impl EmLengthExt for EmLength { + fn to_pdf(self) -> f32 { + 1000.0 * self.get() as f32 + } +} diff --git a/tests/typeset.rs b/tests/typeset.rs index 771c86daf..6aef27463 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -418,9 +418,8 @@ fn draw_text(canvas: &mut Pixmap, env: &Env, ts: Transform, shaped: &Text) { let mut x = 0.0; for glyph in &shaped.glyphs { - let units_per_em = ttf.units_per_em().unwrap_or(1000); - - let s = (shaped.size / units_per_em as f64).to_pt() as f32; + let units_per_em = ttf.units_per_em(); + let s = shaped.size.to_pt() as f32 / units_per_em as f32; let dx = glyph.x_offset.to_pt() as f32; let ts = ts.pre_translate(x + dx, 0.0);