diff --git a/Cargo.toml b/Cargo.toml index 066d80c2e..f15d22d7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ fxhash = "0.2.1" image = { version = "0.23", default-features = false, features = ["png", "jpeg"] } itertools = "0.10" miniz_oxide = "0.4" -pdf-writer = "0.3" +pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "818659b" } rand = "0.8" rustybuzz = "0.4" serde = { version = "1", features = ["derive", "rc"] } diff --git a/src/export/pdf.rs b/src/export/pdf.rs index e77ad9311..7ff600ffe 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -14,8 +14,8 @@ use pdf_writer::{ use ttf_parser::{name_id, GlyphId}; use crate::color::Color; -use crate::font::{Em, FaceId, FontStore}; -use crate::geom::{self, Length, Size}; +use crate::font::{FaceId, FontStore}; +use crate::geom::{self, Em, Length, Size}; use crate::image::{Image, ImageId, ImageStore}; use crate::layout::{Element, Frame, Geometry, Paint}; use crate::Context; @@ -140,7 +140,7 @@ impl<'a> PdfExporter<'a> { // We only write font switching actions when the used face changes. To // do that, we need to remember the active face. - let mut face = None; + let mut face_id = None; let mut size = Length::zero(); let mut fill: Option = None; @@ -159,17 +159,50 @@ impl<'a> PdfExporter<'a> { // Then, also check if we need to issue a font switching // action. - if face != Some(shaped.face_id) || shaped.size != size { - face = Some(shaped.face_id); + if face_id != Some(shaped.face_id) || shaped.size != size { + face_id = Some(shaped.face_id); size = shaped.size; let name = format!("F{}", self.font_map.map(shaped.face_id)); text.font(Name(name.as_bytes()), size.to_pt() as f32); } - // TODO: Respect individual glyph offsets. + let face = self.fonts.get(shaped.face_id); + + // Position the text. text.matrix(1.0, 0.0, 0.0, 1.0, x, y); - text.show(Str(&shaped.encode_glyphs_be())); + + let mut positioned = text.show_positioned(); + let mut adjustment = Em::zero(); + let mut encoded = vec![]; + + // Write the glyphs with kerning adjustments. + for glyph in &shaped.glyphs { + adjustment += glyph.x_offset; + + if !adjustment.is_zero() { + if !encoded.is_empty() { + positioned.show(Str(&encoded)); + encoded.clear(); + } + + positioned.adjust(-adjustment.to_pdf()); + adjustment = Em::zero(); + } + + encoded.push((glyph.id >> 8) as u8); + encoded.push((glyph.id & 0xff) as u8); + + if let Some(advance) = face.advance(glyph.id) { + adjustment += glyph.x_advance - advance; + } + + adjustment -= glyph.x_offset; + } + + if !encoded.is_empty() { + positioned.show(Str(&encoded)); + } } Element::Geometry(ref geometry, paint) => { diff --git a/src/font.rs b/src/font.rs index dadf6830b..690884f44 100644 --- a/src/font.rs +++ b/src/font.rs @@ -2,15 +2,13 @@ use std::collections::{hash_map::Entry, HashMap}; use std::fmt::{self, Debug, Display, Formatter}; -use std::ops::Add; use std::path::{Path, PathBuf}; use std::rc::Rc; -use decorum::N64; use serde::{Deserialize, Serialize}; -use ttf_parser::name_id; +use ttf_parser::{name_id, GlyphId}; -use crate::geom::Length; +use crate::geom::Em; use crate::loading::{FileHash, Loader}; /// A unique identifier for a loaded font face. @@ -255,6 +253,13 @@ impl Face { Em::from_units(units, self.units_per_em) } + /// Look up the horizontal advance width of a glyph. + pub fn advance(&self, glyph: u16) -> Option { + self.ttf + .glyph_hor_advance(GlyphId(glyph)) + .map(|units| self.to_em(units)) + } + /// Look up a vertical metric. pub fn vertical_metric(&self, metric: VerticalFontMetric) -> Em { match metric { @@ -267,47 +272,6 @@ impl Face { } } -/// A length in em units. -/// -/// `1em` is the same as the font size. -#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub struct Em(N64); - -impl Em { - /// The zero length. - pub fn zero() -> Self { - Self(N64::from(0.0)) - } - - /// Create an em length. - pub fn new(em: f64) -> Self { - Self(N64::from(em)) - } - - /// Convert units to an em length at the given units per em. - pub fn from_units(units: impl Into, units_per_em: f64) -> Self { - Self(N64::from(units.into() / units_per_em)) - } - - /// The number of em units. - pub fn get(self) -> f64 { - self.0.into() - } - - /// Convert to a length at the given font size. - pub fn to_length(self, font_size: Length) -> Length { - self.get() * font_size - } -} - -impl Add for Em { - type Output = Self; - - fn add(self, other: Self) -> Self { - Self(self.0 + other.0) - } -} - /// Identifies a vertical metric of a font. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum VerticalFontMetric { diff --git a/src/geom/angle.rs b/src/geom/angle.rs index cf3e49a0f..023b1d98e 100644 --- a/src/geom/angle.rs +++ b/src/geom/angle.rs @@ -1,6 +1,3 @@ -use decorum::N64; -use serde::{Deserialize, Serialize}; - use super::*; /// An angle. diff --git a/src/geom/em.rs b/src/geom/em.rs new file mode 100644 index 000000000..45e5ba608 --- /dev/null +++ b/src/geom/em.rs @@ -0,0 +1,112 @@ +use super::*; + +/// A length that is relative to the font size. +/// +/// `1em` is the same as the font size. +#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[derive(Serialize, Deserialize)] +pub struct Em(N64); + +impl Em { + /// The zero length. + pub fn zero() -> Self { + Self(N64::from(0.0)) + } + + /// The font size. + pub fn one() -> Self { + Self(N64::from(1.0)) + } + + /// Create an font-relative length. + pub fn new(em: f64) -> Self { + Self(N64::from(em)) + } + + /// Create font units at the given units per em. + pub fn from_units(units: impl Into, units_per_em: f64) -> Self { + Self(N64::from(units.into() / units_per_em)) + } + + /// Convert to a length at the given font size. + pub fn to_length(self, font_size: Length) -> Length { + self.get() * font_size + } + + /// The number of em units. + pub fn get(self) -> f64 { + self.0.into() + } + + /// Whether the length is zero. + pub fn is_zero(self) -> bool { + self.0 == 0.0 + } +} + +impl Debug for Em { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + Display::fmt(self, f) + } +} + +impl Display for Em { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}em", self.get()) + } +} + +impl Neg for Em { + type Output = Self; + + fn neg(self) -> Self { + Self(-self.0) + } +} + +impl Add for Em { + type Output = Self; + + fn add(self, other: Self) -> Self { + Self(self.0 + other.0) + } +} + +sub_impl!(Em - Em -> Em); + +impl Mul for Em { + type Output = Self; + + fn mul(self, other: f64) -> Self { + Self(self.0 * other) + } +} + +impl Mul for f64 { + type Output = Em; + + fn mul(self, other: Em) -> Em { + other * self + } +} + +impl Div for Em { + type Output = Self; + + fn div(self, other: f64) -> Self { + Self(self.0 / other) + } +} + +impl Div for Em { + type Output = f64; + + fn div(self, other: Self) -> f64 { + self.get() / other.get() + } +} + +assign_impl!(Em += Em); +assign_impl!(Em -= Em); +assign_impl!(Em *= f64); +assign_impl!(Em /= f64); diff --git a/src/geom/fr.rs b/src/geom/fr.rs index eaf539d96..ed0c83293 100644 --- a/src/geom/fr.rs +++ b/src/geom/fr.rs @@ -1,5 +1,3 @@ -use decorum::N64; - use super::*; /// A fractional length. diff --git a/src/geom/length.rs b/src/geom/length.rs index 3510fa6fa..f8484f75d 100644 --- a/src/geom/length.rs +++ b/src/geom/length.rs @@ -1,6 +1,3 @@ -use decorum::N64; -use serde::{Deserialize, Serialize}; - use super::*; /// An absolute length. diff --git a/src/geom/mod.rs b/src/geom/mod.rs index 6f48d7d9e..11e082b89 100644 --- a/src/geom/mod.rs +++ b/src/geom/mod.rs @@ -5,6 +5,7 @@ mod macros; mod align; mod angle; mod dir; +mod em; mod fr; mod gen; mod length; @@ -19,6 +20,7 @@ mod spec; pub use align::*; pub use angle::*; pub use dir::*; +pub use em::*; pub use fr::*; pub use gen::*; pub use length::*; @@ -35,6 +37,9 @@ use std::fmt::{self, Debug, Display, Formatter}; use std::iter::Sum; use std::ops::*; +use decorum::N64; +use serde::{Deserialize, Serialize}; + /// Generic access to a structure's components. pub trait Get { /// The structure's component type. diff --git a/src/geom/path.rs b/src/geom/path.rs index f4e4a8b22..bc0d3f2d4 100644 --- a/src/geom/path.rs +++ b/src/geom/path.rs @@ -1,7 +1,5 @@ use super::*; -use serde::{Deserialize, Serialize}; - /// A bezier path. #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] #[serde(transparent)] diff --git a/src/geom/point.rs b/src/geom/point.rs index 02f1d2771..8ab043c3b 100644 --- a/src/geom/point.rs +++ b/src/geom/point.rs @@ -1,7 +1,5 @@ use super::*; -use serde::{Deserialize, Serialize}; - /// A point in 2D. #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct Point { diff --git a/src/geom/relative.rs b/src/geom/relative.rs index 7785788ce..056af2066 100644 --- a/src/geom/relative.rs +++ b/src/geom/relative.rs @@ -1,5 +1,3 @@ -use decorum::N64; - use super::*; /// A relative length. diff --git a/src/geom/size.rs b/src/geom/size.rs index 44ceea362..506e1c8fa 100644 --- a/src/geom/size.rs +++ b/src/geom/size.rs @@ -1,7 +1,5 @@ use super::*; -use serde::{Deserialize, Serialize}; - /// A size in 2D. #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct Size { diff --git a/src/layout/frame.rs b/src/layout/frame.rs index 0c307dd49..e52e27514 100644 --- a/src/layout/frame.rs +++ b/src/layout/frame.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use super::{Constrained, Constraints}; use crate::color::Color; use crate::font::FaceId; -use crate::geom::{Length, Path, Point, Size}; +use crate::geom::{Em, Length, Path, Point, Size}; use crate::image::ImageId; /// A finished layout with elements at fixed positions. @@ -130,27 +130,15 @@ pub struct Text { pub glyphs: Vec, } -impl Text { - /// Encode the glyph ids into a big-endian byte buffer. - pub fn encode_glyphs_be(&self) -> Vec { - let mut bytes = Vec::with_capacity(2 * self.glyphs.len()); - for glyph in &self.glyphs { - bytes.push((glyph.id >> 8) as u8); - bytes.push((glyph.id & 0xff) as u8); - } - bytes - } -} - /// A glyph in a run of shaped text. #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Glyph { /// The glyph's index in the face. pub id: u16, /// The advance width of the glyph. - pub x_advance: Length, + pub x_advance: Em, /// The horizontal offset of the glyph. - pub x_offset: Length, + pub x_offset: Em, } /// A geometric shape. diff --git a/src/layout/shaping.rs b/src/layout/shaping.rs index fad7f2348..efee1591f 100644 --- a/src/layout/shaping.rs +++ b/src/layout/shaping.rs @@ -6,7 +6,7 @@ use rustybuzz::UnicodeBuffer; use super::{Element, Frame, Glyph, LayoutContext, Text}; use crate::eval::{FontState, LineState}; use crate::font::{Face, FaceId, FontVariant, LineMetrics}; -use crate::geom::{Dir, Length, Point, Size}; +use crate::geom::{Dir, Em, Length, Point, Size}; use crate::layout::Geometry; use crate::util::SliceExt; @@ -72,9 +72,9 @@ pub struct ShapedGlyph { /// The glyph's index in the face. pub glyph_id: u16, /// The advance width of the glyph. - pub x_advance: Length, + pub x_advance: Em, /// The horizontal offset of the glyph. - pub x_offset: Length, + pub x_offset: Em, /// The start index of the glyph in the source text. pub text_index: usize, /// Whether splitting the shaping result before this glyph would yield the @@ -106,7 +106,7 @@ impl<'a> ShapedText<'a> { x_advance: glyph.x_advance, x_offset: glyph.x_offset, }); - width += glyph.x_advance; + width += glyph.x_advance.to_length(text.size); } frame.push(pos, Element::Text(text)); @@ -267,8 +267,8 @@ fn shape_segment<'a>( glyphs.push(ShapedGlyph { face_id, glyph_id: info.glyph_id as u16, - x_advance: face.to_em(pos[i].x_advance).to_length(size), - x_offset: face.to_em(pos[i].x_offset).to_length(size), + x_advance: face.to_em(pos[i].x_advance), + x_offset: face.to_em(pos[i].x_offset), text_index: base + cluster, safe_to_break: !info.unsafe_to_break(), }); @@ -342,7 +342,9 @@ fn measure( let mut width = Length::zero(); let mut top = Length::zero(); let mut bottom = Length::zero(); - let mut expand_vertical = |face: &Face| { + + // Expand top and bottom by reading the face's vertical metrics. + let mut expand = |face: &Face| { top.set_max(face.vertical_metric(state.top_edge).to_length(state.size)); bottom.set_max(-face.vertical_metric(state.bottom_edge).to_length(state.size)); }; @@ -352,17 +354,17 @@ fn measure( // first available font. for family in state.families() { if let Some(face_id) = ctx.fonts.select(family, state.variant) { - expand_vertical(ctx.fonts.get(face_id)); + expand(ctx.fonts.get(face_id)); break; } } } else { for (face_id, group) in glyphs.group_by_key(|g| g.face_id) { let face = ctx.fonts.get(face_id); - expand_vertical(face); + expand(face); for glyph in group { - width += glyph.x_advance; + width += glyph.x_advance.to_length(state.size); } } } diff --git a/tests/typeset.rs b/tests/typeset.rs index ac5ab9e44..80eb9da45 100644 --- a/tests/typeset.rs +++ b/tests/typeset.rs @@ -444,7 +444,7 @@ fn draw_text(canvas: &mut sk::Pixmap, ts: sk::Transform, ctx: &Context, text: &T for glyph in &text.glyphs { let units_per_em = ttf.units_per_em(); let s = text.size.to_pt() as f32 / units_per_em as f32; - let dx = glyph.x_offset.to_pt() as f32; + let dx = glyph.x_offset.to_length(text.size).to_pt() as f32; let ts = ts.pre_translate(x + dx, 0.0); // Try drawing SVG if present. @@ -481,7 +481,7 @@ fn draw_text(canvas: &mut sk::Pixmap, ts: sk::Transform, ctx: &Context, text: &T } } - x += glyph.x_advance.to_pt() as f32; + x += glyph.x_advance.to_length(text.size).to_pt() as f32; } }