Kerned PDF output

This commit is contained in:
Laurenz 2021-08-23 13:18:20 +02:00
parent c0377de653
commit 0806af4aec
15 changed files with 184 additions and 96 deletions

View File

@ -24,7 +24,7 @@ fxhash = "0.2.1"
image = { version = "0.23", default-features = false, features = ["png", "jpeg"] } image = { version = "0.23", default-features = false, features = ["png", "jpeg"] }
itertools = "0.10" itertools = "0.10"
miniz_oxide = "0.4" miniz_oxide = "0.4"
pdf-writer = "0.3" pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "818659b" }
rand = "0.8" rand = "0.8"
rustybuzz = "0.4" rustybuzz = "0.4"
serde = { version = "1", features = ["derive", "rc"] } serde = { version = "1", features = ["derive", "rc"] }

View File

@ -14,8 +14,8 @@ use pdf_writer::{
use ttf_parser::{name_id, GlyphId}; use ttf_parser::{name_id, GlyphId};
use crate::color::Color; use crate::color::Color;
use crate::font::{Em, FaceId, FontStore}; use crate::font::{FaceId, FontStore};
use crate::geom::{self, Length, Size}; use crate::geom::{self, Em, Length, Size};
use crate::image::{Image, ImageId, ImageStore}; use crate::image::{Image, ImageId, ImageStore};
use crate::layout::{Element, Frame, Geometry, Paint}; use crate::layout::{Element, Frame, Geometry, Paint};
use crate::Context; use crate::Context;
@ -140,7 +140,7 @@ impl<'a> PdfExporter<'a> {
// We only write font switching actions when the used face changes. To // We only write font switching actions when the used face changes. To
// do that, we need to remember the active face. // 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 size = Length::zero();
let mut fill: Option<Paint> = None; let mut fill: Option<Paint> = None;
@ -159,17 +159,50 @@ impl<'a> PdfExporter<'a> {
// Then, also check if we need to issue a font switching // Then, also check if we need to issue a font switching
// action. // action.
if face != Some(shaped.face_id) || shaped.size != size { if face_id != Some(shaped.face_id) || shaped.size != size {
face = Some(shaped.face_id); face_id = Some(shaped.face_id);
size = shaped.size; size = shaped.size;
let name = format!("F{}", self.font_map.map(shaped.face_id)); let name = format!("F{}", self.font_map.map(shaped.face_id));
text.font(Name(name.as_bytes()), size.to_pt() as f32); 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.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) => { Element::Geometry(ref geometry, paint) => {

View File

@ -2,15 +2,13 @@
use std::collections::{hash_map::Entry, HashMap}; use std::collections::{hash_map::Entry, HashMap};
use std::fmt::{self, Debug, Display, Formatter}; use std::fmt::{self, Debug, Display, Formatter};
use std::ops::Add;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::rc::Rc; use std::rc::Rc;
use decorum::N64;
use serde::{Deserialize, Serialize}; 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}; use crate::loading::{FileHash, Loader};
/// A unique identifier for a loaded font face. /// A unique identifier for a loaded font face.
@ -255,6 +253,13 @@ impl Face {
Em::from_units(units, self.units_per_em) Em::from_units(units, self.units_per_em)
} }
/// Look up the horizontal advance width of a glyph.
pub fn advance(&self, glyph: u16) -> Option<Em> {
self.ttf
.glyph_hor_advance(GlyphId(glyph))
.map(|units| self.to_em(units))
}
/// Look up a vertical metric. /// Look up a vertical metric.
pub fn vertical_metric(&self, metric: VerticalFontMetric) -> Em { pub fn vertical_metric(&self, metric: VerticalFontMetric) -> Em {
match metric { 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<f64>, 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. /// Identifies a vertical metric of a font.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum VerticalFontMetric { pub enum VerticalFontMetric {

View File

@ -1,6 +1,3 @@
use decorum::N64;
use serde::{Deserialize, Serialize};
use super::*; use super::*;
/// An angle. /// An angle.

112
src/geom/em.rs Normal file
View File

@ -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<f64>, 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<f64> for Em {
type Output = Self;
fn mul(self, other: f64) -> Self {
Self(self.0 * other)
}
}
impl Mul<Em> for f64 {
type Output = Em;
fn mul(self, other: Em) -> Em {
other * self
}
}
impl Div<f64> 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);

View File

@ -1,5 +1,3 @@
use decorum::N64;
use super::*; use super::*;
/// A fractional length. /// A fractional length.

View File

@ -1,6 +1,3 @@
use decorum::N64;
use serde::{Deserialize, Serialize};
use super::*; use super::*;
/// An absolute length. /// An absolute length.

View File

@ -5,6 +5,7 @@ mod macros;
mod align; mod align;
mod angle; mod angle;
mod dir; mod dir;
mod em;
mod fr; mod fr;
mod gen; mod gen;
mod length; mod length;
@ -19,6 +20,7 @@ mod spec;
pub use align::*; pub use align::*;
pub use angle::*; pub use angle::*;
pub use dir::*; pub use dir::*;
pub use em::*;
pub use fr::*; pub use fr::*;
pub use gen::*; pub use gen::*;
pub use length::*; pub use length::*;
@ -35,6 +37,9 @@ use std::fmt::{self, Debug, Display, Formatter};
use std::iter::Sum; use std::iter::Sum;
use std::ops::*; use std::ops::*;
use decorum::N64;
use serde::{Deserialize, Serialize};
/// Generic access to a structure's components. /// Generic access to a structure's components.
pub trait Get<Index> { pub trait Get<Index> {
/// The structure's component type. /// The structure's component type.

View File

@ -1,7 +1,5 @@
use super::*; use super::*;
use serde::{Deserialize, Serialize};
/// A bezier path. /// A bezier path.
#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]

View File

@ -1,7 +1,5 @@
use super::*; use super::*;
use serde::{Deserialize, Serialize};
/// A point in 2D. /// A point in 2D.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct Point { pub struct Point {

View File

@ -1,5 +1,3 @@
use decorum::N64;
use super::*; use super::*;
/// A relative length. /// A relative length.

View File

@ -1,7 +1,5 @@
use super::*; use super::*;
use serde::{Deserialize, Serialize};
/// A size in 2D. /// A size in 2D.
#[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Default, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct Size { pub struct Size {

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use super::{Constrained, Constraints}; use super::{Constrained, Constraints};
use crate::color::Color; use crate::color::Color;
use crate::font::FaceId; use crate::font::FaceId;
use crate::geom::{Length, Path, Point, Size}; use crate::geom::{Em, Length, Path, Point, Size};
use crate::image::ImageId; use crate::image::ImageId;
/// A finished layout with elements at fixed positions. /// A finished layout with elements at fixed positions.
@ -130,27 +130,15 @@ pub struct Text {
pub glyphs: Vec<Glyph>, pub glyphs: Vec<Glyph>,
} }
impl Text {
/// Encode the glyph ids into a big-endian byte buffer.
pub fn encode_glyphs_be(&self) -> Vec<u8> {
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. /// A glyph in a run of shaped text.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Glyph { pub struct Glyph {
/// The glyph's index in the face. /// The glyph's index in the face.
pub id: u16, pub id: u16,
/// The advance width of the glyph. /// The advance width of the glyph.
pub x_advance: Length, pub x_advance: Em,
/// The horizontal offset of the glyph. /// The horizontal offset of the glyph.
pub x_offset: Length, pub x_offset: Em,
} }
/// A geometric shape. /// A geometric shape.

View File

@ -6,7 +6,7 @@ use rustybuzz::UnicodeBuffer;
use super::{Element, Frame, Glyph, LayoutContext, Text}; use super::{Element, Frame, Glyph, LayoutContext, Text};
use crate::eval::{FontState, LineState}; use crate::eval::{FontState, LineState};
use crate::font::{Face, FaceId, FontVariant, LineMetrics}; 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::layout::Geometry;
use crate::util::SliceExt; use crate::util::SliceExt;
@ -72,9 +72,9 @@ pub struct ShapedGlyph {
/// The glyph's index in the face. /// The glyph's index in the face.
pub glyph_id: u16, pub glyph_id: u16,
/// The advance width of the glyph. /// The advance width of the glyph.
pub x_advance: Length, pub x_advance: Em,
/// The horizontal offset of the glyph. /// The horizontal offset of the glyph.
pub x_offset: Length, pub x_offset: Em,
/// The start index of the glyph in the source text. /// The start index of the glyph in the source text.
pub text_index: usize, pub text_index: usize,
/// Whether splitting the shaping result before this glyph would yield the /// 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_advance: glyph.x_advance,
x_offset: glyph.x_offset, x_offset: glyph.x_offset,
}); });
width += glyph.x_advance; width += glyph.x_advance.to_length(text.size);
} }
frame.push(pos, Element::Text(text)); frame.push(pos, Element::Text(text));
@ -267,8 +267,8 @@ fn shape_segment<'a>(
glyphs.push(ShapedGlyph { glyphs.push(ShapedGlyph {
face_id, face_id,
glyph_id: info.glyph_id as u16, glyph_id: info.glyph_id as u16,
x_advance: face.to_em(pos[i].x_advance).to_length(size), x_advance: face.to_em(pos[i].x_advance),
x_offset: face.to_em(pos[i].x_offset).to_length(size), x_offset: face.to_em(pos[i].x_offset),
text_index: base + cluster, text_index: base + cluster,
safe_to_break: !info.unsafe_to_break(), safe_to_break: !info.unsafe_to_break(),
}); });
@ -342,7 +342,9 @@ fn measure(
let mut width = Length::zero(); let mut width = Length::zero();
let mut top = Length::zero(); let mut top = Length::zero();
let mut bottom = 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)); 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)); bottom.set_max(-face.vertical_metric(state.bottom_edge).to_length(state.size));
}; };
@ -352,17 +354,17 @@ fn measure(
// first available font. // first available font.
for family in state.families() { for family in state.families() {
if let Some(face_id) = ctx.fonts.select(family, state.variant) { 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; break;
} }
} }
} else { } else {
for (face_id, group) in glyphs.group_by_key(|g| g.face_id) { for (face_id, group) in glyphs.group_by_key(|g| g.face_id) {
let face = ctx.fonts.get(face_id); let face = ctx.fonts.get(face_id);
expand_vertical(face); expand(face);
for glyph in group { for glyph in group {
width += glyph.x_advance; width += glyph.x_advance.to_length(state.size);
} }
} }
} }

View File

@ -444,7 +444,7 @@ fn draw_text(canvas: &mut sk::Pixmap, ts: sk::Transform, ctx: &Context, text: &T
for glyph in &text.glyphs { for glyph in &text.glyphs {
let units_per_em = ttf.units_per_em(); let units_per_em = ttf.units_per_em();
let s = text.size.to_pt() as f32 / units_per_em as f32; 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); let ts = ts.pre_translate(x + dx, 0.0);
// Try drawing SVG if present. // 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;
} }
} }