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"] }
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"] }

View File

@ -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<Paint> = 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) => {

View File

@ -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<Em> {
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<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.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum VerticalFontMetric {

View File

@ -1,6 +1,3 @@
use decorum::N64;
use serde::{Deserialize, Serialize};
use super::*;
/// 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::*;
/// A fractional length.

View File

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

View File

@ -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<Index> {
/// The structure's component type.

View File

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

View File

@ -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 {

View File

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

View File

@ -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 {

View File

@ -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<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.
#[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.

View File

@ -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);
}
}
}

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 {
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;
}
}